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"
12 #include "base/i18n/rtl.h"
13 #include "base/logging.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "ui/base/accessibility/accessible_view_state.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/color_utils.h"
21 #include "ui/gfx/insets.h"
22 #include "ui/gfx/shadow_value.h"
23 #include "ui/gfx/text_elider.h"
24 #include "ui/gfx/text_utils.h"
25 #include "ui/native_theme/native_theme.h"
26 #include "ui/views/background.h"
30 const int kCachedSizeLimit = 10;
37 const char Label::kViewClassName[] = "Label";
38 const int Label::kFocusBorderPadding = 1;
41 Init(base::string16(), gfx::FontList());
44 Label::Label(const base::string16& text) {
45 Init(text, gfx::FontList());
48 Label::Label(const base::string16& text, const gfx::FontList& font_list) {
49 Init(text, font_list);
55 void Label::SetFontList(const gfx::FontList& font_list) {
56 font_list_ = font_list;
58 PreferredSizeChanged();
62 void Label::SetText(const base::string16& text) {
67 PreferredSizeChanged();
71 void Label::SetAutoColorReadabilityEnabled(bool enabled) {
72 auto_color_readability_ = enabled;
76 void Label::SetEnabledColor(SkColor color) {
77 requested_enabled_color_ = color;
78 enabled_color_set_ = true;
82 void Label::SetDisabledColor(SkColor color) {
83 requested_disabled_color_ = color;
84 disabled_color_set_ = true;
88 void Label::SetBackgroundColor(SkColor color) {
89 background_color_ = color;
90 background_color_set_ = true;
94 void Label::SetShadowColors(SkColor enabled_color, SkColor disabled_color) {
95 enabled_shadow_color_ = enabled_color;
96 disabled_shadow_color_ = disabled_color;
100 void Label::SetShadowOffset(int x, int y) {
101 shadow_offset_.SetPoint(x, y);
104 void Label::ClearEmbellishing() {
108 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
109 // If the View's UI layout is right-to-left and directionality_mode_ is
110 // USE_UI_DIRECTIONALITY, we need to flip the alignment so that the alignment
111 // settings take into account the text directionality.
112 if (base::i18n::IsRTL() && (directionality_mode_ == USE_UI_DIRECTIONALITY) &&
113 (alignment != gfx::ALIGN_CENTER)) {
114 alignment = (alignment == gfx::ALIGN_LEFT) ?
115 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
117 if (horizontal_alignment_ != alignment) {
118 horizontal_alignment_ = alignment;
123 void Label::SetLineHeight(int height) {
124 if (height != line_height_) {
125 line_height_ = height;
127 PreferredSizeChanged();
132 void Label::SetMultiLine(bool multi_line) {
133 DCHECK(!multi_line || elide_behavior_ != ELIDE_IN_MIDDLE);
134 if (multi_line != is_multi_line_) {
135 is_multi_line_ = multi_line;
137 PreferredSizeChanged();
142 void Label::SetAllowCharacterBreak(bool allow_character_break) {
143 if (allow_character_break != allow_character_break_) {
144 allow_character_break_ = allow_character_break;
146 PreferredSizeChanged();
151 void Label::SetElideBehavior(ElideBehavior elide_behavior) {
152 DCHECK(elide_behavior != ELIDE_IN_MIDDLE || !is_multi_line_);
153 if (elide_behavior != elide_behavior_) {
154 elide_behavior_ = elide_behavior;
156 PreferredSizeChanged();
161 void Label::SetTooltipText(const base::string16& tooltip_text) {
162 tooltip_text_ = tooltip_text;
165 void Label::SizeToFit(int max_width) {
166 DCHECK(is_multi_line_);
168 std::vector<base::string16> lines;
169 base::SplitString(text_, '\n', &lines);
172 for (std::vector<base::string16>::const_iterator iter = lines.begin();
173 iter != lines.end(); ++iter) {
174 label_width = std::max(label_width, gfx::GetStringWidth(*iter, font_list_));
177 label_width += GetInsets().width();
180 label_width = std::min(label_width, max_width);
182 SetBounds(x(), y(), label_width, 0);
183 SizeToPreferredSize();
186 gfx::Insets Label::GetInsets() const {
187 gfx::Insets insets = View::GetInsets();
189 insets += gfx::Insets(kFocusBorderPadding, kFocusBorderPadding,
190 kFocusBorderPadding, kFocusBorderPadding);
195 int Label::GetBaseline() const {
196 return GetInsets().top() + font_list_.GetBaseline();
199 gfx::Size Label::GetPreferredSize() {
200 // Return a size of (0, 0) if the label is not visible and if the
201 // collapse_when_hidden_ flag is set.
202 // TODO(munjal): This logic probably belongs to the View class. But for now,
203 // put it here since putting it in View class means all inheriting classes
204 // need ot respect the collapse_when_hidden_ flag.
205 if (!visible() && collapse_when_hidden_)
208 gfx::Size size(GetTextSize());
209 gfx::Insets insets = GetInsets();
210 size.Enlarge(insets.width(), insets.height());
214 gfx::Size Label::GetMinimumSize() {
215 gfx::Size text_size(GetTextSize());
216 if ((!visible() && collapse_when_hidden_) || text_size.IsEmpty())
219 gfx::Size size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16),
221 font_list_.GetHeight());
222 size.SetToMin(text_size); // The actual text may be shorter than an ellipsis.
223 gfx::Insets insets = GetInsets();
224 size.Enlarge(insets.width(), insets.height());
228 int Label::GetHeightForWidth(int w) {
230 return View::GetHeightForWidth(w);
232 w = std::max(0, w - GetInsets().width());
234 for (size_t i = 0; i < cached_heights_.size(); ++i) {
235 const gfx::Size& s = cached_heights_[i];
237 return s.height() + GetInsets().height();
242 int h = font_list_.GetHeight();
243 const int flags = ComputeDrawStringFlags();
244 gfx::Canvas::SizeStringInt(text_, font_list_, &w, &h, line_height_, flags);
245 cached_heights_[cached_heights_cursor_] = gfx::Size(cache_width, h);
246 cached_heights_cursor_ = (cached_heights_cursor_ + 1) % kCachedSizeLimit;
247 return h + GetInsets().height();
250 const char* Label::GetClassName() const {
251 return kViewClassName;
254 View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
255 // Bail out if the label does not contain the point.
256 // Note that HitTestPoint() cannot be used here as it uses
257 // Label::HitTestRect() to determine if the point hits the label; and
258 // Label::HitTestRect() always fails. Instead, default HitTestRect()
259 // implementation should be used.
260 if (!View::HitTestRect(gfx::Rect(point, gfx::Size(1, 1))))
263 if (tooltip_text_.empty() && !ShouldShowDefaultTooltip())
269 bool Label::HitTestRect(const gfx::Rect& rect) const {
273 bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const {
276 // If a tooltip has been explicitly set, use it.
277 if (!tooltip_text_.empty()) {
278 tooltip->assign(tooltip_text_);
282 // Show the full text if the text does not fit.
283 if (ShouldShowDefaultTooltip()) {
291 void Label::GetAccessibleState(ui::AccessibleViewState* state) {
292 state->role = ui::AccessibilityTypes::ROLE_STATICTEXT;
293 state->state = ui::AccessibilityTypes::STATE_READONLY;
297 void Label::PaintText(gfx::Canvas* canvas,
298 const base::string16& text,
299 const gfx::Rect& text_bounds,
301 gfx::ShadowValues shadows;
303 shadows.push_back(gfx::ShadowValue(shadow_offset_, 0,
304 enabled() ? enabled_shadow_color_ : disabled_shadow_color_));
305 canvas->DrawStringRectWithShadows(text, font_list_,
306 enabled() ? actual_enabled_color_ : actual_disabled_color_,
307 text_bounds, line_height_, flags, shadows);
310 gfx::Rect focus_bounds = text_bounds;
311 focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding);
312 canvas->DrawFocusRect(focus_bounds);
316 gfx::Size Label::GetTextSize() const {
317 if (!text_size_valid_) {
318 // For single-line strings, we supply the largest possible width, because
319 // while adding NO_ELLIPSIS to the flags works on Windows for forcing
320 // SizeStringInt() to calculate the desired width, it doesn't seem to work
322 int w = is_multi_line_ ?
323 GetAvailableRect().width() : std::numeric_limits<int>::max();
324 int h = font_list_.GetHeight();
325 // For single-line strings, ignore the available width and calculate how
326 // wide the text wants to be.
327 int flags = ComputeDrawStringFlags();
329 flags |= gfx::Canvas::NO_ELLIPSIS;
330 gfx::Canvas::SizeStringInt(text_, font_list_, &w, &h, line_height_, flags);
331 text_size_.SetSize(w, h);
332 text_size_valid_ = true;
338 void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
339 text_size_valid_ &= !is_multi_line_;
342 void Label::OnPaint(gfx::Canvas* canvas) {
343 OnPaintBackground(canvas);
344 // We skip painting the focus border because it is being handled seperately by
345 // some subclasses of Label. We do not want View's focus border painting to
346 // interfere with that.
347 OnPaintBorder(canvas);
349 base::string16 paint_text;
350 gfx::Rect text_bounds;
352 CalculateDrawStringParams(&paint_text, &text_bounds, &flags);
353 PaintText(canvas, paint_text, text_bounds, flags);
356 void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) {
357 UpdateColorsFromTheme(theme);
360 void Label::Init(const base::string16& text, const gfx::FontList& font_list) {
361 font_list_ = font_list;
362 enabled_color_set_ = disabled_color_set_ = background_color_set_ = false;
363 auto_color_readability_ = true;
364 UpdateColorsFromTheme(ui::NativeTheme::instance());
365 horizontal_alignment_ = gfx::ALIGN_CENTER;
367 is_multi_line_ = false;
368 allow_character_break_ = false;
369 elide_behavior_ = ELIDE_AT_END;
370 collapse_when_hidden_ = false;
371 directionality_mode_ = USE_UI_DIRECTIONALITY;
372 enabled_shadow_color_ = 0;
373 disabled_shadow_color_ = 0;
374 shadow_offset_.SetPoint(1, 1);
376 cached_heights_.resize(kCachedSizeLimit);
382 void Label::RecalculateColors() {
383 actual_enabled_color_ = auto_color_readability_ ?
384 color_utils::GetReadableColor(requested_enabled_color_,
386 requested_enabled_color_;
387 actual_disabled_color_ = auto_color_readability_ ?
388 color_utils::GetReadableColor(requested_disabled_color_,
390 requested_disabled_color_;
393 gfx::Rect Label::GetTextBounds() const {
394 gfx::Rect available_rect(GetAvailableRect());
395 gfx::Size text_size(GetTextSize());
396 text_size.set_width(std::min(available_rect.width(), text_size.width()));
398 gfx::Insets insets = GetInsets();
399 gfx::Point text_origin(insets.left(), insets.top());
400 switch (horizontal_alignment_) {
401 case gfx::ALIGN_LEFT:
403 case gfx::ALIGN_CENTER:
404 // We put any extra margin pixel on the left rather than the right. We
405 // used to do this because measurement on Windows used
406 // GetTextExtentPoint32(), which could report a value one too large on the
407 // right; we now use DrawText(), and who knows if it can also do this.
408 text_origin.Offset((available_rect.width() + 1 - text_size.width()) / 2,
411 case gfx::ALIGN_RIGHT:
412 text_origin.set_x(available_rect.right() - text_size.width());
418 text_origin.Offset(0,
419 std::max(0, (available_rect.height() - text_size.height())) / 2);
420 return gfx::Rect(text_origin, text_size);
423 int Label::ComputeDrawStringFlags() const {
426 // We can't use subpixel rendering if the background is non-opaque.
427 if (SkColorGetA(background_color_) != 0xFF)
428 flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
430 if (directionality_mode_ == AUTO_DETECT_DIRECTIONALITY) {
431 base::i18n::TextDirection direction =
432 base::i18n::GetFirstStrongCharacterDirection(text_);
433 if (direction == base::i18n::RIGHT_TO_LEFT)
434 flags |= gfx::Canvas::FORCE_RTL_DIRECTIONALITY;
436 flags |= gfx::Canvas::FORCE_LTR_DIRECTIONALITY;
439 switch (horizontal_alignment_) {
440 case gfx::ALIGN_LEFT:
441 flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
443 case gfx::ALIGN_CENTER:
444 flags |= gfx::Canvas::TEXT_ALIGN_CENTER;
446 case gfx::ALIGN_RIGHT:
447 flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
454 flags |= gfx::Canvas::MULTI_LINE;
456 // Don't elide multiline labels on Linux.
457 // Todo(davemoore): Do we depend on eliding multiline text?
458 // Pango insists on limiting the number of lines to one if text is
459 // elided. You can get around this if you can pass a maximum height
460 // but we don't currently have that data when we call the pango code.
461 flags |= gfx::Canvas::NO_ELLIPSIS;
463 if (allow_character_break_)
464 flags |= gfx::Canvas::CHARACTER_BREAK;
469 gfx::Rect Label::GetAvailableRect() const {
470 gfx::Rect bounds(size());
471 bounds.Inset(GetInsets());
475 void Label::CalculateDrawStringParams(base::string16* paint_text,
476 gfx::Rect* text_bounds,
478 DCHECK(paint_text && text_bounds && flags);
480 // TODO(msw): Use ElideRectangleText to support eliding multi-line text. Once
481 // this is done, we can set NO_ELLIPSIS unconditionally at the bottom.
482 if (is_multi_line_ || (elide_behavior_ == NO_ELIDE)) {
484 } else if (elide_behavior_ == ELIDE_IN_MIDDLE) {
485 *paint_text = gfx::ElideText(text_, font_list_, GetAvailableRect().width(),
486 gfx::ELIDE_IN_MIDDLE);
487 } else if (elide_behavior_ == ELIDE_AT_END) {
488 *paint_text = gfx::ElideText(text_, font_list_, GetAvailableRect().width(),
491 DCHECK_EQ(ELIDE_AS_EMAIL, elide_behavior_);
492 *paint_text = gfx::ElideEmail(text_, font_list_,
493 GetAvailableRect().width());
496 *text_bounds = GetTextBounds();
497 *flags = ComputeDrawStringFlags();
498 if (!is_multi_line_ || (elide_behavior_ == NO_ELIDE))
499 *flags |= gfx::Canvas::NO_ELLIPSIS;
502 void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) {
503 if (!enabled_color_set_) {
504 requested_enabled_color_ = theme->GetSystemColor(
505 ui::NativeTheme::kColorId_LabelEnabledColor);
507 if (!disabled_color_set_) {
508 requested_disabled_color_ = theme->GetSystemColor(
509 ui::NativeTheme::kColorId_LabelDisabledColor);
511 if (!background_color_set_) {
512 background_color_ = theme->GetSystemColor(
513 ui::NativeTheme::kColorId_LabelBackgroundColor);
518 void Label::ResetCachedSize() {
519 text_size_valid_ = false;
520 cached_heights_cursor_ = 0;
521 for (int i = 0; i < kCachedSizeLimit; ++i)
522 cached_heights_[i] = gfx::Size();
525 bool Label::ShouldShowDefaultTooltip() const {
526 return !is_multi_line_ &&
527 gfx::GetStringWidth(text_, font_list_) > GetAvailableRect().width();