ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / ui / views / controls / button / menu_button.cc
blob072e1671c4ef2c5dd946a62d54883e86aefe2f3f
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.
5 #include "ui/views/controls/button/menu_button.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "ui/accessibility/ax_view_state.h"
9 #include "ui/base/dragdrop/drag_drop_types.h"
10 #include "ui/base/l10n/l10n_util.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/base/ui_base_switches_util.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_constants.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/image/image.h"
17 #include "ui/gfx/screen.h"
18 #include "ui/gfx/text_constants.h"
19 #include "ui/resources/grit/ui_resources.h"
20 #include "ui/strings/grit/ui_strings.h"
21 #include "ui/views/controls/button/button.h"
22 #include "ui/views/controls/button/menu_button_listener.h"
23 #include "ui/views/mouse_constants.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
27 using base::TimeTicks;
28 using base::TimeDelta;
30 namespace views {
32 // Default menu offset.
33 static const int kDefaultMenuOffsetX = -2;
34 static const int kDefaultMenuOffsetY = -4;
36 // static
37 const char MenuButton::kViewClassName[] = "MenuButton";
38 const int MenuButton::kMenuMarkerPaddingLeft = 3;
39 const int MenuButton::kMenuMarkerPaddingRight = -1;
41 ////////////////////////////////////////////////////////////////////////////////
43 // MenuButton::PressedLock
45 ////////////////////////////////////////////////////////////////////////////////
47 MenuButton::PressedLock::PressedLock(MenuButton* menu_button)
48 : menu_button_(menu_button->weak_factory_.GetWeakPtr()) {
49 menu_button_->IncrementPressedLocked();
52 MenuButton::PressedLock::~PressedLock() {
53 if (menu_button_.get())
54 menu_button_->DecrementPressedLocked();
57 ////////////////////////////////////////////////////////////////////////////////
59 // MenuButton - constructors, destructors, initialization
61 ////////////////////////////////////////////////////////////////////////////////
63 MenuButton::MenuButton(ButtonListener* listener,
64 const base::string16& text,
65 MenuButtonListener* menu_button_listener,
66 bool show_menu_marker)
67 : LabelButton(listener, text),
68 menu_offset_(kDefaultMenuOffsetX, kDefaultMenuOffsetY),
69 listener_(menu_button_listener),
70 show_menu_marker_(show_menu_marker),
71 menu_marker_(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
72 IDR_MENU_DROPARROW).ToImageSkia()),
73 destroyed_flag_(NULL),
74 pressed_lock_count_(0),
75 should_disable_after_press_(false),
76 weak_factory_(this) {
77 SetHorizontalAlignment(gfx::ALIGN_LEFT);
80 MenuButton::~MenuButton() {
81 if (destroyed_flag_)
82 *destroyed_flag_ = true;
85 ////////////////////////////////////////////////////////////////////////////////
87 // MenuButton - Public APIs
89 ////////////////////////////////////////////////////////////////////////////////
91 bool MenuButton::Activate() {
92 SetState(STATE_PRESSED);
93 if (listener_) {
94 gfx::Rect lb = GetLocalBounds();
96 // The position of the menu depends on whether or not the locale is
97 // right-to-left.
98 gfx::Point menu_position(lb.right(), lb.bottom());
99 if (base::i18n::IsRTL())
100 menu_position.set_x(lb.x());
102 View::ConvertPointToScreen(this, &menu_position);
103 if (base::i18n::IsRTL())
104 menu_position.Offset(-menu_offset_.x(), menu_offset_.y());
105 else
106 menu_position.Offset(menu_offset_.x(), menu_offset_.y());
108 int max_x_coordinate = GetMaximumScreenXCoordinate();
109 if (max_x_coordinate && max_x_coordinate <= menu_position.x())
110 menu_position.set_x(max_x_coordinate - 1);
112 // We're about to show the menu from a mouse press. By showing from the
113 // mouse press event we block RootView in mouse dispatching. This also
114 // appears to cause RootView to get a mouse pressed BEFORE the mouse
115 // release is seen, which means RootView sends us another mouse press no
116 // matter where the user pressed. To force RootView to recalculate the
117 // mouse target during the mouse press we explicitly set the mouse handler
118 // to NULL.
119 static_cast<internal::RootView*>(GetWidget()->GetRootView())->
120 SetMouseHandler(NULL);
122 bool destroyed = false;
123 destroyed_flag_ = &destroyed;
125 // We don't set our state here. It's handled in the MenuController code or
126 // by our click listener.
128 listener_->OnMenuButtonClicked(this, menu_position);
130 if (destroyed) {
131 // The menu was deleted while showing. Don't attempt any processing.
132 return false;
135 destroyed_flag_ = NULL;
137 menu_closed_time_ = TimeTicks::Now();
139 // We must return false here so that the RootView does not get stuck
140 // sending all mouse pressed events to us instead of the appropriate
141 // target.
142 return false;
144 return true;
147 void MenuButton::OnPaint(gfx::Canvas* canvas) {
148 LabelButton::OnPaint(canvas);
150 if (show_menu_marker_)
151 PaintMenuMarker(canvas);
154 ////////////////////////////////////////////////////////////////////////////////
156 // MenuButton - Events
158 ////////////////////////////////////////////////////////////////////////////////
160 gfx::Size MenuButton::GetPreferredSize() const {
161 gfx::Size prefsize = LabelButton::GetPreferredSize();
162 if (show_menu_marker_) {
163 prefsize.Enlarge(menu_marker_->width() + kMenuMarkerPaddingLeft +
164 kMenuMarkerPaddingRight,
167 return prefsize;
170 const char* MenuButton::GetClassName() const {
171 return kViewClassName;
174 bool MenuButton::OnMousePressed(const ui::MouseEvent& event) {
175 RequestFocus();
176 if (state() != STATE_DISABLED && ShouldEnterPushedState(event) &&
177 HitTestPoint(event.location())) {
178 TimeDelta delta = TimeTicks::Now() - menu_closed_time_;
179 if (delta.InMilliseconds() > kMinimumMsBetweenButtonClicks)
180 return Activate();
182 return true;
185 void MenuButton::OnMouseReleased(const ui::MouseEvent& event) {
186 if (state() != STATE_DISABLED && ShouldEnterPushedState(event) &&
187 HitTestPoint(event.location()) && !InDrag()) {
188 Activate();
189 } else {
190 LabelButton::OnMouseReleased(event);
194 void MenuButton::OnMouseEntered(const ui::MouseEvent& event) {
195 if (pressed_lock_count_ == 0) // Ignore mouse movement if state is locked.
196 LabelButton::OnMouseEntered(event);
199 void MenuButton::OnMouseExited(const ui::MouseEvent& event) {
200 if (pressed_lock_count_ == 0) // Ignore mouse movement if state is locked.
201 LabelButton::OnMouseExited(event);
204 void MenuButton::OnMouseMoved(const ui::MouseEvent& event) {
205 if (pressed_lock_count_ == 0) // Ignore mouse movement if state is locked.
206 LabelButton::OnMouseMoved(event);
209 void MenuButton::OnGestureEvent(ui::GestureEvent* event) {
210 if (state() != STATE_DISABLED) {
211 if (ShouldEnterPushedState(*event) && !Activate()) {
212 // When |Activate()| returns |false|, it means that a menu is shown and
213 // has handled the gesture event. So, there is no need to further process
214 // the gesture event here.
215 return;
217 if (switches::IsTouchFeedbackEnabled()) {
218 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
219 event->SetHandled();
220 SetState(Button::STATE_HOVERED);
221 } else if (state() == Button::STATE_HOVERED &&
222 (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
223 event->type() == ui::ET_GESTURE_END)) {
224 SetState(Button::STATE_NORMAL);
228 LabelButton::OnGestureEvent(event);
231 bool MenuButton::OnKeyPressed(const ui::KeyEvent& event) {
232 switch (event.key_code()) {
233 case ui::VKEY_SPACE:
234 // Alt-space on windows should show the window menu.
235 if (event.IsAltDown())
236 break;
237 case ui::VKEY_RETURN:
238 case ui::VKEY_UP:
239 case ui::VKEY_DOWN: {
240 // WARNING: we may have been deleted by the time Activate returns.
241 Activate();
242 // This is to prevent the keyboard event from being dispatched twice. If
243 // the keyboard event is not handled, we pass it to the default handler
244 // which dispatches the event back to us causing the menu to get displayed
245 // again. Return true to prevent this.
246 return true;
248 default:
249 break;
251 return false;
254 bool MenuButton::OnKeyReleased(const ui::KeyEvent& event) {
255 // Override CustomButton's implementation, which presses the button when
256 // you press space and clicks it when you release space. For a MenuButton
257 // we always activate the menu on key press.
258 return false;
261 void MenuButton::GetAccessibleState(ui::AXViewState* state) {
262 CustomButton::GetAccessibleState(state);
263 state->role = ui::AX_ROLE_POP_UP_BUTTON;
264 state->default_action = l10n_util::GetStringUTF16(IDS_APP_ACCACTION_PRESS);
265 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
268 void MenuButton::PaintMenuMarker(gfx::Canvas* canvas) {
269 gfx::Insets insets = GetInsets();
271 // Using the Views mirroring infrastructure incorrectly flips icon content.
272 // Instead, manually mirror the position of the down arrow.
273 gfx::Rect arrow_bounds(width() - insets.right() -
274 menu_marker_->width() - kMenuMarkerPaddingRight,
275 height() / 2 - menu_marker_->height() / 2,
276 menu_marker_->width(),
277 menu_marker_->height());
278 arrow_bounds.set_x(GetMirroredXForRect(arrow_bounds));
279 canvas->DrawImageInt(*menu_marker_, arrow_bounds.x(), arrow_bounds.y());
282 gfx::Rect MenuButton::GetChildAreaBounds() {
283 gfx::Size s = size();
285 if (show_menu_marker_) {
286 s.set_width(s.width() - menu_marker_->width() - kMenuMarkerPaddingLeft -
287 kMenuMarkerPaddingRight);
290 return gfx::Rect(s);
293 bool MenuButton::ShouldEnterPushedState(const ui::Event& event) {
294 if (event.IsMouseEvent()) {
295 const ui::MouseEvent& mouseev = static_cast<const ui::MouseEvent&>(event);
296 // Active on left mouse button only, to prevent a menu from being activated
297 // when a right-click would also activate a context menu.
298 if (!mouseev.IsOnlyLeftMouseButton())
299 return false;
300 // If dragging is supported activate on release, otherwise activate on
301 // pressed.
302 ui::EventType active_on =
303 GetDragOperations(mouseev.location()) == ui::DragDropTypes::DRAG_NONE
304 ? ui::ET_MOUSE_PRESSED
305 : ui::ET_MOUSE_RELEASED;
306 return event.type() == active_on;
309 return event.type() == ui::ET_GESTURE_TAP;
312 void MenuButton::StateChanged() {
313 if (pressed_lock_count_ != 0) {
314 // The button's state was changed while it was supposed to be locked in a
315 // pressed state. This shouldn't happen, but conceivably could if a caller
316 // tries to switch from enabled to disabled or vice versa while the button
317 // is pressed.
318 if (state() == STATE_NORMAL)
319 should_disable_after_press_ = false;
320 else if (state() == STATE_DISABLED)
321 should_disable_after_press_ = true;
325 void MenuButton::IncrementPressedLocked() {
326 ++pressed_lock_count_;
327 should_disable_after_press_ = state() == STATE_DISABLED;
328 SetState(STATE_PRESSED);
331 void MenuButton::DecrementPressedLocked() {
332 --pressed_lock_count_;
333 DCHECK_GE(pressed_lock_count_, 0);
335 // If this was the last lock, manually reset state to the desired state.
336 if (pressed_lock_count_ == 0) {
337 ButtonState desired_state = STATE_NORMAL;
338 if (should_disable_after_press_) {
339 desired_state = STATE_DISABLED;
340 should_disable_after_press_ = false;
341 } else if (IsMouseHovered()) {
342 desired_state = STATE_HOVERED;
344 SetState(desired_state);
348 int MenuButton::GetMaximumScreenXCoordinate() {
349 if (!GetWidget()) {
350 NOTREACHED();
351 return 0;
354 gfx::Rect monitor_bounds = GetWidget()->GetWorkAreaBoundsInScreen();
355 return monitor_bounds.right() - 1;
358 } // namespace views