Demonstrate the basic functionality of the File System
[chromium-blink-merge.git] / ui / views / controls / button / menu_button.cc
blob29271cea51f3d5d9f9ac96ae61759a2452267a07
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 "grit/ui_resources.h"
9 #include "grit/ui_strings.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/base/dragdrop/drag_drop_types.h"
12 #include "ui/base/l10n/l10n_util.h"
13 #include "ui/base/resource/resource_bundle.h"
14 #include "ui/events/event.h"
15 #include "ui/events/event_constants.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/image/image.h"
18 #include "ui/gfx/screen.h"
19 #include "ui/gfx/text_constants.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"
26 using base::TimeTicks;
27 using base::TimeDelta;
29 namespace views {
31 // Default menu offset.
32 static const int kDefaultMenuOffsetX = -2;
33 static const int kDefaultMenuOffsetY = -4;
35 // static
36 const char MenuButton::kViewClassName[] = "MenuButton";
37 const int MenuButton::kMenuMarkerPaddingLeft = 3;
38 const int MenuButton::kMenuMarkerPaddingRight = -1;
40 ////////////////////////////////////////////////////////////////////////////////
42 // MenuButton - constructors, destructors, initialization
44 ////////////////////////////////////////////////////////////////////////////////
46 MenuButton::MenuButton(ButtonListener* listener,
47 const base::string16& text,
48 MenuButtonListener* menu_button_listener,
49 bool show_menu_marker)
50 : LabelButton(listener, text),
51 menu_visible_(false),
52 menu_offset_(kDefaultMenuOffsetX, kDefaultMenuOffsetY),
53 listener_(menu_button_listener),
54 show_menu_marker_(show_menu_marker),
55 menu_marker_(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
56 IDR_MENU_DROPARROW).ToImageSkia()),
57 destroyed_flag_(NULL) {
58 SetHorizontalAlignment(gfx::ALIGN_LEFT);
61 MenuButton::~MenuButton() {
62 if (destroyed_flag_)
63 *destroyed_flag_ = true;
66 ////////////////////////////////////////////////////////////////////////////////
68 // MenuButton - Public APIs
70 ////////////////////////////////////////////////////////////////////////////////
72 bool MenuButton::Activate() {
73 SetState(STATE_PRESSED);
74 if (listener_) {
75 gfx::Rect lb = GetLocalBounds();
77 // The position of the menu depends on whether or not the locale is
78 // right-to-left.
79 gfx::Point menu_position(lb.right(), lb.bottom());
80 if (base::i18n::IsRTL())
81 menu_position.set_x(lb.x());
83 View::ConvertPointToScreen(this, &menu_position);
84 if (base::i18n::IsRTL())
85 menu_position.Offset(-menu_offset_.x(), menu_offset_.y());
86 else
87 menu_position.Offset(menu_offset_.x(), menu_offset_.y());
89 int max_x_coordinate = GetMaximumScreenXCoordinate();
90 if (max_x_coordinate && max_x_coordinate <= menu_position.x())
91 menu_position.set_x(max_x_coordinate - 1);
93 // We're about to show the menu from a mouse press. By showing from the
94 // mouse press event we block RootView in mouse dispatching. This also
95 // appears to cause RootView to get a mouse pressed BEFORE the mouse
96 // release is seen, which means RootView sends us another mouse press no
97 // matter where the user pressed. To force RootView to recalculate the
98 // mouse target during the mouse press we explicitly set the mouse handler
99 // to NULL.
100 static_cast<internal::RootView*>(GetWidget()->GetRootView())->
101 SetMouseHandler(NULL);
103 menu_visible_ = true;
105 bool destroyed = false;
106 destroyed_flag_ = &destroyed;
108 listener_->OnMenuButtonClicked(this, menu_position);
110 if (destroyed) {
111 // The menu was deleted while showing. Don't attempt any processing.
112 return false;
115 destroyed_flag_ = NULL;
117 menu_visible_ = false;
118 menu_closed_time_ = TimeTicks::Now();
120 // Now that the menu has closed, we need to manually reset state to
121 // "normal" since the menu modal loop will have prevented normal
122 // mouse move messages from getting to this View. We set "normal"
123 // and not "hot" because the likelihood is that the mouse is now
124 // somewhere else (user clicked elsewhere on screen to close the menu
125 // or selected an item) and we will inevitably refresh the hot state
126 // in the event the mouse _is_ over the view.
127 SetState(STATE_NORMAL);
129 // We must return false here so that the RootView does not get stuck
130 // sending all mouse pressed events to us instead of the appropriate
131 // target.
132 return false;
134 return true;
137 void MenuButton::OnPaint(gfx::Canvas* canvas) {
138 LabelButton::OnPaint(canvas);
140 if (show_menu_marker_)
141 PaintMenuMarker(canvas);
144 ////////////////////////////////////////////////////////////////////////////////
146 // MenuButton - Events
148 ////////////////////////////////////////////////////////////////////////////////
150 gfx::Size MenuButton::GetPreferredSize() const {
151 gfx::Size prefsize = LabelButton::GetPreferredSize();
152 if (show_menu_marker_) {
153 prefsize.Enlarge(menu_marker_->width() + kMenuMarkerPaddingLeft +
154 kMenuMarkerPaddingRight,
157 return prefsize;
160 const char* MenuButton::GetClassName() const {
161 return kViewClassName;
164 bool MenuButton::OnMousePressed(const ui::MouseEvent& event) {
165 RequestFocus();
166 if (state() != STATE_DISABLED) {
167 // If we're draggable (GetDragOperations returns a non-zero value), then
168 // don't pop on press, instead wait for release.
169 if (event.IsOnlyLeftMouseButton() &&
170 HitTestPoint(event.location()) &&
171 GetDragOperations(event.location()) == ui::DragDropTypes::DRAG_NONE) {
172 TimeDelta delta = TimeTicks::Now() - menu_closed_time_;
173 if (delta.InMilliseconds() > kMinimumMsBetweenButtonClicks)
174 return Activate();
177 return true;
180 void MenuButton::OnMouseReleased(const ui::MouseEvent& event) {
181 // Explicitly test for left mouse button to show the menu. If we tested for
182 // !IsTriggerableEvent it could lead to a situation where we end up showing
183 // the menu and context menu (this would happen if the right button is not
184 // triggerable and there's a context menu).
185 if (GetDragOperations(event.location()) != ui::DragDropTypes::DRAG_NONE &&
186 state() != STATE_DISABLED && !InDrag() && event.IsOnlyLeftMouseButton() &&
187 HitTestPoint(event.location())) {
188 Activate();
189 } else {
190 LabelButton::OnMouseReleased(event);
194 // The reason we override View::OnMouseExited is because we get this event when
195 // we display the menu. If we don't override this method then
196 // BaseButton::OnMouseExited will get the event and will set the button's state
197 // to STATE_NORMAL instead of keeping the state BM_PUSHED. This, in turn, will
198 // cause the button to appear depressed while the menu is displayed.
199 void MenuButton::OnMouseExited(const ui::MouseEvent& event) {
200 if ((state_ != STATE_DISABLED) && (!menu_visible_) && (!InDrag())) {
201 SetState(STATE_NORMAL);
205 void MenuButton::OnGestureEvent(ui::GestureEvent* event) {
206 if (state() != STATE_DISABLED && event->type() == ui::ET_GESTURE_TAP &&
207 !Activate()) {
208 // When |Activate()| returns |false|, it means that a menu is shown and
209 // has handled the gesture event. So, there is no need to further process
210 // the gesture event here.
211 return;
213 LabelButton::OnGestureEvent(event);
216 bool MenuButton::OnKeyPressed(const ui::KeyEvent& event) {
217 switch (event.key_code()) {
218 case ui::VKEY_SPACE:
219 // Alt-space on windows should show the window menu.
220 if (event.IsAltDown())
221 break;
222 case ui::VKEY_RETURN:
223 case ui::VKEY_UP:
224 case ui::VKEY_DOWN: {
225 // WARNING: we may have been deleted by the time Activate returns.
226 Activate();
227 // This is to prevent the keyboard event from being dispatched twice. If
228 // the keyboard event is not handled, we pass it to the default handler
229 // which dispatches the event back to us causing the menu to get displayed
230 // again. Return true to prevent this.
231 return true;
233 default:
234 break;
236 return false;
239 bool MenuButton::OnKeyReleased(const ui::KeyEvent& event) {
240 // Override CustomButton's implementation, which presses the button when
241 // you press space and clicks it when you release space. For a MenuButton
242 // we always activate the menu on key press.
243 return false;
246 void MenuButton::GetAccessibleState(ui::AXViewState* state) {
247 CustomButton::GetAccessibleState(state);
248 state->role = ui::AX_ROLE_POP_UP_BUTTON;
249 state->default_action = l10n_util::GetStringUTF16(IDS_APP_ACCACTION_PRESS);
250 state->AddStateFlag(ui::AX_STATE_HASPOPUP);
253 void MenuButton::PaintMenuMarker(gfx::Canvas* canvas) {
254 gfx::Insets insets = GetInsets();
256 // We can not use the views' mirroring infrastructure for mirroring a
257 // MenuButton control (see TextButton::OnPaint() for a detailed explanation
258 // regarding why we can not flip the canvas). Therefore, we need to
259 // manually mirror the position of the down arrow.
260 gfx::Rect arrow_bounds(width() - insets.right() -
261 menu_marker_->width() - kMenuMarkerPaddingRight,
262 height() / 2 - menu_marker_->height() / 2,
263 menu_marker_->width(),
264 menu_marker_->height());
265 arrow_bounds.set_x(GetMirroredXForRect(arrow_bounds));
266 canvas->DrawImageInt(*menu_marker_, arrow_bounds.x(), arrow_bounds.y());
269 gfx::Rect MenuButton::GetChildAreaBounds() {
270 gfx::Size s = size();
272 if (show_menu_marker_) {
273 s.set_width(s.width() - menu_marker_->width() - kMenuMarkerPaddingLeft -
274 kMenuMarkerPaddingRight);
277 return gfx::Rect(s);
280 int MenuButton::GetMaximumScreenXCoordinate() {
281 if (!GetWidget()) {
282 NOTREACHED();
283 return 0;
286 gfx::Rect monitor_bounds = GetWidget()->GetWorkAreaBoundsInScreen();
287 return monitor_bounds.right() - 1;
290 } // namespace views