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