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/button_dropdown.h" 6 7 #include "base/bind.h" 8 #include "base/compiler_specific.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "grit/ui_strings.h" 12 #include "ui/base/accessibility/accessible_view_state.h" 13 #include "ui/base/l10n/l10n_util.h" 14 #include "ui/base/models/menu_model.h" 15 #include "ui/gfx/display.h" 16 #include "ui/gfx/screen.h" 17 #include "ui/views/controls/menu/menu_item_view.h" 18 #include "ui/views/controls/menu/menu_model_adapter.h" 19 #include "ui/views/controls/menu/menu_runner.h" 20 #include "ui/views/widget/widget.h" 21 22 namespace views { 23 24 // static 25 const char ButtonDropDown::kViewClassName[] = 26 "ui/views/controls/button/ButtonDropDown"; 27 28 // How long to wait before showing the menu. 29 const int kMenuTimerDelay = 500; 30 31 //////////////////////////////////////////////////////////////////////////////// 32 // 33 // ButtonDropDown - constructors, destructors, initialization, cleanup 34 // 35 //////////////////////////////////////////////////////////////////////////////// 36 37 ButtonDropDown::ButtonDropDown(ButtonListener* listener, ui::MenuModel* model) 38 : ImageButton(listener), 39 model_(model), 40 menu_showing_(false), 41 y_position_on_lbuttondown_(0), 42 show_menu_factory_(this) { 43 set_context_menu_controller(this); 44 } 45 46 ButtonDropDown::~ButtonDropDown() { 47 } 48 49 void ButtonDropDown::ClearPendingMenu() { 50 show_menu_factory_.InvalidateWeakPtrs(); 51 } 52 53 bool ButtonDropDown::IsMenuShowing() const { 54 return menu_showing_; 55 } 56 57 //////////////////////////////////////////////////////////////////////////////// 58 // 59 // ButtonDropDown - Events 60 // 61 //////////////////////////////////////////////////////////////////////////////// 62 63 bool ButtonDropDown::OnMousePressed(const ui::MouseEvent& event) { 64 if (enabled() && ShouldShowMenu() && 65 IsTriggerableEvent(event) && HitTestPoint(event.location())) { 66 // Store the y pos of the mouse coordinates so we can use them later to 67 // determine if the user dragged the mouse down (which should pop up the 68 // drag down menu immediately, instead of waiting for the timer) 69 y_position_on_lbuttondown_ = event.y(); 70 71 // Schedule a task that will show the menu. 72 base::MessageLoop::current()->PostDelayedTask( 73 FROM_HERE, 74 base::Bind(&ButtonDropDown::ShowDropDownMenu, 75 show_menu_factory_.GetWeakPtr(), 76 ui::GetMenuSourceTypeForEvent(event)), 77 base::TimeDelta::FromMilliseconds(kMenuTimerDelay)); 78 } 79 return ImageButton::OnMousePressed(event); 80 } 81 82 bool ButtonDropDown::OnMouseDragged(const ui::MouseEvent& event) { 83 bool result = ImageButton::OnMouseDragged(event); 84 85 if (show_menu_factory_.HasWeakPtrs()) { 86 // If the mouse is dragged to a y position lower than where it was when 87 // clicked then we should not wait for the menu to appear but show 88 // it immediately. 89 if (event.y() > y_position_on_lbuttondown_ + GetHorizontalDragThreshold()) { 90 show_menu_factory_.InvalidateWeakPtrs(); 91 ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event)); 92 } 93 } 94 95 return result; 96 } 97 98 void ButtonDropDown::OnMouseReleased(const ui::MouseEvent& event) { 99 if (IsTriggerableEvent(event) || 100 (event.IsRightMouseButton() && !HitTestPoint(event.location()))) { 101 ImageButton::OnMouseReleased(event); 102 } 103 104 if (IsTriggerableEvent(event)) 105 show_menu_factory_.InvalidateWeakPtrs(); 106 } 107 108 const char* ButtonDropDown::GetClassName() const { 109 return kViewClassName; 110 } 111 112 void ButtonDropDown::OnMouseExited(const ui::MouseEvent& event) { 113 // Starting a drag results in a MouseExited, we need to ignore it. 114 // A right click release triggers an exit event. We want to 115 // remain in a PUSHED state until the drop down menu closes. 116 if (state_ != STATE_DISABLED && !InDrag() && state_ != STATE_PRESSED) 117 SetState(STATE_NORMAL); 118 } 119 120 void ButtonDropDown::OnGestureEvent(ui::GestureEvent* event) { 121 if (menu_showing_) { 122 // While dropdown menu is showing the button should not handle gestures. 123 event->StopPropagation(); 124 return; 125 } 126 127 ImageButton::OnGestureEvent(event); 128 } 129 130 void ButtonDropDown::GetAccessibleState(ui::AccessibleViewState* state) { 131 CustomButton::GetAccessibleState(state); 132 state->role = ui::AccessibilityTypes::ROLE_BUTTONDROPDOWN; 133 state->default_action = l10n_util::GetStringUTF16(IDS_APP_ACCACTION_PRESS); 134 state->state = ui::AccessibilityTypes::STATE_HASPOPUP; 135 } 136 137 void ButtonDropDown::ShowContextMenuForView(View* source, 138 const gfx::Point& point, 139 ui::MenuSourceType source_type) { 140 if (!enabled()) 141 return; 142 143 show_menu_factory_.InvalidateWeakPtrs(); 144 ShowDropDownMenu(source_type); 145 } 146 147 bool ButtonDropDown::ShouldEnterPushedState(const ui::Event& event) { 148 // Enter PUSHED state on press with Left or Right mouse button or on taps. 149 // Remain in this state while the context menu is open. 150 return event.type() == ui::ET_GESTURE_TAP || 151 event.type() == ui::ET_GESTURE_TAP_DOWN || 152 (event.IsMouseEvent() && ((ui::EF_LEFT_MOUSE_BUTTON | 153 ui::EF_RIGHT_MOUSE_BUTTON) & event.flags()) != 0); 154 } 155 156 bool ButtonDropDown::ShouldShowMenu() { 157 return true; 158 } 159 160 void ButtonDropDown::ShowDropDownMenu(ui::MenuSourceType source_type) { 161 if (!ShouldShowMenu()) 162 return; 163 164 gfx::Rect lb = GetLocalBounds(); 165 166 // Both the menu position and the menu anchor type change if the UI layout 167 // is right-to-left. 168 gfx::Point menu_position(lb.origin()); 169 menu_position.Offset(0, lb.height() - 1); 170 if (base::i18n::IsRTL()) 171 menu_position.Offset(lb.width() - 1, 0); 172 173 View::ConvertPointToScreen(this, &menu_position); 174 175 #if defined(OS_WIN) 176 int left_bound = GetSystemMetrics(SM_XVIRTUALSCREEN); 177 #elif defined(OS_CHROMEOS) 178 // A window won't overlap between displays on ChromeOS. 179 // Use the left bound of the display on which 180 // the menu button exists. 181 gfx::NativeView view = GetWidget()->GetNativeView(); 182 gfx::Display display = gfx::Screen::GetScreenFor( 183 view)->GetDisplayNearestWindow(view); 184 int left_bound = display.bounds().x(); 185 #else 186 int left_bound = 0; 187 NOTIMPLEMENTED(); 188 #endif 189 if (menu_position.x() < left_bound) 190 menu_position.set_x(left_bound); 191 192 // Make the button look depressed while the menu is open. 193 SetState(STATE_PRESSED); 194 195 menu_showing_ = true; 196 197 // Create and run menu. Display an empty menu if model is NULL. 198 if (model_.get()) { 199 MenuModelAdapter menu_delegate(model_.get()); 200 menu_delegate.set_triggerable_event_flags(triggerable_event_flags()); 201 menu_runner_.reset(new MenuRunner(menu_delegate.CreateMenu())); 202 MenuRunner::RunResult result = 203 menu_runner_->RunMenuAt(GetWidget(), NULL, 204 gfx::Rect(menu_position, gfx::Size(0, 0)), 205 MenuItemView::TOPLEFT, 206 source_type, 207 MenuRunner::HAS_MNEMONICS); 208 if (result == MenuRunner::MENU_DELETED) 209 return; 210 } else { 211 MenuDelegate menu_delegate; 212 MenuItemView* menu = new MenuItemView(&menu_delegate); 213 menu_runner_.reset(new MenuRunner(menu)); 214 MenuRunner::RunResult result = 215 menu_runner_->RunMenuAt(GetWidget(), NULL, 216 gfx::Rect(menu_position, gfx::Size(0, 0)), 217 MenuItemView::TOPLEFT, 218 source_type, 219 MenuRunner::HAS_MNEMONICS); 220 if (result == MenuRunner::MENU_DELETED) 221 return; 222 } 223 224 menu_showing_ = false; 225 226 // Need to explicitly clear mouse handler so that events get sent 227 // properly after the menu finishes running. If we don't do this, then 228 // the first click to other parts of the UI is eaten. 229 SetMouseHandler(NULL); 230 231 // Set the state back to normal after the drop down menu is closed. 232 if (state_ != STATE_DISABLED) 233 SetState(STATE_NORMAL); 234 } 235 236 //////////////////////////////////////////////////////////////////////////////// 237 // 238 // ButtonDropDown - Accessibility 239 // 240 //////////////////////////////////////////////////////////////////////////////// 241 242 } // namespace views 243