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/accessibility/ax_view_state.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/text_elider.h"
22 #include "ui/gfx/text_utils.h"
23 #include "ui/gfx/utf16_indexing.h"
24 #include "ui/native_theme/native_theme.h"
25 #include "ui/views/background.h"
29 const int kCachedSizeLimit
= 10;
30 const base::char16 kPasswordReplacementChar
= '*';
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
) {
64 SetTextInternal(text
);
67 void Label::SetTextInternal(const base::string16
& text
) {
71 size_t obscured_text_length
=
72 static_cast<size_t>(gfx::UTF16IndexToOffset(text_
, 0, text_
.length()));
73 layout_text_
.assign(obscured_text_length
, kPasswordReplacementChar
);
79 PreferredSizeChanged();
83 void Label::SetAutoColorReadabilityEnabled(bool enabled
) {
84 auto_color_readability_
= enabled
;
88 void Label::SetEnabledColor(SkColor color
) {
89 requested_enabled_color_
= color
;
90 enabled_color_set_
= true;
94 void Label::SetDisabledColor(SkColor color
) {
95 requested_disabled_color_
= color
;
96 disabled_color_set_
= true;
100 void Label::SetBackgroundColor(SkColor color
) {
101 background_color_
= color
;
102 background_color_set_
= true;
106 void Label::SetShadows(const gfx::ShadowValues
& shadows
) {
108 text_size_valid_
= false;
111 void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled
) {
112 subpixel_rendering_enabled_
= subpixel_rendering_enabled
;
115 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment
) {
116 // If the UI layout is right-to-left, flip the alignment direction.
117 if (base::i18n::IsRTL() &&
118 (alignment
== gfx::ALIGN_LEFT
|| alignment
== gfx::ALIGN_RIGHT
)) {
119 alignment
= (alignment
== gfx::ALIGN_LEFT
) ?
120 gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
;
122 if (horizontal_alignment_
!= alignment
) {
123 horizontal_alignment_
= alignment
;
128 gfx::HorizontalAlignment
Label::GetHorizontalAlignment() const {
129 if (horizontal_alignment_
!= gfx::ALIGN_TO_HEAD
)
130 return horizontal_alignment_
;
132 const base::i18n::TextDirection dir
=
133 base::i18n::GetFirstStrongCharacterDirection(layout_text_
);
134 return dir
== base::i18n::RIGHT_TO_LEFT
? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
;
137 void Label::SetLineHeight(int height
) {
138 if (height
!= line_height_
) {
139 line_height_
= height
;
141 PreferredSizeChanged();
146 void Label::SetMultiLine(bool multi_line
) {
147 DCHECK(!multi_line
|| (elide_behavior_
== gfx::ELIDE_TAIL
||
148 elide_behavior_
== gfx::NO_ELIDE
));
149 if (multi_line
!= multi_line_
) {
150 multi_line_
= multi_line
;
152 PreferredSizeChanged();
157 void Label::SetObscured(bool obscured
) {
158 if (obscured
!= obscured_
) {
159 obscured_
= obscured
;
160 SetTextInternal(text_
);
164 void Label::SetAllowCharacterBreak(bool allow_character_break
) {
165 if (allow_character_break
!= allow_character_break_
) {
166 allow_character_break_
= allow_character_break
;
168 PreferredSizeChanged();
173 void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior
) {
174 DCHECK(!multi_line_
|| (elide_behavior_
== gfx::ELIDE_TAIL
||
175 elide_behavior_
== gfx::NO_ELIDE
));
176 if (elide_behavior
!= elide_behavior_
) {
177 elide_behavior_
= elide_behavior
;
179 PreferredSizeChanged();
184 void Label::SetTooltipText(const base::string16
& tooltip_text
) {
185 DCHECK(handles_tooltips_
);
186 tooltip_text_
= tooltip_text
;
189 void Label::SetHandlesTooltips(bool enabled
) {
190 handles_tooltips_
= enabled
;
193 void Label::SizeToFit(int max_width
) {
196 std::vector
<base::string16
> lines
;
197 base::SplitString(layout_text_
, '\n', &lines
);
200 for (std::vector
<base::string16
>::const_iterator iter
= lines
.begin();
201 iter
!= lines
.end(); ++iter
) {
202 label_width
= std::max(label_width
, gfx::GetStringWidth(*iter
, font_list_
));
205 label_width
+= GetInsets().width();
208 label_width
= std::min(label_width
, max_width
);
210 SetBounds(x(), y(), label_width
, 0);
211 SizeToPreferredSize();
214 const base::string16
& Label::GetLayoutTextForTesting() const {
218 gfx::Insets
Label::GetInsets() const {
219 gfx::Insets insets
= View::GetInsets();
221 insets
+= gfx::Insets(kFocusBorderPadding
, kFocusBorderPadding
,
222 kFocusBorderPadding
, kFocusBorderPadding
);
227 int Label::GetBaseline() const {
228 return GetInsets().top() + font_list_
.GetBaseline();
231 gfx::Size
Label::GetPreferredSize() const {
232 // Return a size of (0, 0) if the label is not visible and if the
233 // collapse_when_hidden_ flag is set.
234 // TODO(munjal): This logic probably belongs to the View class. But for now,
235 // put it here since putting it in View class means all inheriting classes
236 // need ot respect the collapse_when_hidden_ flag.
237 if (!visible() && collapse_when_hidden_
)
240 gfx::Size
size(GetTextSize());
241 gfx::Insets insets
= GetInsets();
242 size
.Enlarge(insets
.width(), insets
.height());
246 gfx::Size
Label::GetMinimumSize() const {
247 gfx::Size
text_size(GetTextSize());
248 if ((!visible() && collapse_when_hidden_
) || text_size
.IsEmpty())
251 gfx::Size
size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16
),
253 font_list_
.GetHeight());
254 size
.SetToMin(text_size
); // The actual text may be shorter than an ellipsis.
255 gfx::Insets insets
= GetInsets();
256 size
.Enlarge(insets
.width(), insets
.height());
260 int Label::GetHeightForWidth(int w
) const {
262 return View::GetHeightForWidth(w
);
264 w
= std::max(0, w
- GetInsets().width());
266 for (size_t i
= 0; i
< cached_heights_
.size(); ++i
) {
267 const gfx::Size
& s
= cached_heights_
[i
];
269 return s
.height() + GetInsets().height();
274 int h
= font_list_
.GetHeight();
275 const int flags
= ComputeDrawStringFlags();
276 gfx::Canvas::SizeStringInt(
277 layout_text_
, font_list_
, &w
, &h
, line_height_
, flags
);
278 cached_heights_
[cached_heights_cursor_
] = gfx::Size(cache_width
, h
);
279 cached_heights_cursor_
= (cached_heights_cursor_
+ 1) % kCachedSizeLimit
;
280 return h
+ GetInsets().height();
283 const char* Label::GetClassName() const {
284 return kViewClassName
;
287 View
* Label::GetTooltipHandlerForPoint(const gfx::Point
& point
) {
288 if (!handles_tooltips_
||
289 (tooltip_text_
.empty() && !ShouldShowDefaultTooltip()))
292 return HitTestPoint(point
) ? this : NULL
;
295 bool Label::CanProcessEventsWithinSubtree() const {
296 // Send events to the parent view for handling.
300 void Label::GetAccessibleState(ui::AXViewState
* state
) {
301 state
->role
= ui::AX_ROLE_STATIC_TEXT
;
302 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
303 state
->name
= layout_text_
;
306 bool Label::GetTooltipText(const gfx::Point
& p
, base::string16
* tooltip
) const {
307 if (!handles_tooltips_
)
310 if (!tooltip_text_
.empty()) {
311 tooltip
->assign(tooltip_text_
);
315 if (ShouldShowDefaultTooltip()) {
316 *tooltip
= layout_text_
;
323 void Label::PaintText(gfx::Canvas
* canvas
,
324 const base::string16
& text
,
325 const gfx::Rect
& text_bounds
,
327 SkColor color
= enabled() ? actual_enabled_color_
: actual_disabled_color_
;
328 if (elide_behavior_
== gfx::FADE_TAIL
) {
329 canvas
->DrawFadedString(text
, font_list_
, color
, text_bounds
, flags
);
331 canvas
->DrawStringRectWithShadows(text
, font_list_
, color
, text_bounds
,
332 line_height_
, flags
, shadows_
);
336 gfx::Rect focus_bounds
= text_bounds
;
337 focus_bounds
.Inset(-kFocusBorderPadding
, -kFocusBorderPadding
);
338 canvas
->DrawFocusRect(focus_bounds
);
342 gfx::Size
Label::GetTextSize() const {
343 if (!text_size_valid_
) {
344 // For single-line strings, we supply the largest possible width, because
345 // while adding NO_ELLIPSIS to the flags works on Windows for forcing
346 // SizeStringInt() to calculate the desired width, it doesn't seem to work
348 int w
= multi_line_
?
349 GetAvailableRect().width() : std::numeric_limits
<int>::max();
350 int h
= font_list_
.GetHeight();
351 // For single-line strings, ignore the available width and calculate how
352 // wide the text wants to be.
353 int flags
= ComputeDrawStringFlags();
355 flags
|= gfx::Canvas::NO_ELLIPSIS
;
356 gfx::Canvas::SizeStringInt(
357 layout_text_
, font_list_
, &w
, &h
, line_height_
, flags
);
358 text_size_
.SetSize(w
, h
);
359 const gfx::Insets shadow_margin
= -gfx::ShadowValue::GetMargin(shadows_
);
360 text_size_
.Enlarge(shadow_margin
.width(), shadow_margin
.height());
361 text_size_valid_
= true;
367 void Label::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
368 text_size_valid_
&= !multi_line_
;
371 void Label::OnPaint(gfx::Canvas
* canvas
) {
372 OnPaintBackground(canvas
);
373 // We skip painting the focus border because it is being handled seperately by
374 // some subclasses of Label. We do not want View's focus border painting to
375 // interfere with that.
376 OnPaintBorder(canvas
);
378 base::string16 paint_text
;
379 gfx::Rect text_bounds
;
381 CalculateDrawStringParams(&paint_text
, &text_bounds
, &flags
);
382 PaintText(canvas
, paint_text
, text_bounds
, flags
);
385 void Label::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
386 UpdateColorsFromTheme(theme
);
389 void Label::Init(const base::string16
& text
, const gfx::FontList
& font_list
) {
390 font_list_
= font_list
;
391 enabled_color_set_
= disabled_color_set_
= background_color_set_
= false;
392 subpixel_rendering_enabled_
= true;
393 auto_color_readability_
= true;
394 UpdateColorsFromTheme(ui::NativeTheme::instance());
395 horizontal_alignment_
= gfx::ALIGN_CENTER
;
399 allow_character_break_
= false;
400 elide_behavior_
= gfx::ELIDE_TAIL
;
401 handles_tooltips_
= true;
402 collapse_when_hidden_
= false;
403 cached_heights_
.resize(kCachedSizeLimit
);
409 void Label::RecalculateColors() {
410 actual_enabled_color_
= auto_color_readability_
?
411 color_utils::GetReadableColor(requested_enabled_color_
,
413 requested_enabled_color_
;
414 actual_disabled_color_
= auto_color_readability_
?
415 color_utils::GetReadableColor(requested_disabled_color_
,
417 requested_disabled_color_
;
420 gfx::Rect
Label::GetTextBounds() const {
421 gfx::Rect
available(GetAvailableRect());
422 gfx::Size
text_size(GetTextSize());
423 text_size
.set_width(std::min(available
.width(), text_size
.width()));
424 gfx::Point
origin(GetInsets().left(), GetInsets().top());
425 switch (GetHorizontalAlignment()) {
426 case gfx::ALIGN_LEFT
:
428 case gfx::ALIGN_CENTER
:
429 // Put any extra margin pixel on the left to match the legacy behavior
430 // from the use of GetTextExtentPoint32() on Windows.
431 origin
.Offset((available
.width() + 1 - text_size
.width()) / 2, 0);
433 case gfx::ALIGN_RIGHT
:
434 origin
.set_x(available
.right() - text_size
.width());
440 text_size
.set_height(available
.height());
441 return gfx::Rect(origin
, text_size
);
444 int Label::ComputeDrawStringFlags() const {
447 // We can't use subpixel rendering if the background is non-opaque.
448 if (SkColorGetA(background_color_
) != 0xFF || !subpixel_rendering_enabled_
)
449 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
451 base::i18n::TextDirection direction
=
452 base::i18n::GetFirstStrongCharacterDirection(layout_text_
);
453 if (direction
== base::i18n::RIGHT_TO_LEFT
)
454 flags
|= gfx::Canvas::FORCE_RTL_DIRECTIONALITY
;
456 flags
|= gfx::Canvas::FORCE_LTR_DIRECTIONALITY
;
458 switch (GetHorizontalAlignment()) {
459 case gfx::ALIGN_LEFT
:
460 flags
|= gfx::Canvas::TEXT_ALIGN_LEFT
;
462 case gfx::ALIGN_CENTER
:
463 flags
|= gfx::Canvas::TEXT_ALIGN_CENTER
;
465 case gfx::ALIGN_RIGHT
:
466 flags
|= gfx::Canvas::TEXT_ALIGN_RIGHT
;
476 flags
|= gfx::Canvas::MULTI_LINE
;
478 // Don't elide multiline labels on Linux.
479 // Todo(davemoore): Do we depend on eliding multiline text?
480 // Pango insists on limiting the number of lines to one if text is
481 // elided. You can get around this if you can pass a maximum height
482 // but we don't currently have that data when we call the pango code.
483 flags
|= gfx::Canvas::NO_ELLIPSIS
;
485 if (allow_character_break_
)
486 flags
|= gfx::Canvas::CHARACTER_BREAK
;
491 gfx::Rect
Label::GetAvailableRect() const {
492 gfx::Rect
bounds(size());
493 bounds
.Inset(GetInsets());
497 void Label::CalculateDrawStringParams(base::string16
* paint_text
,
498 gfx::Rect
* text_bounds
,
500 DCHECK(paint_text
&& text_bounds
&& flags
);
502 const bool forbid_ellipsis
= elide_behavior_
== gfx::NO_ELIDE
||
503 elide_behavior_
== gfx::FADE_TAIL
;
504 if (multi_line_
|| forbid_ellipsis
) {
505 *paint_text
= layout_text_
;
507 *paint_text
= gfx::ElideText(layout_text_
, font_list_
,
508 GetAvailableRect().width(), elide_behavior_
);
511 *text_bounds
= GetTextBounds();
512 *flags
= ComputeDrawStringFlags();
513 // TODO(msw): Elide multi-line text with ElideRectangleText instead.
514 if (!multi_line_
|| forbid_ellipsis
)
515 *flags
|= gfx::Canvas::NO_ELLIPSIS
;
518 void Label::UpdateColorsFromTheme(const ui::NativeTheme
* theme
) {
519 if (!enabled_color_set_
) {
520 requested_enabled_color_
= theme
->GetSystemColor(
521 ui::NativeTheme::kColorId_LabelEnabledColor
);
523 if (!disabled_color_set_
) {
524 requested_disabled_color_
= theme
->GetSystemColor(
525 ui::NativeTheme::kColorId_LabelDisabledColor
);
527 if (!background_color_set_
) {
528 background_color_
= theme
->GetSystemColor(
529 ui::NativeTheme::kColorId_LabelBackgroundColor
);
534 void Label::ResetCachedSize() {
535 text_size_valid_
= false;
536 cached_heights_cursor_
= 0;
537 for (int i
= 0; i
< kCachedSizeLimit
; ++i
)
538 cached_heights_
[i
] = gfx::Size();
541 bool Label::ShouldShowDefaultTooltip() const {
542 const gfx::Size text_size
= GetTextSize();
543 const gfx::Size size
= GetContentsBounds().size();
544 return !obscured() && (text_size
.width() > size
.width() ||
545 (multi_line_
&& text_size
.height() > size
.height()));