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