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.
4
5 #include "ui/views/controls/label.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <cmath>
11 #include <limits>
12 #include <utility>
13 #include <vector>
14
15 #include "base/i18n/rtl.h"
16 #include "base/logging.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "build/build_config.h"
20 #include "build/chromeos_buildflags.h"
21 #include "ui/accessibility/ax_enums.mojom.h"
22 #include "ui/accessibility/ax_node_data.h"
23 #include "ui/base/clipboard/scoped_clipboard_writer.h"
24 #include "ui/base/cursor/cursor.h"
25 #include "ui/base/default_style.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/gfx/color_utils.h"
28 #include "ui/gfx/geometry/insets.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/gfx/text_utils.h"
31 #include "ui/native_theme/native_theme.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/background.h"
34 #include "ui/views/controls/menu/menu_runner.h"
35 #include "ui/views/focus/focus_manager.h"
36 #include "ui/views/metadata/metadata_impl_macros.h"
37 #include "ui/views/native_cursor.h"
38 #include "ui/views/selection_controller.h"
39
40 namespace {
41
42 // An enum giving different RenderText properties unique keys for the
43 // OnPropertyChanged call.
44 enum LabelPropertyKey {
45 kLabelText = 1,
46 kLabelShadows,
47 kLabelHorizontalAlignment,
48 kLabelVerticalAlignment,
49 kLabelLineHeight,
50 kLabelObscured,
51 kLabelAllowCharacterBreak,
52 };
53
IsOpaque(SkColor color)54 bool IsOpaque(SkColor color) {
55 return SkColorGetA(color) == SK_AlphaOPAQUE;
56 }
57
58 } // namespace
59
60 namespace views {
61
Label()62 Label::Label() : Label(base::string16()) {}
63
Label(const base::string16 & text)64 Label::Label(const base::string16& text)
65 : Label(text, style::CONTEXT_LABEL, style::STYLE_PRIMARY) {}
66
Label(const base::string16 & text,int text_context,int text_style,gfx::DirectionalityMode directionality_mode)67 Label::Label(const base::string16& text,
68 int text_context,
69 int text_style,
70 gfx::DirectionalityMode directionality_mode)
71 : text_context_(text_context),
72 text_style_(text_style),
73 context_menu_contents_(this) {
74 Init(text, style::GetFont(text_context, text_style), directionality_mode);
75 }
76
Label(const base::string16 & text,const CustomFont & font)77 Label::Label(const base::string16& text, const CustomFont& font)
78 : text_context_(style::CONTEXT_LABEL),
79 text_style_(style::STYLE_PRIMARY),
80 context_menu_contents_(this) {
81 Init(text, font.font_list, gfx::DirectionalityMode::DIRECTIONALITY_FROM_TEXT);
82 }
83
84 Label::~Label() = default;
85
86 // static
GetDefaultFontList()87 const gfx::FontList& Label::GetDefaultFontList() {
88 return style::GetFont(style::CONTEXT_LABEL, style::STYLE_PRIMARY);
89 }
90
SetFontList(const gfx::FontList & font_list)91 void Label::SetFontList(const gfx::FontList& font_list) {
92 full_text_->SetFontList(font_list);
93 ClearDisplayText();
94 PreferredSizeChanged();
95 }
96
GetText() const97 const base::string16& Label::GetText() const {
98 return full_text_->text();
99 }
100
SetText(const base::string16 & new_text)101 void Label::SetText(const base::string16& new_text) {
102 if (new_text == GetText())
103 return;
104 full_text_->SetText(new_text);
105 ClearDisplayText();
106 OnPropertyChanged(&full_text_ + kLabelText,
107 kPropertyEffectsPreferredSizeChanged);
108 stored_selection_range_ = gfx::Range::InvalidRange();
109 }
110
GetTextContext() const111 int Label::GetTextContext() const {
112 return text_context_;
113 }
114
SetTextContext(int text_context)115 void Label::SetTextContext(int text_context) {
116 if (text_context == text_context_)
117 return;
118 text_context_ = text_context;
119 UpdateColorsFromTheme();
120 OnPropertyChanged(&text_context_, views::kPropertyEffectsPaint);
121 }
122
GetTextStyle() const123 int Label::GetTextStyle() const {
124 return text_style_;
125 }
126
SetTextStyle(int style)127 void Label::SetTextStyle(int style) {
128 if (style == text_style_)
129 return;
130
131 text_style_ = style;
132 // TODO(pkasting): Seems like potentially |full_text_|'s font list and line
133 // height should be updated here?
134 UpdateColorsFromTheme();
135 OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
136 }
137
GetAutoColorReadabilityEnabled() const138 bool Label::GetAutoColorReadabilityEnabled() const {
139 return auto_color_readability_enabled_;
140 }
141
SetAutoColorReadabilityEnabled(bool auto_color_readability_enabled)142 void Label::SetAutoColorReadabilityEnabled(
143 bool auto_color_readability_enabled) {
144 if (auto_color_readability_enabled_ == auto_color_readability_enabled)
145 return;
146 auto_color_readability_enabled_ = auto_color_readability_enabled;
147 RecalculateColors();
148 OnPropertyChanged(&auto_color_readability_enabled_, kPropertyEffectsPaint);
149 }
150
GetEnabledColor() const151 SkColor Label::GetEnabledColor() const {
152 return actual_enabled_color_;
153 }
154
SetEnabledColor(SkColor color)155 void Label::SetEnabledColor(SkColor color) {
156 if (enabled_color_set_ && requested_enabled_color_ == color)
157 return;
158 requested_enabled_color_ = color;
159 enabled_color_set_ = true;
160 RecalculateColors();
161 OnPropertyChanged(&requested_enabled_color_, kPropertyEffectsPaint);
162 }
163
GetBackgroundColor() const164 SkColor Label::GetBackgroundColor() const {
165 return background_color_;
166 }
167
SetBackgroundColor(SkColor color)168 void Label::SetBackgroundColor(SkColor color) {
169 if (background_color_set_ && background_color_ == color)
170 return;
171 background_color_ = color;
172 background_color_set_ = true;
173 RecalculateColors();
174 OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
175 }
176
GetSelectionTextColor() const177 SkColor Label::GetSelectionTextColor() const {
178 return actual_selection_text_color_;
179 }
180
SetSelectionTextColor(SkColor color)181 void Label::SetSelectionTextColor(SkColor color) {
182 if (selection_text_color_set_ && requested_selection_text_color_ == color)
183 return;
184 requested_selection_text_color_ = color;
185 selection_text_color_set_ = true;
186 RecalculateColors();
187 OnPropertyChanged(&requested_selection_text_color_, kPropertyEffectsPaint);
188 }
189
GetSelectionBackgroundColor() const190 SkColor Label::GetSelectionBackgroundColor() const {
191 return selection_background_color_;
192 }
193
SetSelectionBackgroundColor(SkColor color)194 void Label::SetSelectionBackgroundColor(SkColor color) {
195 if (selection_background_color_set_ && selection_background_color_ == color)
196 return;
197 selection_background_color_ = color;
198 selection_background_color_set_ = true;
199 RecalculateColors();
200 OnPropertyChanged(&selection_background_color_, kPropertyEffectsPaint);
201 }
202
GetShadows() const203 const gfx::ShadowValues& Label::GetShadows() const {
204 return full_text_->shadows();
205 }
206
SetShadows(const gfx::ShadowValues & shadows)207 void Label::SetShadows(const gfx::ShadowValues& shadows) {
208 if (full_text_->shadows() == shadows)
209 return;
210 full_text_->set_shadows(shadows);
211 ClearDisplayText();
212 OnPropertyChanged(&full_text_ + kLabelShadows,
213 kPropertyEffectsPreferredSizeChanged);
214 }
215
GetSubpixelRenderingEnabled() const216 bool Label::GetSubpixelRenderingEnabled() const {
217 return subpixel_rendering_enabled_;
218 }
219
SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled)220 void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
221 if (subpixel_rendering_enabled_ == subpixel_rendering_enabled)
222 return;
223 subpixel_rendering_enabled_ = subpixel_rendering_enabled;
224 ApplyTextColors();
225 OnPropertyChanged(&subpixel_rendering_enabled_, kPropertyEffectsPaint);
226 }
227
GetSkipSubpixelRenderingOpacityCheck() const228 bool Label::GetSkipSubpixelRenderingOpacityCheck() const {
229 return skip_subpixel_rendering_opacity_check_;
230 }
231
SetSkipSubpixelRenderingOpacityCheck(bool skip_subpixel_rendering_opacity_check)232 void Label::SetSkipSubpixelRenderingOpacityCheck(
233 bool skip_subpixel_rendering_opacity_check) {
234 if (skip_subpixel_rendering_opacity_check_ ==
235 skip_subpixel_rendering_opacity_check)
236 return;
237 skip_subpixel_rendering_opacity_check_ =
238 skip_subpixel_rendering_opacity_check;
239 OnPropertyChanged(&skip_subpixel_rendering_opacity_check_,
240 kPropertyEffectsNone);
241 }
242
GetHorizontalAlignment() const243 gfx::HorizontalAlignment Label::GetHorizontalAlignment() const {
244 return full_text_->horizontal_alignment();
245 }
246
SetHorizontalAlignment(gfx::HorizontalAlignment alignment)247 void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
248 alignment = gfx::MaybeFlipForRTL(alignment);
249 if (GetHorizontalAlignment() == alignment)
250 return;
251 full_text_->SetHorizontalAlignment(alignment);
252 ClearDisplayText();
253 OnPropertyChanged(&full_text_ + kLabelHorizontalAlignment,
254 kPropertyEffectsPaint);
255 }
256
GetVerticalAlignment() const257 gfx::VerticalAlignment Label::GetVerticalAlignment() const {
258 return full_text_->vertical_alignment();
259 }
260
SetVerticalAlignment(gfx::VerticalAlignment alignment)261 void Label::SetVerticalAlignment(gfx::VerticalAlignment alignment) {
262 if (GetVerticalAlignment() == alignment)
263 return;
264 full_text_->SetVerticalAlignment(alignment);
265 ClearDisplayText();
266 OnPropertyChanged(&full_text_ + kLabelVerticalAlignment,
267 kPropertyEffectsPaint);
268 }
269
GetLineHeight() const270 int Label::GetLineHeight() const {
271 // TODO(pkasting): If we can replace SetFontList() with context/style setter
272 // calls, we can eliminate the reference to font_list().GetHeight() here.
273 return line_height_.value_or(
274 std::max(style::GetLineHeight(text_context_, text_style_),
275 font_list().GetHeight()));
276 }
277
SetLineHeight(int line_height)278 void Label::SetLineHeight(int line_height) {
279 if (line_height_ == line_height)
280 return;
281 line_height_ = line_height;
282 full_text_->SetMinLineHeight(line_height);
283 ClearDisplayText();
284 OnPropertyChanged(&full_text_ + kLabelLineHeight,
285 kPropertyEffectsPreferredSizeChanged);
286 }
287
GetMultiLine() const288 bool Label::GetMultiLine() const {
289 return multi_line_;
290 }
291
SetMultiLine(bool multi_line)292 void Label::SetMultiLine(bool multi_line) {
293 DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL ||
294 elide_behavior_ == gfx::NO_ELIDE));
295 if (this->GetMultiLine() == multi_line)
296 return;
297 multi_line_ = multi_line;
298 full_text_->SetMultiline(multi_line);
299 ClearDisplayText();
300 OnPropertyChanged(&multi_line_, kPropertyEffectsPreferredSizeChanged);
301 }
302
GetMaxLines() const303 int Label::GetMaxLines() const {
304 return max_lines_;
305 }
306
SetMaxLines(int max_lines)307 void Label::SetMaxLines(int max_lines) {
308 if (max_lines_ == max_lines)
309 return;
310 max_lines_ = max_lines;
311 OnPropertyChanged(&max_lines_, kPropertyEffectsPreferredSizeChanged);
312 }
313
GetObscured() const314 bool Label::GetObscured() const {
315 return full_text_->obscured();
316 }
317
SetObscured(bool obscured)318 void Label::SetObscured(bool obscured) {
319 if (this->GetObscured() == obscured)
320 return;
321 full_text_->SetObscured(obscured);
322 ClearDisplayText();
323 if (obscured)
324 SetSelectable(false);
325 OnPropertyChanged(&full_text_ + kLabelObscured,
326 kPropertyEffectsPreferredSizeChanged);
327 }
328
IsDisplayTextTruncated() const329 bool Label::IsDisplayTextTruncated() const {
330 MaybeBuildDisplayText();
331 if (!full_text_ || full_text_->text().empty())
332 return false;
333 auto text_bounds = GetTextBounds();
334 return (display_text_ &&
335 display_text_->text() != display_text_->GetDisplayText()) ||
336 text_bounds.width() > GetContentsBounds().width() ||
337 text_bounds.height() > GetContentsBounds().height();
338 }
339
GetAllowCharacterBreak() const340 bool Label::GetAllowCharacterBreak() const {
341 return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS;
342 }
343
SetAllowCharacterBreak(bool allow_character_break)344 void Label::SetAllowCharacterBreak(bool allow_character_break) {
345 const gfx::WordWrapBehavior behavior =
346 allow_character_break ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS;
347 if (full_text_->word_wrap_behavior() == behavior)
348 return;
349 full_text_->SetWordWrapBehavior(behavior);
350 ClearDisplayText();
351 OnPropertyChanged(&full_text_ + kLabelAllowCharacterBreak,
352 kPropertyEffectsPreferredSizeChanged);
353 }
354
GetTextIndexOfLine(size_t line) const355 size_t Label::GetTextIndexOfLine(size_t line) const {
356 return full_text_->GetTextIndexOfLine(line);
357 }
358
SetTruncateLength(size_t truncate_length)359 void Label::SetTruncateLength(size_t truncate_length) {
360 return full_text_->set_truncate_length(truncate_length);
361 }
362
GetElideBehavior() const363 gfx::ElideBehavior Label::GetElideBehavior() const {
364 return elide_behavior_;
365 }
366
SetElideBehavior(gfx::ElideBehavior elide_behavior)367 void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
368 DCHECK(!GetMultiLine() || (elide_behavior == gfx::ELIDE_TAIL ||
369 elide_behavior == gfx::NO_ELIDE));
370 if (elide_behavior_ == elide_behavior)
371 return;
372 elide_behavior_ = elide_behavior;
373 ClearDisplayText();
374 OnPropertyChanged(&elide_behavior_, kPropertyEffectsPreferredSizeChanged);
375 }
376
GetTooltipText() const377 base::string16 Label::GetTooltipText() const {
378 return tooltip_text_;
379 }
380
SetTooltipText(const base::string16 & tooltip_text)381 void Label::SetTooltipText(const base::string16& tooltip_text) {
382 DCHECK(handles_tooltips_);
383 if (tooltip_text_ == tooltip_text)
384 return;
385 tooltip_text_ = tooltip_text;
386 OnPropertyChanged(&tooltip_text_, kPropertyEffectsNone);
387 }
388
GetHandlesTooltips() const389 bool Label::GetHandlesTooltips() const {
390 return handles_tooltips_;
391 }
392
SetHandlesTooltips(bool enabled)393 void Label::SetHandlesTooltips(bool enabled) {
394 if (handles_tooltips_ == enabled)
395 return;
396 handles_tooltips_ = enabled;
397 OnPropertyChanged(&handles_tooltips_, kPropertyEffectsNone);
398 }
399
SizeToFit(int fixed_width)400 void Label::SizeToFit(int fixed_width) {
401 DCHECK(GetMultiLine());
402 DCHECK_EQ(0, max_width_);
403 fixed_width_ = fixed_width;
404 SizeToPreferredSize();
405 }
406
GetMaximumWidth() const407 int Label::GetMaximumWidth() const {
408 return max_width_;
409 }
410
SetMaximumWidth(int max_width)411 void Label::SetMaximumWidth(int max_width) {
412 DCHECK(GetMultiLine());
413 DCHECK_EQ(0, fixed_width_);
414 if (max_width_ == max_width)
415 return;
416 max_width_ = max_width;
417 OnPropertyChanged(&max_width_, kPropertyEffectsPreferredSizeChanged);
418 }
419
GetCollapseWhenHidden() const420 bool Label::GetCollapseWhenHidden() const {
421 return collapse_when_hidden_;
422 }
423
SetCollapseWhenHidden(bool value)424 void Label::SetCollapseWhenHidden(bool value) {
425 if (collapse_when_hidden_ == value)
426 return;
427 collapse_when_hidden_ = value;
428 OnPropertyChanged(&collapse_when_hidden_,
429 kPropertyEffectsPreferredSizeChanged);
430 }
431
GetRequiredLines() const432 size_t Label::GetRequiredLines() const {
433 return full_text_->GetNumLines();
434 }
435
GetDisplayTextForTesting()436 base::string16 Label::GetDisplayTextForTesting() {
437 MaybeBuildDisplayText();
438 return display_text_ ? display_text_->GetDisplayText() : base::string16();
439 }
440
GetTextDirectionForTesting()441 base::i18n::TextDirection Label::GetTextDirectionForTesting() {
442 return full_text_->GetDisplayTextDirection();
443 }
444
IsSelectionSupported() const445 bool Label::IsSelectionSupported() const {
446 return !GetObscured();
447 }
448
GetSelectable() const449 bool Label::GetSelectable() const {
450 return !!selection_controller_;
451 }
452
SetSelectable(bool value)453 bool Label::SetSelectable(bool value) {
454 if (value == GetSelectable())
455 return true;
456
457 if (!value) {
458 ClearSelection();
459 stored_selection_range_ = gfx::Range::InvalidRange();
460 selection_controller_.reset();
461 return true;
462 }
463
464 DCHECK(!stored_selection_range_.IsValid());
465 if (!IsSelectionSupported())
466 return false;
467
468 selection_controller_ = std::make_unique<SelectionController>(this);
469 return true;
470 }
471
HasSelection() const472 bool Label::HasSelection() const {
473 const gfx::RenderText* render_text = GetRenderTextForSelectionController();
474 return render_text ? !render_text->selection().is_empty() : false;
475 }
476
SelectAll()477 void Label::SelectAll() {
478 gfx::RenderText* render_text = GetRenderTextForSelectionController();
479 if (!render_text)
480 return;
481 render_text->SelectAll(false);
482 SchedulePaint();
483 }
484
ClearSelection()485 void Label::ClearSelection() {
486 gfx::RenderText* render_text = GetRenderTextForSelectionController();
487 if (!render_text)
488 return;
489 render_text->ClearSelection();
490 SchedulePaint();
491 }
492
SelectRange(const gfx::Range & range)493 void Label::SelectRange(const gfx::Range& range) {
494 gfx::RenderText* render_text = GetRenderTextForSelectionController();
495 if (render_text && render_text->SelectRange(range))
496 SchedulePaint();
497 }
498
GetSubstringBounds(const gfx::Range & range)499 std::vector<gfx::Rect> Label::GetSubstringBounds(const gfx::Range& range) {
500 auto substring_bounds = full_text_->GetSubstringBounds(range);
501 for (auto& bound : substring_bounds) {
502 bound.Offset(GetInsets().left(), GetInsets().top());
503 }
504 return substring_bounds;
505 }
506
AddTextChangedCallback(views::PropertyChangedCallback callback)507 views::PropertyChangedSubscription Label::AddTextChangedCallback(
508 views::PropertyChangedCallback callback) {
509 return AddPropertyChangedCallback(&full_text_ + kLabelText,
510 std::move(callback));
511 }
512
GetBaseline() const513 int Label::GetBaseline() const {
514 return GetInsets().top() + font_list().GetBaseline();
515 }
516
CalculatePreferredSize() const517 gfx::Size Label::CalculatePreferredSize() const {
518 // Return a size of (0, 0) if the label is not visible and if the
519 // |collapse_when_hidden_| flag is set.
520 // TODO(munjal): This logic probably belongs to the View class. But for now,
521 // put it here since putting it in View class means all inheriting classes
522 // need to respect the |collapse_when_hidden_| flag.
523 if (!GetVisible() && collapse_when_hidden_)
524 return gfx::Size();
525
526 if (GetMultiLine() && fixed_width_ != 0 && !GetText().empty())
527 return gfx::Size(fixed_width_, GetHeightForWidth(fixed_width_));
528
529 gfx::Size size(GetTextSize());
530 const gfx::Insets insets = GetInsets();
531 size.Enlarge(insets.width(), insets.height());
532
533 if (GetMultiLine() && max_width_ != 0 && max_width_ < size.width())
534 return gfx::Size(max_width_, GetHeightForWidth(max_width_));
535
536 if (GetMultiLine() && GetMaxLines() > 0)
537 return gfx::Size(size.width(), GetHeightForWidth(size.width()));
538 return size;
539 }
540
GetMinimumSize() const541 gfx::Size Label::GetMinimumSize() const {
542 if (!GetVisible() && collapse_when_hidden_)
543 return gfx::Size();
544
545 // Always reserve vertical space for at least one line.
546 gfx::Size size(0, GetLineHeight());
547 if (elide_behavior_ == gfx::ELIDE_HEAD ||
548 elide_behavior_ == gfx::ELIDE_MIDDLE ||
549 elide_behavior_ == gfx::ELIDE_TAIL ||
550 elide_behavior_ == gfx::ELIDE_EMAIL) {
551 size.set_width(gfx::Canvas::GetStringWidth(
552 base::string16(gfx::kEllipsisUTF16), font_list()));
553 }
554
555 if (!GetMultiLine()) {
556 if (elide_behavior_ == gfx::NO_ELIDE) {
557 // If elision is disabled on single-line Labels, use text size as minimum.
558 // This is OK because clients can use |gfx::ElideBehavior::TRUNCATE|
559 // to get a non-eliding Label that should size itself less aggressively.
560 size.SetToMax(GetTextSize());
561 } else {
562 size.SetToMin(GetTextSize());
563 }
564 }
565
566 size.Enlarge(GetInsets().width(), GetInsets().height());
567 return size;
568 }
569
GetHeightForWidth(int w) const570 int Label::GetHeightForWidth(int w) const {
571 if (!GetVisible() && collapse_when_hidden_)
572 return 0;
573
574 w -= GetInsets().width();
575 int height = 0;
576 int base_line_height = GetLineHeight();
577 if (!GetMultiLine() || GetText().empty() || w < 0) {
578 height = base_line_height;
579 } else if (w == 0) {
580 height = std::max(GetMaxLines(), 1) * base_line_height;
581 } else {
582 // SetDisplayRect() has a side effect for later calls of GetStringSize().
583 // Be careful to invoke |full_text_->SetDisplayRect(gfx::Rect())| to
584 // cancel this effect before the next time GetStringSize() is called.
585 // It would be beneficial not to cancel here, considering that some layout
586 // managers invoke GetHeightForWidth() for the same width multiple times
587 // and |full_text_| can cache the height.
588 full_text_->SetDisplayRect(gfx::Rect(0, 0, w, 0));
589 int string_height = full_text_->GetStringSize().height();
590 // Cap the number of lines to |GetMaxLines()| if multi-line and non-zero
591 // |GetMaxLines()|.
592 height = GetMultiLine() && GetMaxLines() > 0
593 ? std::min(GetMaxLines() * base_line_height, string_height)
594 : string_height;
595 }
596 height -= gfx::ShadowValue::GetMargin(full_text_->shadows()).height();
597 return height + GetInsets().height();
598 }
599
GetTooltipHandlerForPoint(const gfx::Point & point)600 View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
601 if (!handles_tooltips_ ||
602 (tooltip_text_.empty() && !ShouldShowDefaultTooltip()))
603 return nullptr;
604
605 return HitTestPoint(point) ? this : nullptr;
606 }
607
GetCanProcessEventsWithinSubtree() const608 bool Label::GetCanProcessEventsWithinSubtree() const {
609 return !!GetRenderTextForSelectionController();
610 }
611
GetWordLookupClient()612 WordLookupClient* Label::GetWordLookupClient() {
613 return this;
614 }
615
GetAccessibleNodeData(ui::AXNodeData * node_data)616 void Label::GetAccessibleNodeData(ui::AXNodeData* node_data) {
617 if (text_context_ == style::CONTEXT_DIALOG_TITLE)
618 node_data->role = ax::mojom::Role::kTitleBar;
619 else
620 node_data->role = ax::mojom::Role::kStaticText;
621
622 node_data->SetName(full_text_->GetDisplayText());
623 }
624
GetTooltipText(const gfx::Point & p) const625 base::string16 Label::GetTooltipText(const gfx::Point& p) const {
626 if (handles_tooltips_) {
627 if (!tooltip_text_.empty())
628 return tooltip_text_;
629
630 if (ShouldShowDefaultTooltip())
631 return full_text_->GetDisplayText();
632 }
633
634 return base::string16();
635 }
636
CreateRenderText() const637 std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
638 // Multi-line labels only support NO_ELIDE and ELIDE_TAIL for now.
639 // TODO(warx): Investigate more elide text support.
640 gfx::ElideBehavior elide_behavior =
641 GetMultiLine() && (elide_behavior_ != gfx::NO_ELIDE) ? gfx::ELIDE_TAIL
642 : elide_behavior_;
643
644 std::unique_ptr<gfx::RenderText> render_text =
645 gfx::RenderText::CreateRenderText();
646 render_text->SetHorizontalAlignment(GetHorizontalAlignment());
647 render_text->SetVerticalAlignment(GetVerticalAlignment());
648 render_text->SetDirectionalityMode(full_text_->directionality_mode());
649 render_text->SetElideBehavior(elide_behavior);
650 render_text->SetObscured(GetObscured());
651 render_text->SetMinLineHeight(GetLineHeight());
652 render_text->SetFontList(font_list());
653 render_text->set_shadows(GetShadows());
654 render_text->SetCursorEnabled(false);
655 render_text->SetText(GetText());
656 const bool multiline = GetMultiLine();
657 render_text->SetMultiline(multiline);
658 render_text->SetMaxLines(multiline ? GetMaxLines() : 0);
659 render_text->SetWordWrapBehavior(full_text_->word_wrap_behavior());
660
661 // Setup render text for selection controller.
662 if (GetSelectable()) {
663 render_text->set_focused(HasFocus());
664 if (stored_selection_range_.IsValid())
665 render_text->SelectRange(stored_selection_range_);
666 }
667
668 return render_text;
669 }
670
GetTextBounds() const671 gfx::Rect Label::GetTextBounds() const {
672 MaybeBuildDisplayText();
673
674 if (!display_text_)
675 return gfx::Rect(GetTextSize());
676
677 return gfx::Rect(gfx::Point() + display_text_->GetLineOffset(0),
678 display_text_->GetStringSize());
679 }
680
PaintText(gfx::Canvas * canvas)681 void Label::PaintText(gfx::Canvas* canvas) {
682 MaybeBuildDisplayText();
683
684 if (display_text_)
685 display_text_->Draw(canvas);
686
687 #if DCHECK_IS_ON() && !defined(OS_CHROMEOS) && !BUILDFLAG(IS_LACROS)
688 // TODO(crbug.com/1139395): Enable this DCHECK on ChromeOS and LaCrOS by
689 // fixing either this check (to correctly idenfify more paints-on-opaque
690 // cases), refactoring parents to use background() or by fixing
691 // subpixel-rendering issues that the DCHECK detects.
692 if (!display_text_ || display_text_->subpixel_rendering_suppressed() ||
693 skip_subpixel_rendering_opacity_check_)
694 return;
695
696 // Ensure that, if we're using subpixel rendering, we're painted to an opaque
697 // region. Subpixel rendering will sample from the r,g,b color channels of the
698 // canvas. These values are incorrect when sampling from transparent pixels.
699 // Note that these checks may need to be amended for other methods of painting
700 // opaquely underneath the Label. For now, individual cases can skip this
701 // DCHECK by calling Label::SetSkipSubpixelRenderingOpacityCheck().
702 for (View* view = this; view; view = view->parent()) {
703 // This is our approximation of being painted on an opaque region. If any
704 // parent has an opaque background we assume that that background covers the
705 // text bounds. This is not necessarily true as the background could be
706 // inset from the parent bounds, and get_color() does not imply that all of
707 // the background is painted with the same opaque color.
708 if (view->background() && IsOpaque(view->background()->get_color()))
709 break;
710
711 if (view->layer()) {
712 // If we aren't painted to an opaque background, we must paint to an
713 // opaque layer.
714 DCHECK(view->layer()->fills_bounds_opaquely());
715 break;
716 }
717 }
718 #endif
719 }
720
OnBoundsChanged(const gfx::Rect & previous_bounds)721 void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
722 ClearDisplayText();
723 }
724
OnPaint(gfx::Canvas * canvas)725 void Label::OnPaint(gfx::Canvas* canvas) {
726 View::OnPaint(canvas);
727 PaintText(canvas);
728 }
729
OnThemeChanged()730 void Label::OnThemeChanged() {
731 View::OnThemeChanged();
732 UpdateColorsFromTheme();
733 }
734
GetCursor(const ui::MouseEvent & event)735 gfx::NativeCursor Label::GetCursor(const ui::MouseEvent& event) {
736 return GetRenderTextForSelectionController() ? GetNativeIBeamCursor()
737 : gfx::kNullCursor;
738 }
739
OnFocus()740 void Label::OnFocus() {
741 gfx::RenderText* render_text = GetRenderTextForSelectionController();
742 if (render_text) {
743 render_text->set_focused(true);
744 SchedulePaint();
745 }
746 View::OnFocus();
747 }
748
OnBlur()749 void Label::OnBlur() {
750 gfx::RenderText* render_text = GetRenderTextForSelectionController();
751 if (render_text) {
752 render_text->set_focused(false);
753 SchedulePaint();
754 }
755 View::OnBlur();
756 }
757
OnMousePressed(const ui::MouseEvent & event)758 bool Label::OnMousePressed(const ui::MouseEvent& event) {
759 if (!GetRenderTextForSelectionController())
760 return false;
761
762 const bool had_focus = HasFocus();
763
764 // RequestFocus() won't work when the label has FocusBehavior::NEVER. Hence
765 // explicitly set the focused view.
766 // TODO(karandeepb): If a widget with a label having FocusBehavior::NEVER as
767 // the currently focused view (due to selection) was to lose focus, focus
768 // won't be restored to the label (and hence a text selection won't be drawn)
769 // when the widget gets focus again. Fix this.
770 // Tracked in https://crbug.com/630365.
771 if ((event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) &&
772 GetFocusManager() && !had_focus) {
773 GetFocusManager()->SetFocusedView(this);
774 }
775
776 #if (defined(OS_LINUX) || defined(OS_BSD)) && !defined(OS_CHROMEOS)
777 if (event.IsOnlyMiddleMouseButton() && GetFocusManager() && !had_focus)
778 GetFocusManager()->SetFocusedView(this);
779 #endif
780
781 return selection_controller_->OnMousePressed(
782 event, false,
783 had_focus
784 ? SelectionController::InitialFocusStateOnMousePress::kFocused
785 : SelectionController::InitialFocusStateOnMousePress::kUnFocused);
786 }
787
OnMouseDragged(const ui::MouseEvent & event)788 bool Label::OnMouseDragged(const ui::MouseEvent& event) {
789 if (!GetRenderTextForSelectionController())
790 return false;
791
792 return selection_controller_->OnMouseDragged(event);
793 }
794
OnMouseReleased(const ui::MouseEvent & event)795 void Label::OnMouseReleased(const ui::MouseEvent& event) {
796 if (!GetRenderTextForSelectionController())
797 return;
798
799 selection_controller_->OnMouseReleased(event);
800 }
801
OnMouseCaptureLost()802 void Label::OnMouseCaptureLost() {
803 if (!GetRenderTextForSelectionController())
804 return;
805
806 selection_controller_->OnMouseCaptureLost();
807 }
808
OnKeyPressed(const ui::KeyEvent & event)809 bool Label::OnKeyPressed(const ui::KeyEvent& event) {
810 if (!GetRenderTextForSelectionController())
811 return false;
812
813 const bool shift = event.IsShiftDown();
814 const bool control = event.IsControlDown();
815 const bool alt = event.IsAltDown() || event.IsAltGrDown();
816
817 switch (event.key_code()) {
818 case ui::VKEY_C:
819 if (control && !alt && HasSelection()) {
820 CopyToClipboard();
821 return true;
822 }
823 break;
824 case ui::VKEY_INSERT:
825 if (control && !shift && HasSelection()) {
826 CopyToClipboard();
827 return true;
828 }
829 break;
830 case ui::VKEY_A:
831 if (control && !alt && !GetText().empty()) {
832 SelectAll();
833 DCHECK(HasSelection());
834 UpdateSelectionClipboard();
835 return true;
836 }
837 break;
838 default:
839 break;
840 }
841
842 return false;
843 }
844
AcceleratorPressed(const ui::Accelerator & accelerator)845 bool Label::AcceleratorPressed(const ui::Accelerator& accelerator) {
846 // Allow the "Copy" action from the Chrome menu to be invoked. E.g., if a user
847 // selects a Label on a web modal dialog. "Select All" doesn't appear in the
848 // Chrome menu so isn't handled here.
849 if (accelerator.key_code() == ui::VKEY_C && accelerator.IsCtrlDown()) {
850 CopyToClipboard();
851 return true;
852 }
853 return false;
854 }
855
CanHandleAccelerators() const856 bool Label::CanHandleAccelerators() const {
857 // Focus needs to be checked since the accelerator for the Copy command from
858 // the Chrome menu should only be handled when the current view has focus. See
859 // related comment in BrowserView::CutCopyPaste.
860 return HasFocus() && GetRenderTextForSelectionController() &&
861 View::CanHandleAccelerators();
862 }
863
OnDeviceScaleFactorChanged(float old_device_scale_factor,float new_device_scale_factor)864 void Label::OnDeviceScaleFactorChanged(float old_device_scale_factor,
865 float new_device_scale_factor) {
866 View::OnDeviceScaleFactorChanged(old_device_scale_factor,
867 new_device_scale_factor);
868 // When the device scale factor is changed, some font rendering parameters is
869 // changed (especially, hinting). The bounding box of the text has to be
870 // re-computed based on the new parameters. See crbug.com/441439
871 PreferredSizeChanged();
872 }
873
VisibilityChanged(View * starting_from,bool is_visible)874 void Label::VisibilityChanged(View* starting_from, bool is_visible) {
875 if (!is_visible)
876 ClearDisplayText();
877 }
878
ShowContextMenuForViewImpl(View * source,const gfx::Point & point,ui::MenuSourceType source_type)879 void Label::ShowContextMenuForViewImpl(View* source,
880 const gfx::Point& point,
881 ui::MenuSourceType source_type) {
882 if (!GetRenderTextForSelectionController())
883 return;
884
885 context_menu_runner_ = std::make_unique<MenuRunner>(
886 &context_menu_contents_,
887 MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU);
888 context_menu_runner_->RunMenuAt(GetWidget(), nullptr,
889 gfx::Rect(point, gfx::Size()),
890 MenuAnchorPosition::kTopLeft, source_type);
891 }
892
GetWordLookupDataAtPoint(const gfx::Point & point,gfx::DecoratedText * decorated_word,gfx::Point * baseline_point)893 bool Label::GetWordLookupDataAtPoint(const gfx::Point& point,
894 gfx::DecoratedText* decorated_word,
895 gfx::Point* baseline_point) {
896 gfx::RenderText* render_text = GetRenderTextForSelectionController();
897 return render_text ? render_text->GetWordLookupDataAtPoint(
898 point, decorated_word, baseline_point)
899 : false;
900 }
901
GetWordLookupDataFromSelection(gfx::DecoratedText * decorated_text,gfx::Point * baseline_point)902 bool Label::GetWordLookupDataFromSelection(gfx::DecoratedText* decorated_text,
903 gfx::Point* baseline_point) {
904 gfx::RenderText* render_text = GetRenderTextForSelectionController();
905 return render_text
906 ? render_text->GetLookupDataForRange(
907 render_text->selection(), decorated_text, baseline_point)
908 : false;
909 }
910
GetRenderTextForSelectionController()911 gfx::RenderText* Label::GetRenderTextForSelectionController() {
912 return const_cast<gfx::RenderText*>(
913 static_cast<const Label*>(this)->GetRenderTextForSelectionController());
914 }
915
IsReadOnly() const916 bool Label::IsReadOnly() const {
917 return true;
918 }
919
SupportsDrag() const920 bool Label::SupportsDrag() const {
921 // TODO(crbug.com/661379): Labels should support dragging selected text.
922 return false;
923 }
924
HasTextBeingDragged() const925 bool Label::HasTextBeingDragged() const {
926 return false;
927 }
928
SetTextBeingDragged(bool value)929 void Label::SetTextBeingDragged(bool value) {
930 NOTREACHED();
931 }
932
GetViewHeight() const933 int Label::GetViewHeight() const {
934 return height();
935 }
936
GetViewWidth() const937 int Label::GetViewWidth() const {
938 return width();
939 }
940
GetDragSelectionDelay() const941 int Label::GetDragSelectionDelay() const {
942 // Labels don't need to use a repeating timer to update the drag selection.
943 // Since the cursor is disabled for labels, a selection outside the display
944 // area won't change the text in the display area. It is expected that all the
945 // text will fit in the display area for labels anyway.
946 return 0;
947 }
948
OnBeforePointerAction()949 void Label::OnBeforePointerAction() {}
950
OnAfterPointerAction(bool text_changed,bool selection_changed)951 void Label::OnAfterPointerAction(bool text_changed, bool selection_changed) {
952 DCHECK(!text_changed);
953 if (selection_changed)
954 SchedulePaint();
955 }
956
PasteSelectionClipboard()957 bool Label::PasteSelectionClipboard() {
958 NOTREACHED();
959 return false;
960 }
961
UpdateSelectionClipboard()962 void Label::UpdateSelectionClipboard() {
963 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
964 if (!GetObscured()) {
965 ui::ScopedClipboardWriter(ui::ClipboardBuffer::kSelection)
966 .WriteText(GetSelectedText());
967 }
968 #endif
969 }
970
IsCommandIdChecked(int command_id) const971 bool Label::IsCommandIdChecked(int command_id) const {
972 return true;
973 }
974
IsCommandIdEnabled(int command_id) const975 bool Label::IsCommandIdEnabled(int command_id) const {
976 switch (command_id) {
977 case MenuCommands::kCopy:
978 return HasSelection() && !GetObscured();
979 case MenuCommands::kSelectAll:
980 return GetRenderTextForSelectionController() && !GetText().empty();
981 }
982 return false;
983 }
984
ExecuteCommand(int command_id,int event_flags)985 void Label::ExecuteCommand(int command_id, int event_flags) {
986 switch (command_id) {
987 case MenuCommands::kCopy:
988 CopyToClipboard();
989 break;
990 case MenuCommands::kSelectAll:
991 SelectAll();
992 DCHECK(HasSelection());
993 UpdateSelectionClipboard();
994 break;
995 default:
996 NOTREACHED();
997 }
998 }
999
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator) const1000 bool Label::GetAcceleratorForCommandId(int command_id,
1001 ui::Accelerator* accelerator) const {
1002 switch (command_id) {
1003 case MenuCommands::kCopy:
1004 *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN);
1005 return true;
1006
1007 case MenuCommands::kSelectAll:
1008 *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN);
1009 return true;
1010
1011 default:
1012 return false;
1013 }
1014 }
1015
GetRenderTextForSelectionController() const1016 const gfx::RenderText* Label::GetRenderTextForSelectionController() const {
1017 if (!GetSelectable())
1018 return nullptr;
1019 MaybeBuildDisplayText();
1020
1021 // This may be null when the content bounds of the view are empty.
1022 return display_text_.get();
1023 }
1024
Init(const base::string16 & text,const gfx::FontList & font_list,gfx::DirectionalityMode directionality_mode)1025 void Label::Init(const base::string16& text,
1026 const gfx::FontList& font_list,
1027 gfx::DirectionalityMode directionality_mode) {
1028 full_text_ = gfx::RenderText::CreateRenderText();
1029 full_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
1030 full_text_->SetFontList(font_list);
1031 full_text_->SetCursorEnabled(false);
1032 full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
1033 full_text_->SetMinLineHeight(GetLineHeight());
1034 // NOTE: |full_text_| should not be elided at all. This is used to keep
1035 // some properties and to compute the size of the string.
1036 full_text_->SetElideBehavior(gfx::NO_ELIDE);
1037 full_text_->SetDirectionalityMode(directionality_mode);
1038
1039 SetText(text);
1040
1041 // Only selectable labels will get requests to show the context menu, due to
1042 // GetCanProcessEventsWithinSubtree().
1043 BuildContextMenuContents();
1044 set_context_menu_controller(this);
1045
1046 // This allows the BrowserView to pass the copy command from the Chrome menu
1047 // to the Label.
1048 AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
1049 }
1050
MaybeBuildDisplayText() const1051 void Label::MaybeBuildDisplayText() const {
1052 if (display_text_)
1053 return;
1054
1055 gfx::Rect rect = GetContentsBounds();
1056 if (rect.IsEmpty())
1057 return;
1058
1059 rect.Inset(-gfx::ShadowValue::GetMargin(GetShadows()));
1060 display_text_ = CreateRenderText();
1061 display_text_->SetDisplayRect(rect);
1062 stored_selection_range_ = gfx::Range::InvalidRange();
1063 ApplyTextColors();
1064 }
1065
GetTextSize() const1066 gfx::Size Label::GetTextSize() const {
1067 gfx::Size size;
1068 if (GetText().empty()) {
1069 size = gfx::Size(0, GetLineHeight());
1070 } else {
1071 // Cancel the display rect of |full_text_|. The display rect may be
1072 // specified in GetHeightForWidth(), and specifying empty Rect cancels
1073 // its effect. See also the comment in GetHeightForWidth().
1074 // TODO(mukai): use gfx::Rect() to compute the ideal size rather than
1075 // the current width(). See crbug.com/468494, crbug.com/467526, and
1076 // the comment for MultilinePreferredSizeTest in label_unittest.cc.
1077 full_text_->SetDisplayRect(gfx::Rect(0, 0, width(), 0));
1078 size = full_text_->GetStringSize();
1079 }
1080 const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(GetShadows());
1081 size.Enlarge(shadow_margin.width(), shadow_margin.height());
1082 return size;
1083 }
1084
GetForegroundColor(SkColor foreground,SkColor background) const1085 SkColor Label::GetForegroundColor(SkColor foreground,
1086 SkColor background) const {
1087 return (auto_color_readability_enabled_ && IsOpaque(background))
1088 ? color_utils::BlendForMinContrast(foreground, background).color
1089 : foreground;
1090 }
1091
RecalculateColors()1092 void Label::RecalculateColors() {
1093 actual_enabled_color_ =
1094 GetForegroundColor(requested_enabled_color_, background_color_);
1095 // Using GetResultingPaintColor() here allows non-opaque selection backgrounds
1096 // to still participate in auto color readability, assuming
1097 // |background_color_| is itself opaque.
1098 actual_selection_text_color_ =
1099 GetForegroundColor(requested_selection_text_color_,
1100 color_utils::GetResultingPaintColor(
1101 selection_background_color_, background_color_));
1102
1103 ApplyTextColors();
1104 SchedulePaint();
1105 }
1106
ApplyTextColors() const1107 void Label::ApplyTextColors() const {
1108 if (!display_text_)
1109 return;
1110
1111 display_text_->SetColor(actual_enabled_color_);
1112 display_text_->set_selection_color(actual_selection_text_color_);
1113 display_text_->set_selection_background_focused_color(
1114 selection_background_color_);
1115 const bool subpixel_rendering_enabled =
1116 subpixel_rendering_enabled_ && IsOpaque(background_color_);
1117 display_text_->set_subpixel_rendering_suppressed(!subpixel_rendering_enabled);
1118 }
1119
UpdateColorsFromTheme()1120 void Label::UpdateColorsFromTheme() {
1121 ui::NativeTheme* theme = GetNativeTheme();
1122 if (!enabled_color_set_) {
1123 requested_enabled_color_ =
1124 style::GetColor(*this, text_context_, text_style_);
1125 }
1126 if (!background_color_set_) {
1127 background_color_ =
1128 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground);
1129 }
1130 if (!selection_text_color_set_) {
1131 requested_selection_text_color_ = theme->GetSystemColor(
1132 ui::NativeTheme::kColorId_LabelTextSelectionColor);
1133 }
1134 if (!selection_background_color_set_) {
1135 selection_background_color_ = theme->GetSystemColor(
1136 ui::NativeTheme::kColorId_LabelTextSelectionBackgroundFocused);
1137 }
1138 RecalculateColors();
1139 }
1140
ShouldShowDefaultTooltip() const1141 bool Label::ShouldShowDefaultTooltip() const {
1142 const gfx::Size text_size = GetTextSize();
1143 const gfx::Size size = GetContentsBounds().size();
1144 return !GetObscured() &&
1145 (text_size.width() > size.width() ||
1146 (GetMultiLine() && text_size.height() > size.height()));
1147 }
1148
ClearDisplayText()1149 void Label::ClearDisplayText() {
1150 // The HasSelection() call below will build |display_text_| in case it is
1151 // empty. Return early to avoid this.
1152 if (!display_text_)
1153 return;
1154
1155 // Persist the selection range if there is an active selection.
1156 if (HasSelection()) {
1157 stored_selection_range_ =
1158 GetRenderTextForSelectionController()->selection();
1159 }
1160 display_text_ = nullptr;
1161
1162 SchedulePaint();
1163 }
1164
GetSelectedText() const1165 base::string16 Label::GetSelectedText() const {
1166 const gfx::RenderText* render_text = GetRenderTextForSelectionController();
1167 return render_text ? render_text->GetTextFromRange(render_text->selection())
1168 : base::string16();
1169 }
1170
CopyToClipboard()1171 void Label::CopyToClipboard() {
1172 if (!HasSelection() || GetObscured())
1173 return;
1174 ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
1175 .WriteText(GetSelectedText());
1176 }
1177
BuildContextMenuContents()1178 void Label::BuildContextMenuContents() {
1179 context_menu_contents_.AddItemWithStringId(MenuCommands::kCopy, IDS_APP_COPY);
1180 context_menu_contents_.AddItemWithStringId(MenuCommands::kSelectAll,
1181 IDS_APP_SELECT_ALL);
1182 }
1183
1184 BEGIN_METADATA(Label, View)
1185 ADD_PROPERTY_METADATA(base::string16, Text)
1186 ADD_PROPERTY_METADATA(int, TextContext)
1187 ADD_PROPERTY_METADATA(int, TextStyle)
1188 ADD_PROPERTY_METADATA(bool, AutoColorReadabilityEnabled)
1189 ADD_PROPERTY_METADATA(SkColor, EnabledColor)
1190 ADD_PROPERTY_METADATA(gfx::ElideBehavior, ElideBehavior)
1191 ADD_PROPERTY_METADATA(SkColor, BackgroundColor)
1192 ADD_PROPERTY_METADATA(SkColor, SelectionTextColor)
1193 ADD_PROPERTY_METADATA(SkColor, SelectionBackgroundColor)
1194 ADD_PROPERTY_METADATA(bool, SubpixelRenderingEnabled)
1195 ADD_PROPERTY_METADATA(bool, SkipSubpixelRenderingOpacityCheck)
1196 ADD_PROPERTY_METADATA(gfx::ShadowValues, Shadows)
1197 ADD_PROPERTY_METADATA(gfx::HorizontalAlignment, HorizontalAlignment)
1198 ADD_PROPERTY_METADATA(gfx::VerticalAlignment, VerticalAlignment)
1199 ADD_PROPERTY_METADATA(int, LineHeight)
1200 ADD_PROPERTY_METADATA(bool, MultiLine)
1201 ADD_PROPERTY_METADATA(int, MaxLines)
1202 ADD_PROPERTY_METADATA(bool, Obscured)
1203 ADD_PROPERTY_METADATA(bool, AllowCharacterBreak)
1204 ADD_PROPERTY_METADATA(base::string16, TooltipText)
1205 ADD_PROPERTY_METADATA(bool, HandlesTooltips)
1206 ADD_PROPERTY_METADATA(bool, CollapseWhenHidden)
1207 ADD_PROPERTY_METADATA(int, MaximumWidth)
1208 END_METADATA
1209
1210 } // namespace views
1211