// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/views/controls/label.h" #include #include #include #include #include #include #include "base/i18n/rtl.h" #include "base/logging.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/cursor/cursor.h" #include "ui/base/default_style.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/text_elider.h" #include "ui/gfx/text_utils.h" #include "ui/native_theme/native_theme.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/background.h" #include "ui/views/controls/menu/menu_runner.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/native_cursor.h" #include "ui/views/selection_controller.h" namespace views { namespace { // Returns additional Insets applied to |label->GetContentsBounds()| to obtain // the text bounds. GetContentsBounds() includes the Border, but not any // additional insets used by the Label (e.g. for a focus ring). gfx::Insets NonBorderInsets(const Label& label) { return label.GetInsets() - label.View::GetInsets(); } } // namespace const char Label::kViewClassName[] = "Label"; Label::Label() : Label(base::string16()) { } Label::Label(const base::string16& text) : Label(text, style::CONTEXT_LABEL, style::STYLE_PRIMARY) {} Label::Label(const base::string16& text, int text_context, int text_style) : text_context_(text_context), context_menu_contents_(this) { Init(text, style::GetFont(text_context, text_style)); SetLineHeight(style::GetLineHeight(text_context, text_style)); // If an explicit style is given, ignore color changes due to the NativeTheme. if (text_style != style::STYLE_PRIMARY) SetEnabledColor(style::GetColor(*this, text_context, text_style)); } Label::Label(const base::string16& text, const CustomFont& font) : text_context_(style::CONTEXT_LABEL), context_menu_contents_(this) { Init(text, font.font_list); } Label::~Label() { } // static const gfx::FontList& Label::GetDefaultFontList() { return style::GetFont(style::CONTEXT_LABEL, style::STYLE_PRIMARY); } void Label::SetFontList(const gfx::FontList& font_list) { full_text_->SetFontList(font_list); ResetLayout(); } void Label::SetText(const base::string16& new_text) { if (new_text == text()) return; full_text_->SetText(new_text); ResetLayout(); stored_selection_range_ = gfx::Range::InvalidRange(); } void Label::SetAutoColorReadabilityEnabled(bool enabled) { if (auto_color_readability_ == enabled) return; auto_color_readability_ = enabled; RecalculateColors(); } void Label::SetEnabledColor(SkColor color) { if (enabled_color_set_ && requested_enabled_color_ == color) return; requested_enabled_color_ = color; enabled_color_set_ = true; RecalculateColors(); } void Label::SetBackgroundColor(SkColor color) { if (background_color_set_ && background_color_ == color) return; background_color_ = color; background_color_set_ = true; RecalculateColors(); } void Label::SetSelectionTextColor(SkColor color) { if (selection_text_color_set_ && requested_selection_text_color_ == color) return; requested_selection_text_color_ = color; selection_text_color_set_ = true; RecalculateColors(); } void Label::SetSelectionBackgroundColor(SkColor color) { if (selection_background_color_set_ && selection_background_color_ == color) return; selection_background_color_ = color; selection_background_color_set_ = true; RecalculateColors(); } void Label::SetShadows(const gfx::ShadowValues& shadows) { if (full_text_->shadows() == shadows) return; full_text_->set_shadows(shadows); ResetLayout(); } void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) { if (subpixel_rendering_enabled_ == subpixel_rendering_enabled) return; subpixel_rendering_enabled_ = subpixel_rendering_enabled; RecalculateColors(); } void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { alignment = gfx::MaybeFlipForRTL(alignment); if (horizontal_alignment() == alignment) return; full_text_->SetHorizontalAlignment(alignment); ResetLayout(); } void Label::SetLineHeight(int height) { if (line_height() == height) return; full_text_->SetMinLineHeight(height); ResetLayout(); } void Label::SetMultiLine(bool multi_line) { DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL || elide_behavior_ == gfx::NO_ELIDE)); if (this->multi_line() == multi_line) return; multi_line_ = multi_line; full_text_->SetMultiline(multi_line); full_text_->SetReplaceNewlineCharsWithSymbols(!multi_line); ResetLayout(); } void Label::SetMaxLines(int max_lines) { if (max_lines_ == max_lines) return; max_lines_ = max_lines; ResetLayout(); } void Label::SetObscured(bool obscured) { if (this->obscured() == obscured) return; full_text_->SetObscured(obscured); if (obscured) SetSelectable(false); ResetLayout(); } void Label::SetAllowCharacterBreak(bool allow_character_break) { const gfx::WordWrapBehavior behavior = allow_character_break ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS; if (full_text_->word_wrap_behavior() == behavior) return; full_text_->SetWordWrapBehavior(behavior); if (multi_line()) { ResetLayout(); } } void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) { DCHECK(!multi_line() || (elide_behavior_ == gfx::ELIDE_TAIL || elide_behavior_ == gfx::NO_ELIDE)); if (elide_behavior_ == elide_behavior) return; elide_behavior_ = elide_behavior; ResetLayout(); } void Label::SetTooltipText(const base::string16& tooltip_text) { DCHECK(handles_tooltips_); tooltip_text_ = tooltip_text; } void Label::SetHandlesTooltips(bool enabled) { handles_tooltips_ = enabled; } void Label::SizeToFit(int fixed_width) { DCHECK(multi_line()); DCHECK_EQ(0, max_width_); fixed_width_ = fixed_width; SizeToPreferredSize(); } void Label::SetMaximumWidth(int max_width) { DCHECK(multi_line()); DCHECK_EQ(0, fixed_width_); max_width_ = max_width; SizeToPreferredSize(); } base::string16 Label::GetDisplayTextForTesting() { ClearDisplayText(); MaybeBuildDisplayText(); return display_text_ ? display_text_->GetDisplayText() : base::string16(); } bool Label::IsSelectionSupported() const { return !obscured() && full_text_->IsSelectionSupported(); } bool Label::SetSelectable(bool value) { if (value == selectable()) return true; if (!value) { ClearSelection(); stored_selection_range_ = gfx::Range::InvalidRange(); selection_controller_.reset(); return true; } DCHECK(!stored_selection_range_.IsValid()); if (!IsSelectionSupported()) return false; selection_controller_ = std::make_unique(this); return true; } bool Label::HasSelection() const { const gfx::RenderText* render_text = GetRenderTextForSelectionController(); return render_text ? !render_text->selection().is_empty() : false; } void Label::SelectAll() { gfx::RenderText* render_text = GetRenderTextForSelectionController(); if (!render_text) return; render_text->SelectAll(false); SchedulePaint(); } void Label::ClearSelection() { gfx::RenderText* render_text = GetRenderTextForSelectionController(); if (!render_text) return; render_text->ClearSelection(); SchedulePaint(); } void Label::SelectRange(const gfx::Range& range) { gfx::RenderText* render_text = GetRenderTextForSelectionController(); if (render_text && render_text->SelectRange(range)) SchedulePaint(); } int Label::GetBaseline() const { return GetInsets().top() + font_list().GetBaseline(); } gfx::Size Label::CalculatePreferredSize() const { // Return a size of (0, 0) if the label is not visible and if the // |collapse_when_hidden_| flag is set. // TODO(munjal): This logic probably belongs to the View class. But for now, // put it here since putting it in View class means all inheriting classes // need to respect the |collapse_when_hidden_| flag. if (!visible() && collapse_when_hidden_) return gfx::Size(); if (multi_line() && fixed_width_ != 0 && !text().empty()) return gfx::Size(fixed_width_, GetHeightForWidth(fixed_width_)); gfx::Size size(GetTextSize()); const gfx::Insets insets = GetInsets(); size.Enlarge(insets.width(), insets.height()); if (multi_line() && max_width_ != 0 && max_width_ < size.width()) return gfx::Size(max_width_, GetHeightForWidth(max_width_)); if (multi_line() && max_lines() > 0) return gfx::Size(size.width(), GetHeightForWidth(size.width())); return size; } gfx::Size Label::GetMinimumSize() const { if (!visible() && collapse_when_hidden_) return gfx::Size(); gfx::Size size(0, font_list().GetHeight()); if (elide_behavior_ == gfx::ELIDE_HEAD || elide_behavior_ == gfx::ELIDE_MIDDLE || elide_behavior_ == gfx::ELIDE_TAIL || elide_behavior_ == gfx::ELIDE_EMAIL) { size.set_width(gfx::Canvas::GetStringWidth( base::string16(gfx::kEllipsisUTF16), font_list())); } if (!multi_line()) { if (elide_behavior_ == gfx::NO_ELIDE) { // If elision is disabled on single-line Labels, use text size as minimum. // This is OK because clients can use |gfx::ElideBehavior::TRUNCATE| // to get a non-eliding Label that should size itself less aggressively. size.SetToMax(GetTextSize()); } else { size.SetToMin(GetTextSize()); } } size.Enlarge(GetInsets().width(), GetInsets().height()); return size; } int Label::GetHeightForWidth(int w) const { if (!visible() && collapse_when_hidden_) return 0; w -= GetInsets().width(); int height = 0; int base_line_height = std::max(line_height(), font_list().GetHeight()); if (!multi_line() || text().empty() || w <= 0) { height = base_line_height; } else { // SetDisplayRect() has a side effect for later calls of GetStringSize(). // Be careful to invoke |full_text_->SetDisplayRect(gfx::Rect())| to // cancel this effect before the next time GetStringSize() is called. // It would be beneficial not to cancel here, considering that some layout // managers invoke GetHeightForWidth() for the same width multiple times // and |full_text_| can cache the height. full_text_->SetDisplayRect(gfx::Rect(0, 0, w, 0)); int string_height = full_text_->GetStringSize().height(); // Cap the number of lines to |max_lines()| if multi-line and non-zero // |max_lines()|. height = multi_line() && max_lines() > 0 ? std::min(max_lines() * base_line_height, string_height) : string_height; } height -= gfx::ShadowValue::GetMargin(full_text_->shadows()).height(); return height + GetInsets().height(); } void Label::Layout() { ClearDisplayText(); } const char* Label::GetClassName() const { return kViewClassName; } View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) { if (!handles_tooltips_ || (tooltip_text_.empty() && !ShouldShowDefaultTooltip())) return nullptr; return HitTestPoint(point) ? this : nullptr; } bool Label::CanProcessEventsWithinSubtree() const { return !!GetRenderTextForSelectionController(); } WordLookupClient* Label::GetWordLookupClient() { return this; } void Label::GetAccessibleNodeData(ui::AXNodeData* node_data) { if (text_context_ == style::CONTEXT_DIALOG_TITLE) node_data->role = ax::mojom::Role::kTitleBar; else node_data->role = ax::mojom::Role::kStaticText; node_data->SetName(full_text_->GetDisplayText()); } bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { if (!handles_tooltips_) return false; if (!tooltip_text_.empty()) { tooltip->assign(tooltip_text_); return true; } if (ShouldShowDefaultTooltip()) { tooltip->assign(full_text_->GetDisplayText()); return true; } return false; } std::unique_ptr Label::CreateRenderText() const { // Multi-line labels only support NO_ELIDE and ELIDE_TAIL for now. // TODO(warx): Investigate more elide text support. gfx::ElideBehavior elide_behavior = multi_line() && (elide_behavior_ != gfx::NO_ELIDE) ? gfx::ELIDE_TAIL : elide_behavior_; auto render_text = gfx::RenderText::CreateHarfBuzzInstance(); render_text->SetHorizontalAlignment(horizontal_alignment()); render_text->SetDirectionalityMode(full_text_->directionality_mode()); render_text->SetElideBehavior(elide_behavior); render_text->SetObscured(obscured()); render_text->SetMinLineHeight(line_height()); render_text->SetFontList(font_list()); render_text->set_shadows(shadows()); render_text->SetCursorEnabled(false); render_text->SetText(text()); render_text->SetMultiline(multi_line()); render_text->SetMaxLines(multi_line() ? max_lines() : 0); render_text->SetWordWrapBehavior(full_text_->word_wrap_behavior()); // Setup render text for selection controller. if (selectable()) { render_text->set_focused(HasFocus()); if (stored_selection_range_.IsValid()) render_text->SelectRange(stored_selection_range_); } return render_text; } void Label::PaintFocusRing(gfx::Canvas* canvas) const { // No focus ring by default. } gfx::Rect Label::GetFocusRingBounds() const { MaybeBuildDisplayText(); gfx::Rect focus_bounds; if (!display_text_) { focus_bounds = gfx::Rect(GetTextSize()); } else { focus_bounds = gfx::Rect(gfx::Point() + display_text_->GetLineOffset(0), display_text_->GetStringSize()); } focus_bounds.Inset(-NonBorderInsets(*this)); focus_bounds.Intersect(GetLocalBounds()); return focus_bounds; } void Label::PaintText(gfx::Canvas* canvas) { MaybeBuildDisplayText(); if (display_text_) display_text_->Draw(canvas); #if DCHECK_IS_ON() // Attempt to ensure that if we're using subpixel rendering, we're painting // to an opaque background. What we don't want to find is an ancestor in the // hierarchy that paints to a non-opaque layer. if (!display_text_ || display_text_->subpixel_rendering_suppressed()) return; for (View* view = this; view; view = view->parent()) { if (view->background() && SkColorGetA(view->background()->get_color()) == SK_AlphaOPAQUE) break; if (view->layer() && view->layer()->fills_bounds_opaquely()) { DLOG(WARNING) << "Ancestor view has a non-opaque layer: " << view->GetClassName() << " with ID " << view->id(); break; } } #endif } void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) { if (previous_bounds.size() != size()) InvalidateLayout(); } void Label::OnPaint(gfx::Canvas* canvas) { View::OnPaint(canvas); PaintText(canvas); if (HasFocus()) PaintFocusRing(canvas); } void Label::OnNativeThemeChanged(const ui::NativeTheme* theme) { UpdateColorsFromTheme(theme); } gfx::NativeCursor Label::GetCursor(const ui::MouseEvent& event) { return GetRenderTextForSelectionController() ? GetNativeIBeamCursor() : gfx::kNullCursor; } void Label::OnFocus() { gfx::RenderText* render_text = GetRenderTextForSelectionController(); if (render_text) { render_text->set_focused(true); SchedulePaint(); } View::OnFocus(); } void Label::OnBlur() { gfx::RenderText* render_text = GetRenderTextForSelectionController(); if (render_text) { render_text->set_focused(false); SchedulePaint(); } View::OnBlur(); } bool Label::OnMousePressed(const ui::MouseEvent& event) { if (!GetRenderTextForSelectionController()) return false; const bool had_focus = HasFocus(); // RequestFocus() won't work when the label has FocusBehavior::NEVER. Hence // explicitly set the focused view. // TODO(karandeepb): If a widget with a label having FocusBehavior::NEVER as // the currently focused view (due to selection) was to lose focus, focus // won't be restored to the label (and hence a text selection won't be drawn) // when the widget gets focus again. Fix this. // Tracked in https://crbug.com/630365. if ((event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) && GetFocusManager() && !had_focus) { GetFocusManager()->SetFocusedView(this); } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) if (event.IsOnlyMiddleMouseButton() && GetFocusManager() && !had_focus) GetFocusManager()->SetFocusedView(this); #endif return selection_controller_->OnMousePressed( event, false, had_focus ? SelectionController::FOCUSED : SelectionController::UNFOCUSED); } bool Label::OnMouseDragged(const ui::MouseEvent& event) { if (!GetRenderTextForSelectionController()) return false; return selection_controller_->OnMouseDragged(event); } void Label::OnMouseReleased(const ui::MouseEvent& event) { if (!GetRenderTextForSelectionController()) return; selection_controller_->OnMouseReleased(event); } void Label::OnMouseCaptureLost() { if (!GetRenderTextForSelectionController()) return; selection_controller_->OnMouseCaptureLost(); } bool Label::OnKeyPressed(const ui::KeyEvent& event) { if (!GetRenderTextForSelectionController()) return false; const bool shift = event.IsShiftDown(); const bool control = event.IsControlDown(); const bool alt = event.IsAltDown() || event.IsAltGrDown(); switch (event.key_code()) { case ui::VKEY_C: if (control && !alt && HasSelection()) { CopyToClipboard(); return true; } break; case ui::VKEY_INSERT: if (control && !shift && HasSelection()) { CopyToClipboard(); return true; } break; case ui::VKEY_A: if (control && !alt && !text().empty()) { SelectAll(); DCHECK(HasSelection()); UpdateSelectionClipboard(); return true; } break; default: break; } return false; } bool Label::AcceleratorPressed(const ui::Accelerator& accelerator) { // Allow the "Copy" action from the Chrome menu to be invoked. E.g., if a user // selects a Label on a web modal dialog. "Select All" doesn't appear in the // Chrome menu so isn't handled here. if (accelerator.key_code() == ui::VKEY_C && accelerator.IsCtrlDown()) { CopyToClipboard(); return true; } return false; } bool Label::CanHandleAccelerators() const { // Focus needs to be checked since the accelerator for the Copy command from // the Chrome menu should only be handled when the current view has focus. See // related comment in BrowserView::CutCopyPaste. return HasFocus() && GetRenderTextForSelectionController() && View::CanHandleAccelerators(); } void Label::OnDeviceScaleFactorChanged(float old_device_scale_factor, float new_device_scale_factor) { View::OnDeviceScaleFactorChanged(old_device_scale_factor, new_device_scale_factor); // When the device scale factor is changed, some font rendering parameters is // changed (especially, hinting). The bounding box of the text has to be // re-computed based on the new parameters. See crbug.com/441439 ResetLayout(); } void Label::VisibilityChanged(View* starting_from, bool is_visible) { if (!is_visible) ClearDisplayText(); } void Label::ShowContextMenuForView(View* source, const gfx::Point& point, ui::MenuSourceType source_type) { if (!GetRenderTextForSelectionController()) return; context_menu_runner_.reset( new MenuRunner(&context_menu_contents_, MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU)); context_menu_runner_->RunMenuAt(GetWidget(), nullptr, gfx::Rect(point, gfx::Size()), MENU_ANCHOR_TOPLEFT, source_type); } bool Label::GetWordLookupDataAtPoint(const gfx::Point& point, gfx::DecoratedText* decorated_word, gfx::Point* baseline_point) { gfx::RenderText* render_text = GetRenderTextForSelectionController(); return render_text ? render_text->GetWordLookupDataAtPoint( point, decorated_word, baseline_point) : false; } bool Label::GetWordLookupDataFromSelection(gfx::DecoratedText* decorated_text, gfx::Point* baseline_point) { gfx::RenderText* render_text = GetRenderTextForSelectionController(); return render_text ? render_text->GetLookupDataForRange( render_text->selection(), decorated_text, baseline_point) : false; } gfx::RenderText* Label::GetRenderTextForSelectionController() { return const_cast( static_cast(this)->GetRenderTextForSelectionController()); } bool Label::IsReadOnly() const { return true; } bool Label::SupportsDrag() const { // TODO(crbug.com/661379): Labels should support dragging selected text. return false; } bool Label::HasTextBeingDragged() const { return false; } void Label::SetTextBeingDragged(bool value) { NOTREACHED(); } int Label::GetViewHeight() const { return height(); } int Label::GetViewWidth() const { return width(); } int Label::GetDragSelectionDelay() const { // Labels don't need to use a repeating timer to update the drag selection. // Since the cursor is disabled for labels, a selection outside the display // area won't change the text in the display area. It is expected that all the // text will fit in the display area for labels anyway. return 0; } void Label::OnBeforePointerAction() {} void Label::OnAfterPointerAction(bool text_changed, bool selection_changed) { DCHECK(!text_changed); if (selection_changed) SchedulePaint(); } bool Label::PasteSelectionClipboard() { NOTREACHED(); return false; } void Label::UpdateSelectionClipboard() { #if defined(OS_LINUX) && !defined(OS_CHROMEOS) if (!obscured()) { ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_SELECTION) .WriteText(GetSelectedText()); } #endif } bool Label::IsCommandIdChecked(int command_id) const { return true; } bool Label::IsCommandIdEnabled(int command_id) const { switch (command_id) { case IDS_APP_COPY: return HasSelection() && !obscured(); case IDS_APP_SELECT_ALL: return GetRenderTextForSelectionController() && !text().empty(); } return false; } void Label::ExecuteCommand(int command_id, int event_flags) { switch (command_id) { case IDS_APP_COPY: CopyToClipboard(); break; case IDS_APP_SELECT_ALL: SelectAll(); DCHECK(HasSelection()); UpdateSelectionClipboard(); break; default: NOTREACHED(); } } bool Label::GetAcceleratorForCommandId(int command_id, ui::Accelerator* accelerator) const { switch (command_id) { case IDS_APP_COPY: *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN); return true; case IDS_APP_SELECT_ALL: *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN); return true; default: return false; } } const gfx::RenderText* Label::GetRenderTextForSelectionController() const { if (!selectable()) return nullptr; MaybeBuildDisplayText(); // This may be null when the content bounds of the view are empty. return display_text_.get(); } void Label::Init(const base::string16& text, const gfx::FontList& font_list) { full_text_ = gfx::RenderText::CreateHarfBuzzInstance(); DCHECK(full_text_->MultilineSupported()); full_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER); full_text_->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT); // NOTE: |full_text_| should not be elided at all. This is used to keep // some properties and to compute the size of the string. full_text_->SetElideBehavior(gfx::NO_ELIDE); full_text_->SetFontList(font_list); full_text_->SetCursorEnabled(false); full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS); elide_behavior_ = gfx::ELIDE_TAIL; stored_selection_range_ = gfx::Range::InvalidRange(); enabled_color_set_ = background_color_set_ = false; selection_text_color_set_ = selection_background_color_set_ = false; subpixel_rendering_enabled_ = true; auto_color_readability_ = true; multi_line_ = false; max_lines_ = 0; UpdateColorsFromTheme(GetNativeTheme()); handles_tooltips_ = true; collapse_when_hidden_ = false; fixed_width_ = 0; max_width_ = 0; SetText(text); // Only selectable labels will get requests to show the context menu, due to // CanProcessEventsWithinSubtree(). BuildContextMenuContents(); set_context_menu_controller(this); // This allows the BrowserView to pass the copy command from the Chrome menu // to the Label. AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN)); } void Label::ResetLayout() { InvalidateLayout(); PreferredSizeChanged(); SchedulePaint(); ClearDisplayText(); } void Label::MaybeBuildDisplayText() const { if (display_text_) return; gfx::Rect rect = GetContentsBounds(); rect.Inset(NonBorderInsets(*this)); if (rect.IsEmpty()) return; rect.Inset(-gfx::ShadowValue::GetMargin(shadows())); display_text_ = CreateRenderText(); display_text_->SetDisplayRect(rect); stored_selection_range_ = gfx::Range::InvalidRange(); ApplyTextColors(); } gfx::Size Label::GetTextSize() const { gfx::Size size; if (text().empty()) { size = gfx::Size(0, std::max(line_height(), font_list().GetHeight())); } else { // Cancel the display rect of |full_text_|. The display rect may be // specified in GetHeightForWidth(), and specifying empty Rect cancels // its effect. See also the comment in GetHeightForWidth(). // TODO(mukai): use gfx::Rect() to compute the ideal size rather than // the current width(). See crbug.com/468494, crbug.com/467526, and // the comment for MultilinePreferredSizeTest in label_unittest.cc. full_text_->SetDisplayRect(gfx::Rect(0, 0, width(), 0)); size = full_text_->GetStringSize(); } const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows()); size.Enlarge(shadow_margin.width(), shadow_margin.height()); return size; } void Label::RecalculateColors() { actual_enabled_color_ = auto_color_readability_ ? color_utils::GetReadableColor(requested_enabled_color_, background_color_) : requested_enabled_color_; actual_selection_text_color_ = auto_color_readability_ ? color_utils::GetReadableColor(requested_selection_text_color_, selection_background_color_) : requested_selection_text_color_; ApplyTextColors(); SchedulePaint(); } void Label::ApplyTextColors() const { if (!display_text_) return; bool subpixel_rendering_suppressed = SkColorGetA(background_color_) != SK_AlphaOPAQUE || !subpixel_rendering_enabled_; display_text_->SetColor(actual_enabled_color_); display_text_->set_selection_color(actual_selection_text_color_); display_text_->set_selection_background_focused_color( selection_background_color_); display_text_->set_subpixel_rendering_suppressed( subpixel_rendering_suppressed); } void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) { if (!enabled_color_set_) { requested_enabled_color_ = style::GetColor(*this, text_context_, style::STYLE_PRIMARY); } if (!background_color_set_) { background_color_ = theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground); } if (!selection_text_color_set_) { requested_selection_text_color_ = theme->GetSystemColor( ui::NativeTheme::kColorId_LabelTextSelectionColor); } if (!selection_background_color_set_) { selection_background_color_ = theme->GetSystemColor( ui::NativeTheme::kColorId_LabelTextSelectionBackgroundFocused); } RecalculateColors(); } bool Label::ShouldShowDefaultTooltip() const { const gfx::Size text_size = GetTextSize(); const gfx::Size size = GetContentsBounds().size(); return !obscured() && (text_size.width() > size.width() || (multi_line() && text_size.height() > size.height())); } void Label::ClearDisplayText() const { // The HasSelection() call below will build |display_text_| in case it is // empty. Return early to avoid this. if (!display_text_) return; // Persist the selection range if there is an active selection. if (HasSelection()) { stored_selection_range_ = GetRenderTextForSelectionController()->selection(); } display_text_ = nullptr; } base::string16 Label::GetSelectedText() const { const gfx::RenderText* render_text = GetRenderTextForSelectionController(); return render_text ? render_text->GetTextFromRange(render_text->selection()) : base::string16(); } void Label::CopyToClipboard() { if (!HasSelection() || obscured()) return; ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE) .WriteText(GetSelectedText()); } void Label::BuildContextMenuContents() { context_menu_contents_.AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY); context_menu_contents_.AddItemWithStringId(IDS_APP_SELECT_ALL, IDS_APP_SELECT_ALL); } } // namespace views