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