Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / button / menu_button.cc
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/button/menu_button.h"
6
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/events/event.h"
13 #include "ui/events/event_constants.h"
14 #include "ui/gfx/canvas.h"
15 #include "ui/gfx/image/image.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/gfx/text_constants.h"
18 #include "ui/resources/grit/ui_resources.h"
19 #include "ui/strings/grit/ui_strings.h"
20 #include "ui/views/controls/button/button.h"
21 #include "ui/views/controls/button/menu_button_listener.h"
22 #include "ui/views/mouse_constants.h"
23 #include "ui/views/widget/root_view.h"
24 #include "ui/views/widget/widget.h"
25
26 using base::TimeTicks;
27 using base::TimeDelta;
28
29 namespace views {
30
31 // Default menu offset.
32 static const int kDefaultMenuOffsetX = -2;
33 static const int kDefaultMenuOffsetY = -4;
34
35 // static
36 const char MenuButton::kViewClassName[] = "MenuButton";
37 const int MenuButton::kMenuMarkerPaddingLeft = 3;
38 const int MenuButton::kMenuMarkerPaddingRight = -1;
39
40 ////////////////////////////////////////////////////////////////////////////////
41 //
42 // MenuButton::PressedLock
43 //
44 ////////////////////////////////////////////////////////////////////////////////
45
46 MenuButton::PressedLock::PressedLock(MenuButton* menu_button)
47     : menu_button_(menu_button->weak_factory_.GetWeakPtr()) {
48   menu_button_->IncrementPressedLocked();
49 }
50
51 MenuButton::PressedLock::~PressedLock() {
52   if (menu_button_.get())
53     menu_button_->DecrementPressedLocked();
54 }
55
56 ////////////////////////////////////////////////////////////////////////////////
57 //
58 // MenuButton - constructors, destructors, initialization
59 //
60 ////////////////////////////////////////////////////////////////////////////////
61
62 MenuButton::MenuButton(ButtonListener* listener,
63                        const base::string16& text,
64                        MenuButtonListener* menu_button_listener,
65                        bool show_menu_marker)
66     : LabelButton(listener, text),
67       menu_offset_(kDefaultMenuOffsetX, kDefaultMenuOffsetY),
68       listener_(menu_button_listener),
69       show_menu_marker_(show_menu_marker),
70       menu_marker_(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
71           IDR_MENU_DROPARROW).ToImageSkia()),
72       destroyed_flag_(NULL),
73       pressed_lock_count_(0),
74       weak_factory_(this) {
75   SetHorizontalAlignment(gfx::ALIGN_LEFT);
76 }
77
78 MenuButton::~MenuButton() {
79   if (destroyed_flag_)
80     *destroyed_flag_ = true;
81 }
82
83 ////////////////////////////////////////////////////////////////////////////////
84 //
85 // MenuButton - Public APIs
86 //
87 ////////////////////////////////////////////////////////////////////////////////
88
89 bool MenuButton::Activate() {
90   SetState(STATE_PRESSED);
91   if (listener_) {
92     gfx::Rect lb = GetLocalBounds();
93
94     // The position of the menu depends on whether or not the locale is
95     // right-to-left.
96     gfx::Point menu_position(lb.right(), lb.bottom());
97     if (base::i18n::IsRTL())
98       menu_position.set_x(lb.x());
99
100     View::ConvertPointToScreen(this, &menu_position);
101     if (base::i18n::IsRTL())
102       menu_position.Offset(-menu_offset_.x(), menu_offset_.y());
103     else
104       menu_position.Offset(menu_offset_.x(), menu_offset_.y());
105
106     int max_x_coordinate = GetMaximumScreenXCoordinate();
107     if (max_x_coordinate && max_x_coordinate <= menu_position.x())
108       menu_position.set_x(max_x_coordinate - 1);
109
110     // We're about to show the menu from a mouse press. By showing from the
111     // mouse press event we block RootView in mouse dispatching. This also
112     // appears to cause RootView to get a mouse pressed BEFORE the mouse
113     // release is seen, which means RootView sends us another mouse press no
114     // matter where the user pressed. To force RootView to recalculate the
115     // mouse target during the mouse press we explicitly set the mouse handler
116     // to NULL.
117     static_cast<internal::RootView*>(GetWidget()->GetRootView())->
118         SetMouseHandler(NULL);
119
120     bool destroyed = false;
121     destroyed_flag_ = &destroyed;
122
123     // We don't set our state here. It's handled in the MenuController code or
124     // by our click listener.
125
126     listener_->OnMenuButtonClicked(this, menu_position);
127
128     if (destroyed) {
129       // The menu was deleted while showing. Don't attempt any processing.
130       return false;
131     }
132
133     destroyed_flag_ = NULL;
134
135     menu_closed_time_ = TimeTicks::Now();
136
137     // We must return false here so that the RootView does not get stuck
138     // sending all mouse pressed events to us instead of the appropriate
139     // target.
140     return false;
141   }
142   return true;
143 }
144
145 void MenuButton::OnPaint(gfx::Canvas* canvas) {
146   LabelButton::OnPaint(canvas);
147
148   if (show_menu_marker_)
149     PaintMenuMarker(canvas);
150 }
151
152 ////////////////////////////////////////////////////////////////////////////////
153 //
154 // MenuButton - Events
155 //
156 ////////////////////////////////////////////////////////////////////////////////
157
158 gfx::Size MenuButton::GetPreferredSize() const {
159   gfx::Size prefsize = LabelButton::GetPreferredSize();
160   if (show_menu_marker_) {
161     prefsize.Enlarge(menu_marker_->width() + kMenuMarkerPaddingLeft +
162                          kMenuMarkerPaddingRight,
163                      0);
164   }
165   return prefsize;
166 }
167
168 const char* MenuButton::GetClassName() const {
169   return kViewClassName;
170 }
171
172 bool MenuButton::OnMousePressed(const ui::MouseEvent& event) {
173   RequestFocus();
174   if (state() != STATE_DISABLED) {
175     // If we're draggable (GetDragOperations returns a non-zero value), then
176     // don't pop on press, instead wait for release.
177     if (event.IsOnlyLeftMouseButton() &&
178         HitTestPoint(event.location()) &&
179         GetDragOperations(event.location()) == ui::DragDropTypes::DRAG_NONE) {
180       TimeDelta delta = TimeTicks::Now() - menu_closed_time_;
181       if (delta.InMilliseconds() > kMinimumMsBetweenButtonClicks)
182         return Activate();
183     }
184   }
185   return true;
186 }
187
188 void MenuButton::OnMouseReleased(const ui::MouseEvent& event) {
189   // Explicitly test for left mouse button to show the menu. If we tested for
190   // !IsTriggerableEvent it could lead to a situation where we end up showing
191   // the menu and context menu (this would happen if the right button is not
192   // triggerable and there's a context menu).
193   if (GetDragOperations(event.location()) != ui::DragDropTypes::DRAG_NONE &&
194       state() != STATE_DISABLED && !InDrag() && event.IsOnlyLeftMouseButton() &&
195       HitTestPoint(event.location())) {
196     Activate();
197   } else {
198     LabelButton::OnMouseReleased(event);
199   }
200 }
201
202 void MenuButton::OnMouseEntered(const ui::MouseEvent& event) {
203   if (pressed_lock_count_ == 0)  // Ignore mouse movement if state is locked.
204     CustomButton::OnMouseEntered(event);
205 }
206
207 void MenuButton::OnMouseExited(const ui::MouseEvent& event) {
208   if (pressed_lock_count_ == 0)  // Ignore mouse movement if state is locked.
209     CustomButton::OnMouseExited(event);
210 }
211
212 void MenuButton::OnMouseMoved(const ui::MouseEvent& event) {
213   if (pressed_lock_count_ == 0)  // Ignore mouse movement if state is locked.
214     CustomButton::OnMouseMoved(event);
215 }
216
217 void MenuButton::OnGestureEvent(ui::GestureEvent* event) {
218   if (state() != STATE_DISABLED && event->type() == ui::ET_GESTURE_TAP &&
219       !Activate()) {
220     // When |Activate()| returns |false|, it means that a menu is shown and
221     // has handled the gesture event. So, there is no need to further process
222     // the gesture event here.
223     return;
224   }
225   LabelButton::OnGestureEvent(event);
226 }
227
228 bool MenuButton::OnKeyPressed(const ui::KeyEvent& event) {
229   switch (event.key_code()) {
230     case ui::VKEY_SPACE:
231       // Alt-space on windows should show the window menu.
232       if (event.IsAltDown())
233         break;
234     case ui::VKEY_RETURN:
235     case ui::VKEY_UP:
236     case ui::VKEY_DOWN: {
237       // WARNING: we may have been deleted by the time Activate returns.
238       Activate();
239       // This is to prevent the keyboard event from being dispatched twice.  If
240       // the keyboard event is not handled, we pass it to the default handler
241       // which dispatches the event back to us causing the menu to get displayed
242       // again. Return true to prevent this.
243       return true;
244     }
245     default:
246       break;
247   }
248   return false;
249 }
250
251 bool MenuButton::OnKeyReleased(const ui::KeyEvent& event) {
252   // Override CustomButton's implementation, which presses the button when
253   // you press space and clicks it when you release space.  For a MenuButton
254   // we always activate the menu on key press.
255   return false;
256 }
257
258 void MenuButton::GetAccessibleState(ui::AXViewState* state) {
259   CustomButton::GetAccessibleState(state);
260   state->role = ui::AX_ROLE_POP_UP_BUTTON;
261   state->default_action = l10n_util::GetStringUTF16(IDS_APP_ACCACTION_PRESS);
262   state->AddStateFlag(ui::AX_STATE_HASPOPUP);
263 }
264
265 void MenuButton::PaintMenuMarker(gfx::Canvas* canvas) {
266   gfx::Insets insets = GetInsets();
267
268   // Using the Views mirroring infrastructure incorrectly flips icon content.
269   // Instead, manually mirror the position of the down arrow.
270   gfx::Rect arrow_bounds(width() - insets.right() -
271                          menu_marker_->width() - kMenuMarkerPaddingRight,
272                          height() / 2 - menu_marker_->height() / 2,
273                          menu_marker_->width(),
274                          menu_marker_->height());
275   arrow_bounds.set_x(GetMirroredXForRect(arrow_bounds));
276   canvas->DrawImageInt(*menu_marker_, arrow_bounds.x(), arrow_bounds.y());
277 }
278
279 gfx::Rect MenuButton::GetChildAreaBounds() {
280   gfx::Size s = size();
281
282   if (show_menu_marker_) {
283     s.set_width(s.width() - menu_marker_->width() - kMenuMarkerPaddingLeft -
284                 kMenuMarkerPaddingRight);
285   }
286
287   return gfx::Rect(s);
288 }
289
290 void MenuButton::IncrementPressedLocked() {
291   ++pressed_lock_count_;
292   SetState(STATE_PRESSED);
293 }
294
295 void MenuButton::DecrementPressedLocked() {
296   --pressed_lock_count_;
297   DCHECK_GE(pressed_lock_count_, 0);
298
299   // If this was the last lock, manually reset state to "normal". We set
300   // "normal" and not "hot" because the likelihood is that the mouse is now
301   // somewhere else (user clicked elsewhere on screen to close the menu or
302   // selected an item) and we will inevitably refresh the hot state in the event
303   // the mouse _is_ over the view.
304   if (pressed_lock_count_ == 0)
305     SetState(STATE_NORMAL);
306 }
307
308 int MenuButton::GetMaximumScreenXCoordinate() {
309   if (!GetWidget()) {
310     NOTREACHED();
311     return 0;
312   }
313
314   gfx::Rect monitor_bounds = GetWidget()->GetWorkAreaBoundsInScreen();
315   return monitor_bounds.right() - 1;
316 }
317
318 }  // namespace views