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/link.h"
6 
7 #include "build/build_config.h"
8 
9 #include "base/check.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "ui/accessibility/ax_enums.mojom.h"
12 #include "ui/accessibility/ax_node_data.h"
13 #include "ui/base/cursor/cursor.h"
14 #include "ui/events/event.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/color_utils.h"
18 #include "ui/gfx/font_list.h"
19 #include "ui/native_theme/native_theme.h"
20 #include "ui/views/metadata/metadata_impl_macros.h"
21 #include "ui/views/native_cursor.h"
22 #include "ui/views/style/platform_style.h"
23 
24 namespace views {
25 
Link(const base::string16 & title,int text_context,int text_style)26 Link::Link(const base::string16& title, int text_context, int text_style)
27     : Label(title, text_context, text_style) {
28   RecalculateFont();
29 
30   enabled_changed_subscription_ = AddEnabledChangedCallback(
31       base::BindRepeating(&Link::RecalculateFont, base::Unretained(this)));
32 
33   // Label() indirectly calls SetText(), but at that point our virtual override
34   // will not be reached.  Call it explicitly here to configure focus.
35   SetText(GetText());
36 }
37 
38 Link::~Link() = default;
39 
GetColor() const40 SkColor Link::GetColor() const {
41   // TODO(tapted): Use style::GetColor().
42   const ui::NativeTheme* theme = GetNativeTheme();
43   DCHECK(theme);
44   if (!GetEnabled())
45     return theme->GetSystemColor(ui::NativeTheme::kColorId_LinkDisabled);
46 
47   if (requested_enabled_color_.has_value())
48     return requested_enabled_color_.value();
49 
50   return GetNativeTheme()->GetSystemColor(
51       pressed_ ? ui::NativeTheme::kColorId_LinkPressed
52                : ui::NativeTheme::kColorId_LinkEnabled);
53 }
54 
SetForceUnderline(bool force_underline)55 void Link::SetForceUnderline(bool force_underline) {
56   if (force_underline_ == force_underline)
57     return;
58 
59   force_underline_ = force_underline;
60   RecalculateFont();
61 }
62 
GetCursor(const ui::MouseEvent & event)63 gfx::NativeCursor Link::GetCursor(const ui::MouseEvent& event) {
64   if (!GetEnabled())
65     return gfx::kNullCursor;
66   return GetNativeHandCursor();
67 }
68 
GetCanProcessEventsWithinSubtree() const69 bool Link::GetCanProcessEventsWithinSubtree() const {
70   // Links need to be able to accept events (e.g., clicking) even though
71   // in general Labels do not.
72   return View::GetCanProcessEventsWithinSubtree();
73 }
74 
OnMouseEntered(const ui::MouseEvent & event)75 void Link::OnMouseEntered(const ui::MouseEvent& event) {
76   RecalculateFont();
77 }
78 
OnMouseExited(const ui::MouseEvent & event)79 void Link::OnMouseExited(const ui::MouseEvent& event) {
80   RecalculateFont();
81 }
82 
OnMousePressed(const ui::MouseEvent & event)83 bool Link::OnMousePressed(const ui::MouseEvent& event) {
84   if (!GetEnabled() ||
85       (!event.IsLeftMouseButton() && !event.IsMiddleMouseButton()))
86     return false;
87   SetPressed(true);
88   return true;
89 }
90 
OnMouseDragged(const ui::MouseEvent & event)91 bool Link::OnMouseDragged(const ui::MouseEvent& event) {
92   SetPressed(GetEnabled() &&
93              (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
94              HitTestPoint(event.location()));
95   return true;
96 }
97 
OnMouseReleased(const ui::MouseEvent & event)98 void Link::OnMouseReleased(const ui::MouseEvent& event) {
99   // Change the highlight first just in case this instance is deleted
100   // while calling the controller
101   OnMouseCaptureLost();
102   if (GetEnabled() &&
103       (event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
104       HitTestPoint(event.location()))
105     OnClick(event);
106 }
107 
OnMouseCaptureLost()108 void Link::OnMouseCaptureLost() {
109   SetPressed(false);
110 }
111 
OnKeyPressed(const ui::KeyEvent & event)112 bool Link::OnKeyPressed(const ui::KeyEvent& event) {
113   bool activate = (((event.key_code() == ui::VKEY_SPACE) &&
114                     (event.flags() & ui::EF_ALT_DOWN) == 0) ||
115                    (event.key_code() == ui::VKEY_RETURN &&
116                     PlatformStyle::kReturnClicksFocusedControl));
117   if (!activate)
118     return false;
119 
120   SetPressed(false);
121   OnClick(event);
122   return true;
123 }
124 
OnGestureEvent(ui::GestureEvent * event)125 void Link::OnGestureEvent(ui::GestureEvent* event) {
126   if (!GetEnabled())
127     return;
128 
129   if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
130     SetPressed(true);
131   } else if (event->type() == ui::ET_GESTURE_TAP) {
132     OnClick(*event);
133   } else {
134     SetPressed(false);
135     return;
136   }
137   event->SetHandled();
138 }
139 
SkipDefaultKeyEventProcessing(const ui::KeyEvent & event)140 bool Link::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
141   // Don't process Space and Return (depending on the platform) as an
142   // accelerator.
143   return event.key_code() == ui::VKEY_SPACE ||
144          (event.key_code() == ui::VKEY_RETURN &&
145           PlatformStyle::kReturnClicksFocusedControl);
146 }
147 
GetAccessibleNodeData(ui::AXNodeData * node_data)148 void Link::GetAccessibleNodeData(ui::AXNodeData* node_data) {
149   Label::GetAccessibleNodeData(node_data);
150   // Prevent invisible links from being announced by screen reader.
151   node_data->role =
152       GetText().empty() ? ax::mojom::Role::kIgnored : ax::mojom::Role::kLink;
153 }
154 
OnFocus()155 void Link::OnFocus() {
156   Label::OnFocus();
157   RecalculateFont();
158   // We render differently focused.
159   SchedulePaint();
160 }
161 
OnBlur()162 void Link::OnBlur() {
163   Label::OnBlur();
164   RecalculateFont();
165   // We render differently focused.
166   SchedulePaint();
167 }
168 
SetFontList(const gfx::FontList & font_list)169 void Link::SetFontList(const gfx::FontList& font_list) {
170   Label::SetFontList(font_list);
171   RecalculateFont();
172 }
173 
SetText(const base::string16 & text)174 void Link::SetText(const base::string16& text) {
175   Label::SetText(text);
176   ConfigureFocus();
177 }
178 
OnThemeChanged()179 void Link::OnThemeChanged() {
180   Label::OnThemeChanged();
181   Label::SetEnabledColor(GetColor());
182 }
183 
SetEnabledColor(SkColor color)184 void Link::SetEnabledColor(SkColor color) {
185   requested_enabled_color_ = color;
186   Label::SetEnabledColor(GetColor());
187 }
188 
IsSelectionSupported() const189 bool Link::IsSelectionSupported() const {
190   return false;
191 }
192 
SetPressed(bool pressed)193 void Link::SetPressed(bool pressed) {
194   if (pressed_ != pressed) {
195     pressed_ = pressed;
196     Label::SetEnabledColor(GetColor());
197     RecalculateFont();
198     SchedulePaint();
199   }
200 }
201 
OnClick(const ui::Event & event)202 void Link::OnClick(const ui::Event& event) {
203   RequestFocus();
204   if (callback_)
205     callback_.Run(event);
206 }
207 
RecalculateFont()208 void Link::RecalculateFont() {
209   const int style = font_list().GetFontStyle();
210   const int intended_style =
211       ((GetEnabled() && (HasFocus() || IsMouseHovered())) || force_underline_)
212           ? (style | gfx::Font::UNDERLINE)
213           : (style & ~gfx::Font::UNDERLINE);
214 
215   if (style != intended_style)
216     Label::SetFontList(font_list().DeriveWithStyle(intended_style));
217 }
218 
ConfigureFocus()219 void Link::ConfigureFocus() {
220   // Disable focusability for empty links.
221   if (GetText().empty()) {
222     SetFocusBehavior(FocusBehavior::NEVER);
223   } else {
224 #if defined(OS_APPLE)
225     SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
226 #else
227     SetFocusBehavior(FocusBehavior::ALWAYS);
228 #endif
229   }
230 }
231 
232 BEGIN_METADATA(Link, Label)
233 ADD_READONLY_PROPERTY_METADATA(SkColor, Color)
234 END_METADATA
235 
236 }  // namespace views
237