Drive: Add BatchableRequest subclass.
[chromium-blink-merge.git] / ui / views / controls / label.cc
blob3cd3907910b311f6a8f6af72755c9e804007148d
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/label.h"
7 #include <algorithm>
8 #include <cmath>
9 #include <limits>
10 #include <vector>
12 #include "base/i18n/rtl.h"
13 #include "base/logging.h"
14 #include "base/profiler/scoped_tracker.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "ui/accessibility/ax_view_state.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/geometry/insets.h"
21 #include "ui/gfx/text_elider.h"
22 #include "ui/native_theme/native_theme.h"
24 namespace views {
26 // static
27 const char Label::kViewClassName[] = "Label";
28 const int Label::kFocusBorderPadding = 1;
30 Label::Label() {
31 Init(base::string16(), gfx::FontList());
34 Label::Label(const base::string16& text) {
35 Init(text, gfx::FontList());
38 Label::Label(const base::string16& text, const gfx::FontList& font_list) {
39 Init(text, font_list);
42 Label::~Label() {
45 void Label::SetFontList(const gfx::FontList& font_list) {
46 is_first_paint_text_ = true;
47 render_text_->SetFontList(font_list);
48 ResetLayout();
51 void Label::SetText(const base::string16& new_text) {
52 if (new_text == text())
53 return;
54 is_first_paint_text_ = true;
55 render_text_->SetText(new_text);
56 ResetLayout();
59 void Label::SetAutoColorReadabilityEnabled(bool enabled) {
60 if (auto_color_readability_ == enabled)
61 return;
62 is_first_paint_text_ = true;
63 auto_color_readability_ = enabled;
64 RecalculateColors();
67 void Label::SetEnabledColor(SkColor color) {
68 if (enabled_color_set_ && requested_enabled_color_ == color)
69 return;
70 is_first_paint_text_ = true;
71 requested_enabled_color_ = color;
72 enabled_color_set_ = true;
73 RecalculateColors();
76 void Label::SetDisabledColor(SkColor color) {
77 if (disabled_color_set_ && requested_disabled_color_ == color)
78 return;
79 is_first_paint_text_ = true;
80 requested_disabled_color_ = color;
81 disabled_color_set_ = true;
82 RecalculateColors();
85 void Label::SetBackgroundColor(SkColor color) {
86 if (background_color_set_ && background_color_ == color)
87 return;
88 is_first_paint_text_ = true;
89 background_color_ = color;
90 background_color_set_ = true;
91 RecalculateColors();
94 void Label::SetShadows(const gfx::ShadowValues& shadows) {
95 // TODO(mukai): early exit if the specified shadows are same.
96 is_first_paint_text_ = true;
97 render_text_->set_shadows(shadows);
98 ResetLayout();
101 void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
102 if (subpixel_rendering_enabled_ == subpixel_rendering_enabled)
103 return;
104 is_first_paint_text_ = true;
105 subpixel_rendering_enabled_ = subpixel_rendering_enabled;
106 RecalculateColors();
109 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
110 // If the UI layout is right-to-left, flip the alignment direction.
111 if (base::i18n::IsRTL() &&
112 (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) {
113 alignment = (alignment == gfx::ALIGN_LEFT) ?
114 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
116 if (horizontal_alignment() == alignment)
117 return;
118 is_first_paint_text_ = true;
119 render_text_->SetHorizontalAlignment(alignment);
120 ResetLayout();
123 void Label::SetLineHeight(int height) {
124 if (line_height() == height)
125 return;
126 is_first_paint_text_ = true;
127 render_text_->SetMinLineHeight(height);
128 ResetLayout();
131 void Label::SetMultiLine(bool multi_line) {
132 DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL ||
133 elide_behavior_ == gfx::NO_ELIDE));
134 if (this->multi_line() == multi_line)
135 return;
136 is_first_paint_text_ = true;
137 multi_line_ = multi_line;
138 if (render_text_->MultilineSupported())
139 render_text_->SetMultiline(multi_line);
140 render_text_->SetReplaceNewlineCharsWithSymbols(!multi_line);
141 ResetLayout();
144 void Label::SetObscured(bool obscured) {
145 if (this->obscured() == obscured)
146 return;
147 is_first_paint_text_ = true;
148 render_text_->SetObscured(obscured);
149 ResetLayout();
152 void Label::SetAllowCharacterBreak(bool allow_character_break) {
153 const gfx::WordWrapBehavior behavior =
154 allow_character_break ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS;
155 if (render_text_->word_wrap_behavior() == behavior)
156 return;
157 render_text_->SetWordWrapBehavior(behavior);
158 if (multi_line()) {
159 is_first_paint_text_ = true;
160 ResetLayout();
164 void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
165 DCHECK(!multi_line() || (elide_behavior_ == gfx::ELIDE_TAIL ||
166 elide_behavior_ == gfx::NO_ELIDE));
167 if (elide_behavior_ == elide_behavior)
168 return;
169 is_first_paint_text_ = true;
170 elide_behavior_ = elide_behavior;
171 ResetLayout();
174 void Label::SetTooltipText(const base::string16& tooltip_text) {
175 DCHECK(handles_tooltips_);
176 tooltip_text_ = tooltip_text;
179 void Label::SetHandlesTooltips(bool enabled) {
180 handles_tooltips_ = enabled;
183 void Label::SizeToFit(int max_width) {
184 DCHECK(multi_line());
185 max_width_ = max_width;
186 SizeToPreferredSize();
189 base::string16 Label::GetDisplayTextForTesting() {
190 lines_.clear();
191 MaybeBuildRenderTextLines();
192 base::string16 result;
193 if (lines_.empty())
194 return result;
195 result.append(lines_[0]->GetDisplayText());
196 for (size_t i = 1; i < lines_.size(); ++i) {
197 result.append(1, '\n');
198 result.append(lines_[i]->GetDisplayText());
200 return result;
203 gfx::Insets Label::GetInsets() const {
204 gfx::Insets insets = View::GetInsets();
205 if (focusable()) {
206 insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding,
207 kFocusBorderPadding, kFocusBorderPadding);
209 return insets;
212 int Label::GetBaseline() const {
213 return GetInsets().top() + font_list().GetBaseline();
216 gfx::Size Label::GetPreferredSize() const {
217 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
218 tracked_objects::ScopedTracker tracking_profile(
219 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::GetPreferredSize"));
221 // Return a size of (0, 0) if the label is not visible and if the
222 // |collapse_when_hidden_| flag is set.
223 // TODO(munjal): This logic probably belongs to the View class. But for now,
224 // put it here since putting it in View class means all inheriting classes
225 // need to respect the |collapse_when_hidden_| flag.
226 if (!visible() && collapse_when_hidden_)
227 return gfx::Size();
229 if (multi_line() && max_width_ != 0 && !text().empty())
230 return gfx::Size(max_width_, GetHeightForWidth(max_width_));
232 gfx::Size size(GetTextSize());
233 const gfx::Insets insets = GetInsets();
234 size.Enlarge(insets.width(), insets.height());
235 return size;
238 gfx::Size Label::GetMinimumSize() const {
239 if (!visible() && collapse_when_hidden_)
240 return gfx::Size();
242 gfx::Size size(0, font_list().GetHeight());
243 if (elide_behavior_ == gfx::ELIDE_HEAD ||
244 elide_behavior_ == gfx::ELIDE_MIDDLE ||
245 elide_behavior_ == gfx::ELIDE_TAIL ||
246 elide_behavior_ == gfx::ELIDE_EMAIL) {
247 size.set_width(gfx::Canvas::GetStringWidth(
248 base::string16(gfx::kEllipsisUTF16), font_list()));
250 if (!multi_line())
251 size.SetToMin(GetTextSize());
252 size.Enlarge(GetInsets().width(), GetInsets().height());
253 return size;
256 int Label::GetHeightForWidth(int w) const {
257 if (!visible() && collapse_when_hidden_)
258 return 0;
260 w -= GetInsets().width();
261 int height = 0;
262 if (!multi_line() || text().empty() || w <= 0) {
263 height = std::max(line_height(), font_list().GetHeight());
264 } else if (render_text_->MultilineSupported()) {
265 // SetDisplayRect() has a side effect for later calls of GetStringSize().
266 // Be careful to invoke |render_text_->SetDisplayRect(gfx::Rect())| to
267 // cancel this effect before the next time GetStringSize() is called.
268 // It would be beneficial not to cancel here, considering that some layout
269 // managers invoke GetHeightForWidth() for the same width multiple times
270 // and |render_text_| can cache the height.
271 render_text_->SetDisplayRect(gfx::Rect(0, 0, w, 0));
272 height = render_text_->GetStringSize().height();
273 } else {
274 std::vector<base::string16> lines = GetLinesForWidth(w);
275 height = lines.size() * std::max(line_height(), font_list().GetHeight());
277 height -= gfx::ShadowValue::GetMargin(render_text_->shadows()).height();
278 return height + GetInsets().height();
281 void Label::Layout() {
282 lines_.clear();
285 const char* Label::GetClassName() const {
286 return kViewClassName;
289 View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
290 if (!handles_tooltips_ ||
291 (tooltip_text_.empty() && !ShouldShowDefaultTooltip()))
292 return NULL;
294 return HitTestPoint(point) ? this : NULL;
297 bool Label::CanProcessEventsWithinSubtree() const {
298 // Send events to the parent view for handling.
299 return false;
302 void Label::GetAccessibleState(ui::AXViewState* state) {
303 state->role = ui::AX_ROLE_STATIC_TEXT;
304 state->AddStateFlag(ui::AX_STATE_READ_ONLY);
305 // Note that |render_text_| is never elided (see the comment in Init() too).
306 state->name = render_text_->GetDisplayText();
309 bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
310 if (!handles_tooltips_)
311 return false;
313 if (!tooltip_text_.empty()) {
314 tooltip->assign(tooltip_text_);
315 return true;
318 if (ShouldShowDefaultTooltip()) {
319 // Note that |render_text_| is never elided (see the comment in Init() too).
320 tooltip->assign(render_text_->GetDisplayText());
321 return true;
324 return false;
327 void Label::OnEnabledChanged() {
328 RecalculateColors();
331 scoped_ptr<gfx::RenderText> Label::CreateRenderText(
332 const base::string16& text,
333 gfx::HorizontalAlignment alignment,
334 gfx::DirectionalityMode directionality,
335 gfx::ElideBehavior elide_behavior) {
336 scoped_ptr<gfx::RenderText> render_text(
337 render_text_->CreateInstanceOfSameType());
338 render_text->SetHorizontalAlignment(alignment);
339 render_text->SetDirectionalityMode(directionality);
340 render_text->SetElideBehavior(elide_behavior);
341 render_text->SetObscured(obscured());
342 render_text->SetMinLineHeight(line_height());
343 render_text->SetFontList(font_list());
344 render_text->set_shadows(shadows());
345 render_text->SetCursorEnabled(false);
346 render_text->SetText(text);
347 return render_text.Pass();
350 void Label::PaintText(gfx::Canvas* canvas) {
351 MaybeBuildRenderTextLines();
352 for (size_t i = 0; i < lines_.size(); ++i)
353 lines_[i]->Draw(canvas);
356 void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
357 if (previous_bounds.size() != size())
358 InvalidateLayout();
361 void Label::OnPaint(gfx::Canvas* canvas) {
362 View::OnPaint(canvas);
363 if (is_first_paint_text_) {
364 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
365 tracked_objects::ScopedTracker tracking_profile(
366 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::PaintText first"));
368 is_first_paint_text_ = false;
369 PaintText(canvas);
370 } else {
371 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
372 tracked_objects::ScopedTracker tracking_profile(
373 FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::PaintText not first"));
375 PaintText(canvas);
377 if (HasFocus())
378 canvas->DrawFocusRect(GetFocusBounds());
381 void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) {
382 UpdateColorsFromTheme(theme);
385 void Label::OnDeviceScaleFactorChanged(float device_scale_factor) {
386 View::OnDeviceScaleFactorChanged(device_scale_factor);
387 // When the device scale factor is changed, some font rendering parameters is
388 // changed (especially, hinting). The bounding box of the text has to be
389 // re-computed based on the new parameters. See crbug.com/441439
390 ResetLayout();
393 void Label::VisibilityChanged(View* starting_from, bool is_visible) {
394 if (!is_visible)
395 lines_.clear();
398 void Label::Init(const base::string16& text, const gfx::FontList& font_list) {
399 render_text_.reset(gfx::RenderText::CreateInstance());
400 render_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
401 render_text_->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT);
402 // NOTE: |render_text_| should not be elided at all. This is used to keep some
403 // properties and to compute the size of the string.
404 render_text_->SetElideBehavior(gfx::NO_ELIDE);
405 render_text_->SetFontList(font_list);
406 render_text_->SetCursorEnabled(false);
407 render_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
409 elide_behavior_ = gfx::ELIDE_TAIL;
410 enabled_color_set_ = disabled_color_set_ = background_color_set_ = false;
411 subpixel_rendering_enabled_ = true;
412 auto_color_readability_ = true;
413 multi_line_ = false;
414 UpdateColorsFromTheme(ui::NativeTheme::instance());
415 handles_tooltips_ = true;
416 collapse_when_hidden_ = false;
417 max_width_ = 0;
418 is_first_paint_text_ = true;
419 SetText(text);
422 void Label::ResetLayout() {
423 InvalidateLayout();
424 PreferredSizeChanged();
425 SchedulePaint();
426 lines_.clear();
429 void Label::MaybeBuildRenderTextLines() {
430 if (!lines_.empty())
431 return;
433 gfx::Rect rect = GetContentsBounds();
434 if (focusable())
435 rect.Inset(kFocusBorderPadding, kFocusBorderPadding);
436 if (rect.IsEmpty())
437 return;
439 gfx::HorizontalAlignment alignment = horizontal_alignment();
440 gfx::DirectionalityMode directionality = render_text_->directionality_mode();
441 if (multi_line()) {
442 // Force the directionality and alignment of the first line on other lines.
443 bool rtl =
444 render_text_->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT;
445 if (alignment == gfx::ALIGN_TO_HEAD)
446 alignment = rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
447 directionality =
448 rtl ? gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR;
451 // Text eliding is not supported for multi-lined Labels.
452 // TODO(mukai): Add multi-lined elided text support.
453 gfx::ElideBehavior elide_behavior =
454 multi_line() ? gfx::NO_ELIDE : elide_behavior_;
455 if (!multi_line() || render_text_->MultilineSupported()) {
456 scoped_ptr<gfx::RenderText> render_text =
457 CreateRenderText(text(), alignment, directionality, elide_behavior);
458 render_text->SetDisplayRect(rect);
459 render_text->SetMultiline(multi_line());
460 render_text->SetWordWrapBehavior(render_text_->word_wrap_behavior());
461 lines_.push_back(render_text.release());
462 } else {
463 std::vector<base::string16> lines = GetLinesForWidth(rect.width());
464 if (lines.size() > 1)
465 rect.set_height(std::max(line_height(), font_list().GetHeight()));
467 const int bottom = GetContentsBounds().bottom();
468 for (size_t i = 0; i < lines.size() && rect.y() <= bottom; ++i) {
469 scoped_ptr<gfx::RenderText> line =
470 CreateRenderText(lines[i], alignment, directionality, elide_behavior);
471 line->SetDisplayRect(rect);
472 lines_.push_back(line.release());
473 rect.set_y(rect.y() + rect.height());
475 // Append the remaining text to the last visible line.
476 for (size_t i = lines_.size(); i < lines.size(); ++i)
477 lines_.back()->SetText(lines_.back()->text() + lines[i]);
479 RecalculateColors();
482 gfx::Rect Label::GetFocusBounds() {
483 MaybeBuildRenderTextLines();
485 gfx::Rect focus_bounds;
486 if (lines_.empty()) {
487 focus_bounds = gfx::Rect(GetTextSize());
488 } else {
489 for (size_t i = 0; i < lines_.size(); ++i) {
490 gfx::Point origin;
491 origin += lines_[i]->GetLineOffset(0);
492 focus_bounds.Union(gfx::Rect(origin, lines_[i]->GetStringSize()));
496 focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding);
497 focus_bounds.Intersect(GetLocalBounds());
498 return focus_bounds;
501 std::vector<base::string16> Label::GetLinesForWidth(int width) const {
502 std::vector<base::string16> lines;
503 // |width| can be 0 when getting the default text size, in that case
504 // the ideal lines (i.e. broken at newline characters) are wanted.
505 if (width <= 0) {
506 base::SplitString(render_text_->GetDisplayText(), '\n', &lines);
507 } else {
508 gfx::ElideRectangleText(render_text_->GetDisplayText(), font_list(), width,
509 std::numeric_limits<int>::max(),
510 render_text_->word_wrap_behavior(), &lines);
512 return lines;
515 gfx::Size Label::GetTextSize() const {
516 gfx::Size size;
517 if (text().empty()) {
518 size = gfx::Size(0, std::max(line_height(), font_list().GetHeight()));
519 } else if (!multi_line() || render_text_->MultilineSupported()) {
520 // Cancel the display rect of |render_text_|. The display rect may be
521 // specified in GetHeightForWidth(), and specifying empty Rect cancels
522 // its effect. See also the comment in GetHeightForWidth().
523 // TODO(mukai): use gfx::Rect() to compute the ideal size rather than
524 // the current width(). See crbug.com/468494, crbug.com/467526, and
525 // the comment for MultilinePreferredSizeTest in label_unittest.cc.
526 render_text_->SetDisplayRect(gfx::Rect(0, 0, width(), 0));
527 size = render_text_->GetStringSize();
528 } else {
529 // Get the natural text size, unelided and only wrapped on newlines.
530 std::vector<base::string16> lines = GetLinesForWidth(width());
531 scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
532 render_text->SetFontList(font_list());
533 for (size_t i = 0; i < lines.size(); ++i) {
534 render_text->SetText(lines[i]);
535 const gfx::Size line = render_text->GetStringSize();
536 size.set_width(std::max(size.width(), line.width()));
537 size.set_height(std::max(line_height(), size.height() + line.height()));
540 const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows());
541 size.Enlarge(shadow_margin.width(), shadow_margin.height());
542 return size;
545 void Label::RecalculateColors() {
546 actual_enabled_color_ = auto_color_readability_ ?
547 color_utils::GetReadableColor(requested_enabled_color_,
548 background_color_) :
549 requested_enabled_color_;
550 actual_disabled_color_ = auto_color_readability_ ?
551 color_utils::GetReadableColor(requested_disabled_color_,
552 background_color_) :
553 requested_disabled_color_;
555 SkColor color = enabled() ? actual_enabled_color_ : actual_disabled_color_;
556 bool subpixel_rendering_suppressed =
557 SkColorGetA(background_color_) != 0xFF || !subpixel_rendering_enabled_;
558 for (size_t i = 0; i < lines_.size(); ++i) {
559 lines_[i]->SetColor(color);
560 lines_[i]->set_subpixel_rendering_suppressed(subpixel_rendering_suppressed);
562 SchedulePaint();
565 void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
566 if (!enabled_color_set_) {
567 requested_enabled_color_ = theme->GetSystemColor(
568 ui::NativeTheme::kColorId_LabelEnabledColor);
570 if (!disabled_color_set_) {
571 requested_disabled_color_ = theme->GetSystemColor(
572 ui::NativeTheme::kColorId_LabelDisabledColor);
574 if (!background_color_set_) {
575 background_color_ = theme->GetSystemColor(
576 ui::NativeTheme::kColorId_LabelBackgroundColor);
578 RecalculateColors();
581 bool Label::ShouldShowDefaultTooltip() const {
582 const gfx::Size text_size = GetTextSize();
583 const gfx::Size size = GetContentsBounds().size();
584 return !obscured() && (text_size.width() > size.width() ||
585 (multi_line() && text_size.height() > size.height()));
588 } // namespace views