#include "ui/views/controls/label.h"
#include <stddef.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include <vector>
#include "base/i18n/rtl.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/default_style.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/layer.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/strings/grit/ui_strings.h"
#include "ui/views/background.h"
#include "ui/views/cascading_property.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/selection_controller.h"
namespace {
enum LabelPropertyKey {
kLabelText = 1,
kLabelShadows,
kLabelHorizontalAlignment,
kLabelVerticalAlignment,
kLabelLineHeight,
kLabelObscured,
kLabelAllowCharacterBreak,
kLabelDrawStringsFlags,
};
bool IsOpaque(SkColor color) {
return SkColorGetA(color) == SK_AlphaOPAQUE;
}
gfx::Range StripAcceleratorChars(int flags, std::u16string* text) {
if (flags & (gfx::Canvas::SHOW_PREFIX | gfx::Canvas::HIDE_PREFIX)) {
int char_pos = -1;
int char_span = 0;
*text = gfx::LocateAndRemoveAcceleratorChar(*text, &char_pos, &char_span);
if ((flags & gfx::Canvas::SHOW_PREFIX) && char_pos != -1) {
return gfx::Range(static_cast<size_t>(char_pos),
static_cast<size_t>(char_pos + char_span));
}
}
return gfx::Range::InvalidRange();
}
}
namespace views {
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(CascadingProperty<SkColor>,
kCascadingLabelEnabledColor,
nullptr)
Label::Label() : Label(std::u16string()) {}
Label::Label(const std::u16string& text)
: Label(text, style::CONTEXT_LABEL, style::STYLE_PRIMARY) {}
Label::Label(const std::u16string& text,
int text_context,
int text_style,
gfx::DirectionalityMode directionality_mode)
: text_context_(text_context),
text_style_(text_style),
context_menu_contents_(this) {
Init(text, style::GetFont(text_context, text_style), directionality_mode);
}
Label::Label(const std::u16string& text, const CustomFont& font)
: text_context_(style::CONTEXT_LABEL),
text_style_(style::STYLE_PRIMARY),
context_menu_contents_(this) {
Init(text, font.font_list, gfx::DirectionalityMode::DIRECTIONALITY_FROM_TEXT);
}
Label::~Label() = default;
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);
ClearDisplayText();
PreferredSizeChanged();
}
const std::u16string& Label::GetText() const {
return full_text_->text();
}
void Label::SetText(const std::u16string& new_text) {
if (new_text == GetText())
return;
std::u16string current_text = GetText();
full_text_->SetText(new_text);
ClearDisplayText();
if (GetAccessibleName().empty() || GetAccessibleName() == current_text) {
SetAccessibleName(new_text);
}
OnPropertyChanged(&full_text_ + kLabelText,
kPropertyEffectsPreferredSizeChanged);
stored_selection_range_ = gfx::Range::InvalidRange();
}
void Label::AdjustAccessibleName(std::u16string& new_name,
ax::mojom::NameFrom& name_from) {
if (new_name.empty()) {
new_name = full_text_->GetDisplayText();
}
}
int Label::GetTextContext() const {
return text_context_;
}
void Label::SetTextContext(int text_context) {
if (text_context == text_context_)
return;
text_context_ = text_context;
full_text_->SetFontList(style::GetFont(text_context_, text_style_));
full_text_->SetMinLineHeight(GetLineHeight());
ClearDisplayText();
if (GetWidget())
UpdateColorsFromTheme();
SetAccessibleRole(text_context_ == style::CONTEXT_DIALOG_TITLE
? ax::mojom::Role::kTitleBar
: ax::mojom::Role::kStaticText);
OnPropertyChanged(&text_context_, kPropertyEffectsPreferredSizeChanged);
}
int Label::GetTextStyle() const {
return text_style_;
}
void Label::SetTextStyle(int style) {
if (style == text_style_)
return;
text_style_ = style;
ApplyBaselineTextStyle();
}
void Label::ApplyBaselineTextStyle() {
full_text_->SetFontList(style::GetFont(text_context_, text_style_));
full_text_->SetMinLineHeight(GetLineHeight());
ClearDisplayText();
if (GetWidget())
UpdateColorsFromTheme();
OnPropertyChanged(&text_style_, kPropertyEffectsPreferredSizeChanged);
}
void Label::SetTextStyleRange(int style, const gfx::Range& range) {
if (style == text_style_ || !range.IsValid() || range.is_empty() ||
!gfx::Range(0, GetText().size()).Contains(range)) {
return;
}
const auto details = style::GetFontDetails(text_context_, style);
DCHECK_EQ(details.typeface,
style::GetFontDetails(text_context_, text_style_).typeface);
DCHECK_EQ(details.size_delta,
style::GetFontDetails(text_context_, text_style_).size_delta);
full_text_->ApplyWeight(details.weight, range);
ClearDisplayText();
PreferredSizeChanged();
}
bool Label::GetAutoColorReadabilityEnabled() const {
return auto_color_readability_enabled_;
}
void Label::SetAutoColorReadabilityEnabled(
bool auto_color_readability_enabled) {
if (auto_color_readability_enabled_ == auto_color_readability_enabled)
return;
auto_color_readability_enabled_ = auto_color_readability_enabled;
RecalculateColors();
OnPropertyChanged(&auto_color_readability_enabled_, kPropertyEffectsPaint);
}
SkColor Label::GetEnabledColor() const {
return actual_enabled_color_;
}
void Label::SetEnabledColor(SkColor color) {
if (enabled_color_set_ && requested_enabled_color_ == color)
return;
enabled_color_set_ = true;
requested_enabled_color_ = color;
enabled_color_id_.reset();
RecalculateColors();
OnPropertyChanged(&requested_enabled_color_, kPropertyEffectsPaint);
}
absl::optional<ui::ColorId> Label::GetEnabledColorId() const {
return enabled_color_id_;
}
void Label::SetEnabledColorId(absl::optional<ui::ColorId> enabled_color_id) {
if (enabled_color_id_ == enabled_color_id)
return;
enabled_color_id_ = enabled_color_id;
if (GetWidget()) {
UpdateColorsFromTheme();
enabled_color_set_ = true;
}
OnPropertyChanged(&enabled_color_id_, kPropertyEffectsPaint);
}
SkColor Label::GetBackgroundColor() const {
return background_color_;
}
void Label::SetBackgroundColor(SkColor color) {
if (background_color_set_ && background_color_ == color)
return;
background_color_ = color;
background_color_set_ = true;
if (GetWidget()) {
UpdateColorsFromTheme();
} else {
RecalculateColors();
}
OnPropertyChanged(&background_color_, kPropertyEffectsPaint);
}
void Label::SetBackgroundColorId(
absl::optional<ui::ColorId> background_color_id) {
if (background_color_id_ == background_color_id)
return;
background_color_id_ = background_color_id;
if (GetWidget()) {
UpdateColorsFromTheme();
}
OnPropertyChanged(&background_color_id_, kPropertyEffectsPaint);
}
SkColor Label::GetSelectionTextColor() const {
return actual_selection_text_color_;
}
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();
OnPropertyChanged(&requested_selection_text_color_, kPropertyEffectsPaint);
}
SkColor Label::GetSelectionBackgroundColor() const {
return selection_background_color_;
}
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();
OnPropertyChanged(&selection_background_color_, kPropertyEffectsPaint);
}
const gfx::ShadowValues& Label::GetShadows() const {
return full_text_->shadows();
}
void Label::SetShadows(const gfx::ShadowValues& shadows) {
if (full_text_->shadows() == shadows)
return;
full_text_->set_shadows(shadows);
ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelShadows,
kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetSubpixelRenderingEnabled() const {
return subpixel_rendering_enabled_;
}
void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
if (subpixel_rendering_enabled_ == subpixel_rendering_enabled)
return;
subpixel_rendering_enabled_ = subpixel_rendering_enabled;
ApplyTextColors();
OnPropertyChanged(&subpixel_rendering_enabled_, kPropertyEffectsPaint);
}
bool Label::GetSkipSubpixelRenderingOpacityCheck() const {
return skip_subpixel_rendering_opacity_check_;
}
void Label::SetSkipSubpixelRenderingOpacityCheck(
bool skip_subpixel_rendering_opacity_check) {
if (skip_subpixel_rendering_opacity_check_ ==
skip_subpixel_rendering_opacity_check) {
return;
}
skip_subpixel_rendering_opacity_check_ =
skip_subpixel_rendering_opacity_check;
OnPropertyChanged(&skip_subpixel_rendering_opacity_check_,
kPropertyEffectsNone);
}
gfx::HorizontalAlignment Label::GetHorizontalAlignment() const {
return full_text_->horizontal_alignment();
}
void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
alignment = gfx::MaybeFlipForRTL(alignment);
if (GetHorizontalAlignment() == alignment)
return;
full_text_->SetHorizontalAlignment(alignment);
ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelHorizontalAlignment,
kPropertyEffectsPaint);
}
gfx::VerticalAlignment Label::GetVerticalAlignment() const {
return full_text_->vertical_alignment();
}
void Label::SetVerticalAlignment(gfx::VerticalAlignment alignment) {
if (GetVerticalAlignment() == alignment)
return;
full_text_->SetVerticalAlignment(alignment);
ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelVerticalAlignment,
kPropertyEffectsPaint);
}
int Label::GetLineHeight() const {
return line_height_.value_or(
std::max(style::GetLineHeight(text_context_, text_style_),
font_list().GetHeight()));
}
void Label::SetLineHeight(int line_height) {
if (line_height_ == line_height)
return;
line_height_ = line_height;
full_text_->SetMinLineHeight(line_height);
ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelLineHeight,
kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetMultiLine() const {
return multi_line_;
}
void Label::SetMultiLine(bool multi_line) {
DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL ||
elide_behavior_ == gfx::NO_ELIDE));
if (this->GetMultiLine() == multi_line)
return;
multi_line_ = multi_line;
max_width_single_line_ = 0;
full_text_->SetMultiline(multi_line);
ClearDisplayText();
OnPropertyChanged(&multi_line_, kPropertyEffectsPreferredSizeChanged);
}
size_t Label::GetMaxLines() const {
return max_lines_;
}
void Label::SetMaxLines(size_t max_lines) {
if (max_lines_ == max_lines)
return;
max_lines_ = max_lines;
OnPropertyChanged(&max_lines_, kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetObscured() const {
return full_text_->obscured();
}
void Label::SetObscured(bool obscured) {
if (this->GetObscured() == obscured)
return;
full_text_->SetObscured(obscured);
ClearDisplayText();
if (obscured)
SetSelectable(false);
OnPropertyChanged(&full_text_ + kLabelObscured,
kPropertyEffectsPreferredSizeChanged);
}
bool Label::IsDisplayTextTruncated() const {
MaybeBuildDisplayText();
if (!full_text_ || full_text_->text().empty())
return false;
auto text_bounds = GetTextBounds();
return (display_text_ &&
display_text_->text() != display_text_->GetDisplayText()) ||
text_bounds.width() > GetContentsBounds().width() ||
text_bounds.height() > GetContentsBounds().height();
}
bool Label::GetAllowCharacterBreak() const {
return full_text_->word_wrap_behavior() == gfx::WRAP_LONG_WORDS;
}
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);
ClearDisplayText();
OnPropertyChanged(&full_text_ + kLabelAllowCharacterBreak,
kPropertyEffectsPreferredSizeChanged);
}
size_t Label::GetTextIndexOfLine(size_t line) const {
return full_text_->GetTextIndexOfLine(line);
}
void Label::SetTruncateLength(size_t truncate_length) {
return full_text_->set_truncate_length(truncate_length);
}
gfx::ElideBehavior Label::GetElideBehavior() const {
return elide_behavior_;
}
void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
DCHECK(!GetMultiLine() || (elide_behavior == gfx::ELIDE_TAIL ||
elide_behavior == gfx::NO_ELIDE));
if (elide_behavior_ == elide_behavior)
return;
elide_behavior_ = elide_behavior;
UpdateFullTextElideBehavior();
ClearDisplayText();
OnPropertyChanged(&elide_behavior_, kPropertyEffectsPreferredSizeChanged);
}
void Label::SetDrawStringsFlags(int flags) {
if (draw_strings_flags_ == flags)
return;
draw_strings_flags_ = flags;
full_text_->SetDrawStringsFlags(draw_strings_flags_);
OnPropertyChanged(&full_text_ + kLabelDrawStringsFlags,
kPropertyEffectsPreferredSizeChanged);
}
std::u16string Label::GetTooltipText() const {
return tooltip_text_;
}
void Label::SetTooltipText(const std::u16string& tooltip_text) {
DCHECK(handles_tooltips_);
if (tooltip_text_ == tooltip_text)
return;
tooltip_text_ = tooltip_text;
TooltipTextChanged();
OnPropertyChanged(&tooltip_text_, kPropertyEffectsNone);
}
bool Label::GetHandlesTooltips() const {
return handles_tooltips_;
}
void Label::SetHandlesTooltips(bool enabled) {
if (handles_tooltips_ == enabled)
return;
handles_tooltips_ = enabled;
OnPropertyChanged(&handles_tooltips_, kPropertyEffectsNone);
}
void Label::SizeToFit(int fixed_width) {
DCHECK(GetMultiLine());
DCHECK_EQ(0, max_width_);
fixed_width_ = fixed_width;
SizeToPreferredSize();
}
int Label::GetMaximumWidth() const {
return max_width_;
}
void Label::SetMaximumWidth(int max_width) {
DCHECK(GetMultiLine());
DCHECK_EQ(0, fixed_width_);
if (max_width_ == max_width)
return;
max_width_ = max_width;
OnPropertyChanged(&max_width_, kPropertyEffectsPreferredSizeChanged);
}
void Label::SetMaximumWidthSingleLine(int max_width) {
DCHECK(!GetMultiLine());
if (max_width_single_line_ == max_width)
return;
max_width_single_line_ = max_width;
UpdateFullTextElideBehavior();
OnPropertyChanged(&max_width_single_line_,
kPropertyEffectsPreferredSizeChanged);
}
bool Label::GetCollapseWhenHidden() const {
return collapse_when_hidden_;
}
void Label::SetCollapseWhenHidden(bool value) {
if (collapse_when_hidden_ == value)
return;
collapse_when_hidden_ = value;
OnPropertyChanged(&collapse_when_hidden_,
kPropertyEffectsPreferredSizeChanged);
}
size_t Label::GetRequiredLines() const {
return full_text_->GetNumLines();
}
std::u16string Label::GetDisplayTextForTesting() {
MaybeBuildDisplayText();
return display_text_ ? display_text_->GetDisplayText() : std::u16string();
}
base::i18n::TextDirection Label::GetTextDirectionForTesting() {
return full_text_->GetDisplayTextDirection();
}
bool Label::IsSelectionSupported() const {
return !GetObscured();
}
bool Label::GetSelectable() const {
return !!selection_controller_;
}
bool Label::SetSelectable(bool value) {
if (value == GetSelectable())
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<SelectionController>(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();
}
std::vector<gfx::Rect> Label::GetSubstringBounds(const gfx::Range& range) {
auto substring_bounds = full_text_->GetSubstringBounds(range);
for (auto& bound : substring_bounds) {
bound.Offset(GetInsets().left(), GetInsets().top());
}
return substring_bounds;
}
base::CallbackListSubscription Label::AddTextChangedCallback(
views::PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&full_text_ + kLabelText,
std::move(callback));
}
int Label::GetBaseline() const {
return GetInsets().top() + font_list().GetBaseline();
}
gfx::Size Label::CalculatePreferredSize() const {
return CalculatePreferredSize({width(), {}});
}
gfx::Size Label::CalculatePreferredSize(
const SizeBounds& available_size) const {
if (!GetVisible() && collapse_when_hidden_)
return gfx::Size();
if (GetMultiLine() && fixed_width_ != 0 && !GetText().empty())
return gfx::Size(fixed_width_, GetHeightForWidth(fixed_width_));
gfx::Size size(GetBoundedTextSize(available_size));
const gfx::Insets insets = GetInsets();
size.Enlarge(insets.width(), insets.height());
if (GetMultiLine() && max_width_ != 0 && max_width_ < size.width())
return gfx::Size(max_width_, GetHeightForWidth(max_width_));
if (GetMultiLine() && GetMaxLines() > 0)
return gfx::Size(size.width(), GetHeightForWidth(size.width()));
return size;
}
gfx::Size Label::GetMinimumSize() const {
if (!GetVisible() && collapse_when_hidden_)
return gfx::Size();
gfx::Size size(0, GetLineHeight());
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(
std::u16string(gfx::kEllipsisUTF16), font_list()));
}
if (!GetMultiLine()) {
if (elide_behavior_ == gfx::NO_ELIDE) {
size.SetToMax(GetTextSize());
} else {
size.SetToMin(GetTextSize());
}
}
size.Enlarge(GetInsets().width(), GetInsets().height());
return size;
}
int Label::GetHeightForWidth(int w) const {
if (!GetVisible() && collapse_when_hidden_)
return 0;
w -= GetInsets().width();
int height = 0;
int base_line_height = GetLineHeight();
if (!GetMultiLine() || GetText().empty() || w < 0) {
height = base_line_height;
} else if (w == 0) {
height =
std::max(base::checked_cast<int>(GetMaxLines()), 1) * base_line_height;
} else {
full_text_->SetDisplayRect(gfx::Rect(0, 0, w, 0));
int string_height = full_text_->GetStringSize().height();
height = GetMaxLines() > 0
? std::min(base::checked_cast<int>(GetMaxLines()) *
base_line_height,
string_height)
: string_height;
}
height -= gfx::ShadowValue::GetMargin(full_text_->shadows()).height();
return height + GetInsets().height();
}
View* Label::GetTooltipHandlerForPoint(const gfx::Point& point) {
if (!handles_tooltips_ ||
(tooltip_text_.empty() && !ShouldShowDefaultTooltip())) {
return nullptr;
}
return HitTestPoint(point) ? this : nullptr;
}
bool Label::GetCanProcessEventsWithinSubtree() const {
return !!GetRenderTextForSelectionController();
}
WordLookupClient* Label::GetWordLookupClient() {
return this;
}
std::u16string Label::GetTooltipText(const gfx::Point& p) const {
if (handles_tooltips_) {
if (!tooltip_text_.empty())
return tooltip_text_;
if (ShouldShowDefaultTooltip())
return full_text_->GetDisplayText();
}
return std::u16string();
}
std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
gfx::ElideBehavior elide_behavior =
GetMultiLine() && (elide_behavior_ != gfx::NO_ELIDE) ? gfx::ELIDE_TAIL
: elide_behavior_;
std::unique_ptr<gfx::RenderText> render_text =
full_text_->CreateInstanceOfSameStyle(GetText());
render_text->SetHorizontalAlignment(GetHorizontalAlignment());
render_text->SetVerticalAlignment(GetVerticalAlignment());
render_text->SetElideBehavior(elide_behavior);
render_text->SetObscured(GetObscured());
render_text->SetMinLineHeight(GetLineHeight());
render_text->set_shadows(GetShadows());
const bool multiline = GetMultiLine();
render_text->SetMultiline(multiline);
render_text->SetMaxLines(multiline ? GetMaxLines() : size_t{0});
render_text->SetWordWrapBehavior(full_text_->word_wrap_behavior());
if (GetSelectable()) {
render_text->set_focused(HasFocus());
if (stored_selection_range_.IsValid())
render_text->SelectRange(stored_selection_range_);
}
if (draw_strings_flags_ != 0) {
auto text_str = GetText();
gfx::Range range = StripAcceleratorChars(draw_strings_flags_, &text_str);
render_text->SetText(text_str);
if (range.IsValid()) {
render_text->SetDisplayRect(bounds());
render_text->ApplyStyle(gfx::TEXT_STYLE_UNDERLINE, true, range);
}
}
return render_text;
}
gfx::Rect Label::GetTextBounds() const {
MaybeBuildDisplayText();
if (!display_text_)
return gfx::Rect(GetTextSize());
return gfx::Rect(gfx::Point() + display_text_->GetLineOffset(0),
display_text_->GetStringSize());
}
int Label::GetFontListY() const {
MaybeBuildDisplayText();
if (!display_text_)
return 0;
return GetInsets().top() + display_text_->GetBaseline() -
font_list().GetBaseline();
}
void Label::PaintText(gfx::Canvas* canvas) {
MaybeBuildDisplayText();
if (display_text_)
display_text_->Draw(canvas);
#if DCHECK_IS_ON() && !BUILDFLAG(IS_CHROMEOS_ASH) && \
!BUILDFLAG(IS_CHROMEOS_LACROS)
if (!display_text_ || display_text_->subpixel_rendering_suppressed() ||
skip_subpixel_rendering_opacity_check_) {
return;
}
for (View* view = this; view; view = view->parent()) {
if (view->background() && IsOpaque(view->background()->get_color()))
break;
if (view->layer()) {
DCHECK(view->layer()->fills_bounds_opaquely());
break;
}
}
#endif
}
void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) {
ClearDisplayText();
}
void Label::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
PaintText(canvas);
}
void Label::OnThemeChanged() {
View::OnThemeChanged();
UpdateColorsFromTheme();
}
ui::Cursor Label::GetCursor(const ui::MouseEvent& event) {
return GetRenderTextForSelectionController() ? ui::mojom::CursorType::kIBeam
: ui::Cursor();
}
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();
if ((event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton()) &&
GetFocusManager() && !had_focus) {
GetFocusManager()->SetFocusedView(this);
}
if (ui::Clipboard::IsSupportedClipboardBuffer(
ui::ClipboardBuffer::kSelection)) {
if (event.IsOnlyMiddleMouseButton() && GetFocusManager() && !had_focus)
GetFocusManager()->SetFocusedView(this);
}
return selection_controller_->OnMousePressed(
event, false,
had_focus
? SelectionController::InitialFocusStateOnMousePress::kFocused
: SelectionController::InitialFocusStateOnMousePress::kUnFocused);
}
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 && !GetText().empty()) {
SelectAll();
DCHECK(HasSelection());
UpdateSelectionClipboard();
return true;
}
break;
default:
break;
}
return false;
}
bool Label::AcceleratorPressed(const ui::Accelerator& accelerator) {
if (accelerator.key_code() == ui::VKEY_C && accelerator.IsCtrlDown()) {
CopyToClipboard();
return true;
}
return false;
}
bool Label::CanHandleAccelerators() const {
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);
PreferredSizeChanged();
}
void Label::VisibilityChanged(View* starting_from, bool is_visible) {
if (!is_visible)
ClearDisplayText();
}
void Label::ShowContextMenuForViewImpl(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
if (!GetRenderTextForSelectionController())
return;
context_menu_runner_ = std::make_unique<MenuRunner>(
&context_menu_contents_,
MenuRunner::HAS_MNEMONICS | MenuRunner::CONTEXT_MENU);
context_menu_runner_->RunMenuAt(GetWidget(), nullptr,
gfx::Rect(point, gfx::Size()),
MenuAnchorPosition::kTopLeft, 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<gfx::RenderText*>(
static_cast<const Label*>(this)->GetRenderTextForSelectionController());
}
bool Label::IsReadOnly() const {
return true;
}
bool Label::SupportsDrag() const {
return false;
}
bool Label::HasTextBeingDragged() const {
return false;
}
void Label::SetTextBeingDragged(bool value) {
NOTREACHED_NORETURN();
}
int Label::GetViewHeight() const {
return height();
}
int Label::GetViewWidth() const {
return width();
}
int Label::GetDragSelectionDelay() const {
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_NORETURN();
}
void Label::UpdateSelectionClipboard() {
if (ui::Clipboard::IsSupportedClipboardBuffer(
ui::ClipboardBuffer::kSelection)) {
if (!GetObscured()) {
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kSelection)
.WriteText(GetSelectedText());
}
}
}
bool Label::IsCommandIdChecked(int command_id) const {
return true;
}
bool Label::IsCommandIdEnabled(int command_id) const {
switch (command_id) {
case MenuCommands::kCopy:
return HasSelection() && !GetObscured();
case MenuCommands::kSelectAll:
return GetRenderTextForSelectionController() && !GetText().empty();
}
return false;
}
void Label::ExecuteCommand(int command_id, int event_flags) {
switch (command_id) {
case MenuCommands::kCopy:
CopyToClipboard();
break;
case MenuCommands::kSelectAll:
SelectAll();
DCHECK(HasSelection());
UpdateSelectionClipboard();
break;
default:
NOTREACHED_NORETURN();
}
}
bool Label::GetAcceleratorForCommandId(int command_id,
ui::Accelerator* accelerator) const {
switch (command_id) {
case MenuCommands::kCopy:
*accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN);
return true;
case MenuCommands::kSelectAll:
*accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN);
return true;
default:
return false;
}
}
const gfx::RenderText* Label::GetRenderTextForSelectionController() const {
if (!GetSelectable())
return nullptr;
MaybeBuildDisplayText();
return display_text_.get();
}
void Label::Init(const std::u16string& text,
const gfx::FontList& font_list,
gfx::DirectionalityMode directionality_mode) {
full_text_ = gfx::RenderText::CreateRenderText();
full_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
full_text_->SetFontList(font_list);
full_text_->SetCursorEnabled(false);
full_text_->SetWordWrapBehavior(gfx::TRUNCATE_LONG_WORDS);
full_text_->SetMinLineHeight(GetLineHeight());
UpdateFullTextElideBehavior();
full_text_->SetDirectionalityMode(directionality_mode);
SetAccessibilityProperties(text_context_ == style::CONTEXT_DIALOG_TITLE
? ax::mojom::Role::kTitleBar
: ax::mojom::Role::kStaticText,
text);
SetText(text);
BuildContextMenuContents();
set_context_menu_controller(this);
AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN));
}
void Label::MaybeBuildDisplayText() const {
if (display_text_)
return;
gfx::Rect rect = GetContentsBounds();
if (rect.IsEmpty())
return;
rect.Inset(-gfx::ShadowValue::GetMargin(GetShadows()));
display_text_ = CreateRenderText();
display_text_->SetDisplayRect(rect);
stored_selection_range_ = gfx::Range::InvalidRange();
ApplyTextColors();
}
gfx::Size Label::GetTextSize() const {
return GetBoundedTextSize({width(), {}});
}
gfx::Size Label::GetBoundedTextSize(const SizeBounds& available_size) const {
gfx::Size size;
if (GetText().empty()) {
size = gfx::Size(0, GetLineHeight());
} else if (max_width_single_line_ > 0) {
DCHECK(!GetMultiLine());
full_text_->SetDisplayRect(
gfx::Rect(0, 0, max_width_single_line_ - GetInsets().width(), 0));
size = full_text_->GetStringSize();
} else {
const int width = available_size.width().is_bounded()
? available_size.width().value()
: 0;
full_text_->SetDisplayRect(gfx::Rect(0, 0, width, 0));
size = full_text_->GetStringSize();
}
const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(GetShadows());
size.Enlarge(shadow_margin.width(), shadow_margin.height());
return size;
}
SkColor Label::GetForegroundColor(SkColor foreground,
SkColor background) const {
return (auto_color_readability_enabled_ && IsOpaque(background))
? color_utils::BlendForMinContrast(foreground, background).color
: foreground;
}
void Label::RecalculateColors() {
actual_enabled_color_ =
GetForegroundColor(requested_enabled_color_, background_color_);
actual_selection_text_color_ =
GetForegroundColor(requested_selection_text_color_,
color_utils::GetResultingPaintColor(
selection_background_color_, background_color_));
ApplyTextColors();
SchedulePaint();
}
void Label::ApplyTextColors() const {
if (!display_text_)
return;
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_);
const bool subpixel_rendering_enabled =
subpixel_rendering_enabled_ && IsOpaque(background_color_);
display_text_->set_subpixel_rendering_suppressed(!subpixel_rendering_enabled);
}
void Label::UpdateColorsFromTheme() {
ui::ColorProvider* color_provider = GetColorProvider();
if (enabled_color_id_.has_value()) {
requested_enabled_color_ = color_provider->GetColor(*enabled_color_id_);
} else if (!enabled_color_set_) {
const absl::optional<SkColor> cascading_color =
GetCascadingProperty(this, kCascadingLabelEnabledColor);
requested_enabled_color_ =
cascading_color.value_or(GetColorProvider()->GetColor(
style::GetColorId(text_context_, text_style_)));
}
if (background_color_id_.has_value()) {
background_color_ = color_provider->GetColor(*background_color_id_);
} else if (!background_color_set_) {
background_color_ = color_provider->GetColor(ui::kColorDialogBackground);
}
if (!selection_text_color_set_) {
requested_selection_text_color_ =
color_provider->GetColor(ui::kColorLabelSelectionForeground);
}
if (!selection_background_color_set_) {
selection_background_color_ =
color_provider->GetColor(ui::kColorLabelSelectionBackground);
}
RecalculateColors();
}
bool Label::ShouldShowDefaultTooltip() const {
const gfx::Size text_size = GetTextSize();
const gfx::Size size = GetContentsBounds().size();
return !GetObscured() &&
(text_size.width() > size.width() ||
(GetMultiLine() && text_size.height() > size.height()));
}
void Label::ClearDisplayText() {
if (!display_text_)
return;
if (HasSelection()) {
stored_selection_range_ =
GetRenderTextForSelectionController()->selection();
}
display_text_ = nullptr;
SchedulePaint();
}
std::u16string Label::GetSelectedText() const {
const gfx::RenderText* render_text = GetRenderTextForSelectionController();
return render_text ? render_text->GetTextFromRange(render_text->selection())
: std::u16string();
}
void Label::CopyToClipboard() {
if (!HasSelection() || GetObscured())
return;
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(GetSelectedText());
}
void Label::BuildContextMenuContents() {
context_menu_contents_.AddItemWithStringId(MenuCommands::kCopy, IDS_APP_COPY);
context_menu_contents_.AddItemWithStringId(MenuCommands::kSelectAll,
IDS_APP_SELECT_ALL);
}
void Label::UpdateFullTextElideBehavior() {
full_text_->SetElideBehavior(max_width_single_line_ > 0 ? elide_behavior_
: gfx::NO_ELIDE);
}
BEGIN_METADATA(Label, View)
ADD_PROPERTY_METADATA(std::u16string, Text)
ADD_PROPERTY_METADATA(int, TextContext)
ADD_PROPERTY_METADATA(int, TextStyle)
ADD_PROPERTY_METADATA(bool, AutoColorReadabilityEnabled)
ADD_PROPERTY_METADATA(SkColor, EnabledColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(gfx::ElideBehavior, ElideBehavior)
ADD_PROPERTY_METADATA(SkColor, BackgroundColor, ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor,
SelectionTextColor,
ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(SkColor,
SelectionBackgroundColor,
ui::metadata::SkColorConverter)
ADD_PROPERTY_METADATA(bool, SubpixelRenderingEnabled)
ADD_PROPERTY_METADATA(bool, SkipSubpixelRenderingOpacityCheck)
ADD_PROPERTY_METADATA(gfx::ShadowValues, Shadows)
ADD_PROPERTY_METADATA(gfx::HorizontalAlignment, HorizontalAlignment)
ADD_PROPERTY_METADATA(gfx::VerticalAlignment, VerticalAlignment)
ADD_PROPERTY_METADATA(int, LineHeight)
ADD_PROPERTY_METADATA(bool, MultiLine)
ADD_PROPERTY_METADATA(size_t, MaxLines)
ADD_PROPERTY_METADATA(bool, Obscured)
ADD_PROPERTY_METADATA(bool, AllowCharacterBreak)
ADD_PROPERTY_METADATA(std::u16string, TooltipText)
ADD_PROPERTY_METADATA(bool, HandlesTooltips)
ADD_PROPERTY_METADATA(bool, CollapseWhenHidden)
ADD_PROPERTY_METADATA(int, MaximumWidth)
END_METADATA
}