menu_controller.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// found in the LICENSE file. 409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_controller.h" 609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "base/i18n/case_conversion.h" 809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "base/i18n/rtl.h" 909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "base/strings/utf_string_conversions.h" 1009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "base/time/time.h" 1109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/base/dragdrop/drag_utils.h" 1209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/base/dragdrop/os_exchange_data.h" 1309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h" 1409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/events/event.h" 1509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/events/event_utils.h" 1609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/gfx/canvas.h" 1709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/gfx/native_widget_types.h" 1809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/gfx/point.h" 1909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/gfx/screen.h" 2009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/gfx/vector2d.h" 2109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/native_theme/native_theme.h" 2209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/button/menu_button.h" 2309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_config.h" 2409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_controller_delegate.h" 2509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_host_root_view.h" 2609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_item_view.h" 2709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_message_loop.h" 2809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/menu_scroll_view_container.h" 2909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/controls/menu/submenu_view.h" 3009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/drag_utils.h" 3109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/focus/view_storage.h" 3209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/mouse_constants.h" 3309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/view.h" 3409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/view_constants.h" 3509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/views_delegate.h" 3609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/widget/root_view.h" 3709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/widget/tooltip_manager.h" 3809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "ui/views/widget/widget.h" 3909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 4009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#if defined(OS_WIN) 41197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch#include "ui/base/win/internal_constants.h" 427242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci#include "ui/views/win/hwnd_util.h" 4309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#endif 44197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 45d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)using base::Time; 46197021e6b966cfb06891637935ef33fff06433d1Ben Murdochusing base::TimeDelta; 4709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)using ui::OSExchangeData; 48c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) 4909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// Period of the scroll timer (in milliseconds). 50d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)static const int kScrollTimerMS = 30; 5109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 527242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// Amount of time from when the drop exits the menu and the menu is hidden. 537242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccistatic const int kCloseOnExitTime = 1200; 547242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 557242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// If a context menu is invoked by touch, we shift the menu by this offset so 567242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// that the finger does not obscure the menu. 577242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccistatic const int kCenteredContextMenuYOffset = -15; 587242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 597242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccinamespace views { 607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 617242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccinamespace { 627242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 637242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// When showing context menu on mouse down, the user might accidentally select 647242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// the menu item on the subsequent mouse up. To prevent this, we add the 657242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// following delay before the user is able to select an item. 667242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccistatic int menu_selection_hold_time_ms = kMinimumMsPressedToActivate; 677242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 687242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// The spacing offset for the bubble tip. 697242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciconst int kBubbleTipSizeLeftRight = 12; 707242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciconst int kBubbleTipSizeTopBottom = 11; 717242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 727242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// The maximum distance (in DIPS) that the mouse can be moved before it should 737242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// trigger a mouse menu item activation (regardless of how long the menu has 747242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// been showing). 757242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciconst float kMaximumLengthMovedToActivate = 4.0f; 767242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 777242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// Returns true if the mnemonic of |menu| matches key. 787242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccibool MatchesMnemonic(MenuItemView* menu, base::char16 key) { 797242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return key != 0 && menu->GetMnemonic() == key; 807242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 817242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 827242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// Returns true if |menu| doesn't have a mnemonic and first character of the its 837242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// title is |key|. 847242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccibool TitleMatchesMnemonic(MenuItemView* menu, base::char16 key) { 857242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (menu->GetMnemonic()) 867242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return false; 877242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 887242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci base::string16 lower_title = base::i18n::ToLower(menu->title()); 897242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return !lower_title.empty() && lower_title[0] == key; 907242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} 917242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci 927242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci} // namespace 93d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) 9409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// Returns the first descendant of |view| that is hot tracked. 957242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tuccistatic CustomButton* GetFirstHotTrackedView(View* view) { 9609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (!view) 97aafa69cb17c9d6606c07663ade5f81388a2c5986Ben Murdoch return NULL; 9809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) CustomButton* button = CustomButton::AsCustomButton(view); 9909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (button) { 1007242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (button->IsHotTracked()) 101aafa69cb17c9d6606c07663ade5f81388a2c5986Ben Murdoch return button; 10209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 10309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 104d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) for (int i = 0; i < view->child_count(); ++i) { 10509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) CustomButton* hot_view = GetFirstHotTrackedView(view->child_at(i)); 1067242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci if (hot_view) 10709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return hot_view; 10809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 1097242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return NULL; 110aafa69cb17c9d6606c07663ade5f81388a2c5986Ben Murdoch} 11109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 11209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// Recurses through the child views of |view| returning the first view starting 113d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)// at |start| that is focusable. A value of -1 for |start| indicates to start at 11409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// the first view (if |forward| is false, iterating starts at the last view). If 1157242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci// |forward| is true the children are considered first to last, otherwise last 11609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)// to first. 11709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)static View* GetFirstFocusableView(View* view, int start, bool forward) { 11809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (forward) { 11909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) for (int i = start == -1 ? 0 : start; i < view->child_count(); ++i) { 120197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); 121d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) if (deepest) 122197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch return deepest; 12309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 12409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } else { 12509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) for (int i = start == -1 ? view->child_count() - 1 : start; i >= 0; --i) { 12609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward); 12709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (deepest) 12809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return deepest; 12909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 13009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) } 13109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return view->IsFocusable() ? view : NULL; 132f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)} 133f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles) 134197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch// Returns the first child of |start| that is focusable. 13509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)static View* GetInitialFocusableView(View* start, bool forward) { 13609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return GetFirstFocusableView(start, -1, forward); 13709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)} 138197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch 139197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch// Returns the next view after |start_at| that is focusable. Returns NULL if 140197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch// there are no focusable children of |ancestor| after |start_at|. 141197021e6b966cfb06891637935ef33fff06433d1Ben Murdochstatic View* GetNextFocusableView(View* ancestor, 142197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch View* start_at, 143197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch bool forward) { 14409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) DCHECK(ancestor->Contains(start_at)); 14509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) View* parent = start_at; 14609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) do { 14709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) View* new_parent = parent->parent(); 1487242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci int index = new_parent->GetIndexOf(parent); 14909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) index += forward ? 1 : -1; 15009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (forward || index != -1) { 15109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) View* next = GetFirstFocusableView(new_parent, index, forward); 15209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (next) 1537242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci return next; 1547242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci } 155d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) parent = new_parent; 156d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) } while (parent != ancestor); 157c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) return NULL; 158} 159 160// MenuScrollTask -------------------------------------------------------------- 161 162// MenuScrollTask is used when the SubmenuView does not all fit on screen and 163// the mouse is over the scroll up/down buttons. MenuScrollTask schedules 164// itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls 165// appropriately. 166 167class MenuController::MenuScrollTask { 168 public: 169 MenuScrollTask() : submenu_(NULL), is_scrolling_up_(false), start_y_(0) { 170 pixels_per_second_ = MenuItemView::pref_menu_height() * 20; 171 } 172 173 void Update(const MenuController::MenuPart& part) { 174 if (!part.is_scroll()) { 175 StopScrolling(); 176 return; 177 } 178 DCHECK(part.submenu); 179 SubmenuView* new_menu = part.submenu; 180 bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP); 181 if (new_menu == submenu_ && is_scrolling_up_ == new_is_up) 182 return; 183 184 start_scroll_time_ = base::Time::Now(); 185 start_y_ = part.submenu->GetVisibleBounds().y(); 186 submenu_ = new_menu; 187 is_scrolling_up_ = new_is_up; 188 189 if (!scrolling_timer_.IsRunning()) { 190 scrolling_timer_.Start(FROM_HERE, 191 TimeDelta::FromMilliseconds(kScrollTimerMS), 192 this, &MenuScrollTask::Run); 193 } 194 } 195 196 void StopScrolling() { 197 if (scrolling_timer_.IsRunning()) { 198 scrolling_timer_.Stop(); 199 submenu_ = NULL; 200 } 201 } 202 203 // The menu being scrolled. Returns null if not scrolling. 204 SubmenuView* submenu() const { return submenu_; } 205 206 private: 207 void Run() { 208 DCHECK(submenu_); 209 gfx::Rect vis_rect = submenu_->GetVisibleBounds(); 210 const int delta_y = static_cast<int>( 211 (base::Time::Now() - start_scroll_time_).InMilliseconds() * 212 pixels_per_second_ / 1000); 213 vis_rect.set_y(is_scrolling_up_ ? 214 std::max(0, start_y_ - delta_y) : 215 std::min(submenu_->height() - vis_rect.height(), start_y_ + delta_y)); 216 submenu_->ScrollRectToVisible(vis_rect); 217 } 218 219 // SubmenuView being scrolled. 220 SubmenuView* submenu_; 221 222 // Direction scrolling. 223 bool is_scrolling_up_; 224 225 // Timer to periodically scroll. 226 base::RepeatingTimer<MenuScrollTask> scrolling_timer_; 227 228 // Time we started scrolling at. 229 base::Time start_scroll_time_; 230 231 // How many pixels to scroll per second. 232 int pixels_per_second_; 233 234 // Y-coordinate of submenu_view_ when scrolling started. 235 int start_y_; 236 237 DISALLOW_COPY_AND_ASSIGN(MenuScrollTask); 238}; 239 240// MenuController:SelectByCharDetails ---------------------------------------- 241 242struct MenuController::SelectByCharDetails { 243 SelectByCharDetails() 244 : first_match(-1), 245 has_multiple(false), 246 index_of_item(-1), 247 next_match(-1) { 248 } 249 250 // Index of the first menu with the specified mnemonic. 251 int first_match; 252 253 // If true there are multiple menu items with the same mnemonic. 254 bool has_multiple; 255 256 // Index of the selected item; may remain -1. 257 int index_of_item; 258 259 // If there are multiple matches this is the index of the item after the 260 // currently selected item whose mnemonic matches. This may remain -1 even 261 // though there are matches. 262 int next_match; 263}; 264 265// MenuController:State ------------------------------------------------------ 266 267MenuController::State::State() 268 : item(NULL), 269 submenu_open(false), 270 anchor(MENU_ANCHOR_TOPLEFT), 271 context_menu(false) { 272} 273 274MenuController::State::~State() {} 275 276// MenuController ------------------------------------------------------------ 277 278// static 279MenuController* MenuController::active_instance_ = NULL; 280 281// static 282MenuController* MenuController::GetActiveInstance() { 283 return active_instance_; 284} 285 286MenuItemView* MenuController::Run(Widget* parent, 287 MenuButton* button, 288 MenuItemView* root, 289 const gfx::Rect& bounds, 290 MenuAnchorPosition position, 291 bool context_menu, 292 int* result_event_flags) { 293 exit_type_ = EXIT_NONE; 294 possible_drag_ = false; 295 drag_in_progress_ = false; 296 closing_event_time_ = base::TimeDelta(); 297 menu_start_time_ = base::TimeTicks::Now(); 298 menu_start_mouse_press_loc_ = gfx::Point(); 299 300 // If we are shown on mouse press, we will eat the subsequent mouse down and 301 // the parent widget will not be able to reset its state (it might have mouse 302 // capture from the mouse down). So we clear its state here. 303 if (parent) { 304 View* root_view = parent->GetRootView(); 305 if (root_view) { 306 root_view->SetMouseHandler(NULL); 307 const ui::Event* event = 308 static_cast<internal::RootView*>(root_view)->current_event(); 309 if (event && event->type() == ui::ET_MOUSE_PRESSED) { 310 gfx::Point screen_loc( 311 static_cast<const ui::MouseEvent*>(event)->location()); 312 View::ConvertPointToScreen( 313 static_cast<View*>(event->target()), &screen_loc); 314 menu_start_mouse_press_loc_ = screen_loc; 315 } 316 } 317 } 318 319 bool nested_menu = showing_; 320 if (showing_) { 321 // Only support nesting of blocking_run menus, nesting of 322 // blocking/non-blocking shouldn't be needed. 323 DCHECK(blocking_run_); 324 325 // We're already showing, push the current state. 326 menu_stack_.push_back(state_); 327 328 // The context menu should be owned by the same parent. 329 DCHECK_EQ(owner_, parent); 330 } else { 331 showing_ = true; 332 } 333 334 // Reset current state. 335 pending_state_ = State(); 336 state_ = State(); 337 UpdateInitialLocation(bounds, position, context_menu); 338 339 if (owner_) 340 owner_->RemoveObserver(this); 341 owner_ = parent; 342 if (owner_) 343 owner_->AddObserver(this); 344 345 // Set the selection, which opens the initial menu. 346 SetSelection(root, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 347 348 if (!blocking_run_) { 349 // Start the timer to hide the menu. This is needed as we get no 350 // notification when the drag has finished. 351 StartCancelAllTimer(); 352 return NULL; 353 } 354 355 if (button) 356 menu_button_ = button; 357 358 // Make sure Chrome doesn't attempt to shut down while the menu is showing. 359 if (ViewsDelegate::views_delegate) 360 ViewsDelegate::views_delegate->AddRef(); 361 362 // We need to turn on nestable tasks as in some situations (pressing alt-f for 363 // one) the menus are run from a task. If we don't do this and are invoked 364 // from a task none of the tasks we schedule are processed and the menu 365 // appears totally broken. 366 message_loop_depth_++; 367 DCHECK_LE(message_loop_depth_, 2); 368 RunMessageLoop(nested_menu); 369 message_loop_depth_--; 370 371 if (ViewsDelegate::views_delegate) 372 ViewsDelegate::views_delegate->ReleaseRef(); 373 374 // Close any open menus. 375 SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); 376 377#if defined(OS_WIN) 378 // On Windows, if we select the menu item by touch and if the window at the 379 // location is another window on the same thread, that window gets a 380 // WM_MOUSEACTIVATE message and ends up activating itself, which is not 381 // correct. We workaround this by setting a property on the window at the 382 // current cursor location. We check for this property in our 383 // WM_MOUSEACTIVATE handler and don't activate the window if the property is 384 // set. 385 if (item_selected_by_touch_) { 386 item_selected_by_touch_ = false; 387 POINT cursor_pos; 388 ::GetCursorPos(&cursor_pos); 389 HWND window = ::WindowFromPoint(cursor_pos); 390 if (::GetWindowThreadProcessId(window, NULL) == 391 ::GetCurrentThreadId()) { 392 ::SetProp(window, ui::kIgnoreTouchMouseActivateForWindow, 393 reinterpret_cast<HANDLE>(true)); 394 } 395 } 396#endif 397 398 if (nested_menu) { 399 DCHECK(!menu_stack_.empty()); 400 // We're running from within a menu, restore the previous state. 401 // The menus are already showing, so we don't have to show them. 402 state_ = menu_stack_.back(); 403 pending_state_ = menu_stack_.back(); 404 menu_stack_.pop_back(); 405 } else { 406 showing_ = false; 407 did_capture_ = false; 408 } 409 410 MenuItemView* result = result_; 411 // In case we're nested, reset result_. 412 result_ = NULL; 413 414 if (result_event_flags) 415 *result_event_flags = accept_event_flags_; 416 417 if (exit_type_ == EXIT_OUTERMOST) { 418 SetExitType(EXIT_NONE); 419 } else { 420 if (nested_menu && result) { 421 // We're nested and about to return a value. The caller might enter 422 // another blocking loop. We need to make sure all menus are hidden 423 // before that happens otherwise the menus will stay on screen. 424 CloseAllNestedMenus(); 425 SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); 426 427 // Set exit_all_, which makes sure all nested loops exit immediately. 428 if (exit_type_ != EXIT_DESTROYED) 429 SetExitType(EXIT_ALL); 430 } 431 } 432 433 // If we stopped running because one of the menus was destroyed chances are 434 // the button was also destroyed. 435 if (exit_type_ != EXIT_DESTROYED && menu_button_) { 436 menu_button_->SetState(CustomButton::STATE_NORMAL); 437 menu_button_->SchedulePaint(); 438 } 439 return result; 440} 441 442void MenuController::Cancel(ExitType type) { 443 // If the menu has already been destroyed, no further cancellation is 444 // needed. We especially don't want to set the |exit_type_| to a lesser 445 // value. 446 if (exit_type_ == EXIT_DESTROYED || exit_type_ == type) 447 return; 448 449 if (!showing_) { 450 // This occurs if we're in the process of notifying the delegate for a drop 451 // and the delegate cancels us. 452 return; 453 } 454 455 MenuItemView* selected = state_.item; 456 SetExitType(type); 457 458 SendMouseCaptureLostToActiveView(); 459 460 // Hide windows immediately. 461 SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); 462 463 if (!blocking_run_) { 464 // If we didn't block the caller we need to notify the menu, which 465 // triggers deleting us. 466 DCHECK(selected); 467 showing_ = false; 468 delegate_->DropMenuClosed( 469 internal::MenuControllerDelegate::NOTIFY_DELEGATE, 470 selected->GetRootMenuItem()); 471 // WARNING: the call to MenuClosed deletes us. 472 return; 473 } 474} 475 476void MenuController::OnMousePressed(SubmenuView* source, 477 const ui::MouseEvent& event) { 478 SetSelectionOnPointerDown(source, event); 479} 480 481void MenuController::OnMouseDragged(SubmenuView* source, 482 const ui::MouseEvent& event) { 483 MenuPart part = GetMenuPart(source, event.location()); 484 UpdateScrolling(part); 485 486 if (!blocking_run_) 487 return; 488 489 if (possible_drag_) { 490 if (View::ExceededDragThreshold(event.location() - press_pt_)) 491 StartDrag(source, press_pt_); 492 return; 493 } 494 MenuItemView* mouse_menu = NULL; 495 if (part.type == MenuPart::MENU_ITEM) { 496 if (!part.menu) 497 part.menu = source->GetMenuItem(); 498 else 499 mouse_menu = part.menu; 500 SetSelection(part.menu ? part.menu : state_.item, SELECTION_OPEN_SUBMENU); 501 } else if (part.type == MenuPart::NONE) { 502 ShowSiblingMenu(source, event.location()); 503 } 504 UpdateActiveMouseView(source, event, mouse_menu); 505} 506 507void MenuController::OnMouseReleased(SubmenuView* source, 508 const ui::MouseEvent& event) { 509 if (!blocking_run_) 510 return; 511 512 DCHECK(state_.item); 513 possible_drag_ = false; 514 DCHECK(blocking_run_); 515 MenuPart part = GetMenuPart(source, event.location()); 516 if (event.IsRightMouseButton() && part.type == MenuPart::MENU_ITEM) { 517 MenuItemView* menu = part.menu; 518 // |menu| is NULL means this event is from an empty menu or a separator. 519 // If it is from an empty menu, use parent context menu instead of that. 520 if (menu == NULL && 521 part.submenu->child_count() == 1 && 522 part.submenu->child_at(0)->id() == MenuItemView::kEmptyMenuItemViewID) { 523 menu = part.parent; 524 } 525 526 if (menu != NULL && ShowContextMenu(menu, source, event, 527 ui::MENU_SOURCE_MOUSE)) 528 return; 529 } 530 531 // We can use Ctrl+click or the middle mouse button to recursively open urls 532 // for selected folder menu items. If it's only a left click, show the 533 // contents of the folder. 534 if (!part.is_scroll() && part.menu && 535 !(part.menu->HasSubmenu() && 536 (event.flags() & ui::EF_LEFT_MOUSE_BUTTON))) { 537 if (GetActiveMouseView()) { 538 SendMouseReleaseToActiveView(source, event); 539 return; 540 } 541 // If a mouse release was received quickly after showing. 542 base::TimeDelta time_shown = base::TimeTicks::Now() - menu_start_time_; 543 if (time_shown.InMilliseconds() < menu_selection_hold_time_ms) { 544 // And it wasn't far from the mouse press location. 545 gfx::Point screen_loc(event.location()); 546 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 547 gfx::Vector2d moved = screen_loc - menu_start_mouse_press_loc_; 548 if (moved.Length() < kMaximumLengthMovedToActivate) { 549 // Ignore the mouse release as it was likely this menu was shown under 550 // the mouse and the action was just a normal click. 551 return; 552 } 553 } 554 if (part.menu->GetDelegate()->ShouldExecuteCommandWithoutClosingMenu( 555 part.menu->GetCommand(), event)) { 556 part.menu->GetDelegate()->ExecuteCommand(part.menu->GetCommand(), 557 event.flags()); 558 return; 559 } 560 if (!part.menu->NonIconChildViewsCount() && 561 part.menu->GetDelegate()->IsTriggerableEvent(part.menu, event)) { 562 base::TimeDelta shown_time = base::TimeTicks::Now() - menu_start_time_; 563 if (!state_.context_menu || !View::ShouldShowContextMenuOnMousePress() || 564 shown_time.InMilliseconds() > menu_selection_hold_time_ms) { 565 Accept(part.menu, event.flags()); 566 } 567 return; 568 } 569 } else if (part.type == MenuPart::MENU_ITEM) { 570 // User either clicked on empty space, or a menu that has children. 571 SetSelection(part.menu ? part.menu : state_.item, 572 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 573 } 574 SendMouseCaptureLostToActiveView(); 575} 576 577void MenuController::OnMouseMoved(SubmenuView* source, 578 const ui::MouseEvent& event) { 579 HandleMouseLocation(source, event.location()); 580} 581 582void MenuController::OnMouseEntered(SubmenuView* source, 583 const ui::MouseEvent& event) { 584 // MouseEntered is always followed by a mouse moved, so don't need to 585 // do anything here. 586} 587 588bool MenuController::OnMouseWheel(SubmenuView* source, 589 const ui::MouseWheelEvent& event) { 590 MenuPart part = GetMenuPart(source, event.location()); 591 return part.submenu && part.submenu->OnMouseWheel(event); 592} 593 594void MenuController::OnGestureEvent(SubmenuView* source, 595 ui::GestureEvent* event) { 596 MenuPart part = GetMenuPart(source, event->location()); 597 if (event->type() == ui::ET_GESTURE_TAP_DOWN) { 598 SetSelectionOnPointerDown(source, *event); 599 event->StopPropagation(); 600 } else if (event->type() == ui::ET_GESTURE_LONG_PRESS) { 601 if (part.type == MenuPart::MENU_ITEM && part.menu) { 602 if (ShowContextMenu(part.menu, source, *event, ui::MENU_SOURCE_TOUCH)) 603 event->StopPropagation(); 604 } 605 } else if (event->type() == ui::ET_GESTURE_TAP) { 606 if (!part.is_scroll() && part.menu && 607 !(part.menu->HasSubmenu())) { 608 if (part.menu->GetDelegate()->IsTriggerableEvent( 609 part.menu, *event)) { 610 Accept(part.menu, event->flags()); 611 item_selected_by_touch_ = true; 612 } 613 event->StopPropagation(); 614 } else if (part.type == MenuPart::MENU_ITEM) { 615 // User either tapped on empty space, or a menu that has children. 616 SetSelection(part.menu ? part.menu : state_.item, 617 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 618 event->StopPropagation(); 619 } 620 } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL && 621 part.menu && 622 part.type == MenuPart::MENU_ITEM) { 623 // Move the selection to the parent menu so that the selection in the 624 // current menu is unset. Make sure the submenu remains open by sending the 625 // appropriate SetSelectionTypes flags. 626 SetSelection(part.menu->GetParentMenuItem(), 627 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 628 event->StopPropagation(); 629 } 630 631 if (event->stopped_propagation()) 632 return; 633 634 if (!part.submenu) 635 return; 636 part.submenu->OnGestureEvent(event); 637} 638 639bool MenuController::GetDropFormats( 640 SubmenuView* source, 641 int* formats, 642 std::set<OSExchangeData::CustomFormat>* custom_formats) { 643 return source->GetMenuItem()->GetDelegate()->GetDropFormats( 644 source->GetMenuItem(), formats, custom_formats); 645} 646 647bool MenuController::AreDropTypesRequired(SubmenuView* source) { 648 return source->GetMenuItem()->GetDelegate()->AreDropTypesRequired( 649 source->GetMenuItem()); 650} 651 652bool MenuController::CanDrop(SubmenuView* source, const OSExchangeData& data) { 653 return source->GetMenuItem()->GetDelegate()->CanDrop(source->GetMenuItem(), 654 data); 655} 656 657void MenuController::OnDragEntered(SubmenuView* source, 658 const ui::DropTargetEvent& event) { 659 valid_drop_coordinates_ = false; 660} 661 662int MenuController::OnDragUpdated(SubmenuView* source, 663 const ui::DropTargetEvent& event) { 664 StopCancelAllTimer(); 665 666 gfx::Point screen_loc(event.location()); 667 View::ConvertPointToScreen(source, &screen_loc); 668 if (valid_drop_coordinates_ && screen_loc == drop_pt_) 669 return last_drop_operation_; 670 drop_pt_ = screen_loc; 671 valid_drop_coordinates_ = true; 672 673 MenuItemView* menu_item = GetMenuItemAt(source, event.x(), event.y()); 674 bool over_empty_menu = false; 675 if (!menu_item) { 676 // See if we're over an empty menu. 677 menu_item = GetEmptyMenuItemAt(source, event.x(), event.y()); 678 if (menu_item) 679 over_empty_menu = true; 680 } 681 MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE; 682 int drop_operation = ui::DragDropTypes::DRAG_NONE; 683 if (menu_item) { 684 gfx::Point menu_item_loc(event.location()); 685 View::ConvertPointToTarget(source, menu_item, &menu_item_loc); 686 MenuItemView* query_menu_item; 687 if (!over_empty_menu) { 688 int menu_item_height = menu_item->height(); 689 if (menu_item->HasSubmenu() && 690 (menu_item_loc.y() > kDropBetweenPixels && 691 menu_item_loc.y() < (menu_item_height - kDropBetweenPixels))) { 692 drop_position = MenuDelegate::DROP_ON; 693 } else { 694 drop_position = (menu_item_loc.y() < menu_item_height / 2) ? 695 MenuDelegate::DROP_BEFORE : MenuDelegate::DROP_AFTER; 696 } 697 query_menu_item = menu_item; 698 } else { 699 query_menu_item = menu_item->GetParentMenuItem(); 700 drop_position = MenuDelegate::DROP_ON; 701 } 702 drop_operation = menu_item->GetDelegate()->GetDropOperation( 703 query_menu_item, event, &drop_position); 704 705 // If the menu has a submenu, schedule the submenu to open. 706 SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU : 707 SELECTION_DEFAULT); 708 709 if (drop_position == MenuDelegate::DROP_NONE || 710 drop_operation == ui::DragDropTypes::DRAG_NONE) 711 menu_item = NULL; 712 } else { 713 SetSelection(source->GetMenuItem(), SELECTION_OPEN_SUBMENU); 714 } 715 SetDropMenuItem(menu_item, drop_position); 716 last_drop_operation_ = drop_operation; 717 return drop_operation; 718} 719 720void MenuController::OnDragExited(SubmenuView* source) { 721 StartCancelAllTimer(); 722 723 if (drop_target_) { 724 StopShowTimer(); 725 SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); 726 } 727} 728 729int MenuController::OnPerformDrop(SubmenuView* source, 730 const ui::DropTargetEvent& event) { 731 DCHECK(drop_target_); 732 // NOTE: the delegate may delete us after invoking OnPerformDrop, as such 733 // we don't call cancel here. 734 735 MenuItemView* item = state_.item; 736 DCHECK(item); 737 738 MenuItemView* drop_target = drop_target_; 739 MenuDelegate::DropPosition drop_position = drop_position_; 740 741 // Close all menus, including any nested menus. 742 SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); 743 CloseAllNestedMenus(); 744 745 // Set state such that we exit. 746 showing_ = false; 747 SetExitType(EXIT_ALL); 748 749 // If over an empty menu item, drop occurs on the parent. 750 if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID) 751 drop_target = drop_target->GetParentMenuItem(); 752 753 if (!IsBlockingRun()) { 754 delegate_->DropMenuClosed( 755 internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE, 756 item->GetRootMenuItem()); 757 } 758 759 // WARNING: the call to MenuClosed deletes us. 760 761 return drop_target->GetDelegate()->OnPerformDrop( 762 drop_target, drop_position, event); 763} 764 765void MenuController::OnDragEnteredScrollButton(SubmenuView* source, 766 bool is_up) { 767 MenuPart part; 768 part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN; 769 part.submenu = source; 770 UpdateScrolling(part); 771 772 // Do this to force the selection to hide. 773 SetDropMenuItem(source->GetMenuItemAt(0), MenuDelegate::DROP_NONE); 774 775 StopCancelAllTimer(); 776} 777 778void MenuController::OnDragExitedScrollButton(SubmenuView* source) { 779 StartCancelAllTimer(); 780 SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); 781 StopScrolling(); 782} 783 784void MenuController::UpdateSubmenuSelection(SubmenuView* submenu) { 785 if (submenu->IsShowing()) { 786 gfx::Point point = GetScreen()->GetCursorScreenPoint(); 787 const SubmenuView* root_submenu = 788 submenu->GetMenuItem()->GetRootMenuItem()->GetSubmenu(); 789 View::ConvertPointFromScreen( 790 root_submenu->GetWidget()->GetRootView(), &point); 791 HandleMouseLocation(submenu, point); 792 } 793} 794 795void MenuController::OnWidgetDestroying(Widget* widget) { 796 DCHECK_EQ(owner_, widget); 797 owner_->RemoveObserver(this); 798 owner_ = NULL; 799 message_loop_->ClearOwner(); 800} 801 802// static 803void MenuController::TurnOffMenuSelectionHoldForTest() { 804 menu_selection_hold_time_ms = -1; 805} 806 807void MenuController::SetSelection(MenuItemView* menu_item, 808 int selection_types) { 809 size_t paths_differ_at = 0; 810 std::vector<MenuItemView*> current_path; 811 std::vector<MenuItemView*> new_path; 812 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, 813 &new_path, &paths_differ_at); 814 815 size_t current_size = current_path.size(); 816 size_t new_size = new_path.size(); 817 818 bool pending_item_changed = pending_state_.item != menu_item; 819 if (pending_item_changed && pending_state_.item) { 820 CustomButton* button = GetFirstHotTrackedView(pending_state_.item); 821 if (button) 822 button->SetHotTracked(false); 823 } 824 825 // Notify the old path it isn't selected. 826 MenuDelegate* current_delegate = 827 current_path.empty() ? NULL : current_path.front()->GetDelegate(); 828 for (size_t i = paths_differ_at; i < current_size; ++i) { 829 if (current_delegate && 830 current_path[i]->GetType() == MenuItemView::SUBMENU) { 831 current_delegate->WillHideMenu(current_path[i]); 832 } 833 current_path[i]->SetSelected(false); 834 } 835 836 // Notify the new path it is selected. 837 for (size_t i = paths_differ_at; i < new_size; ++i) { 838 new_path[i]->ScrollRectToVisible(new_path[i]->GetLocalBounds()); 839 new_path[i]->SetSelected(true); 840 } 841 842 if (menu_item && menu_item->GetDelegate()) 843 menu_item->GetDelegate()->SelectionChanged(menu_item); 844 845 DCHECK(menu_item || (selection_types & SELECTION_EXIT) != 0); 846 847 pending_state_.item = menu_item; 848 pending_state_.submenu_open = (selection_types & SELECTION_OPEN_SUBMENU) != 0; 849 850 // Stop timers. 851 StopCancelAllTimer(); 852 // Resets show timer only when pending menu item is changed. 853 if (pending_item_changed) 854 StopShowTimer(); 855 856 if (selection_types & SELECTION_UPDATE_IMMEDIATELY) 857 CommitPendingSelection(); 858 else if (pending_item_changed) 859 StartShowTimer(); 860 861 // Notify an accessibility focus event on all menu items except for the root. 862 if (menu_item && 863 (MenuDepth(menu_item) != 1 || 864 menu_item->GetType() != MenuItemView::SUBMENU)) { 865 menu_item->NotifyAccessibilityEvent( 866 ui::AX_EVENT_FOCUS, true); 867 } 868} 869 870void MenuController::SetSelectionOnPointerDown(SubmenuView* source, 871 const ui::LocatedEvent& event) { 872 if (!blocking_run_) 873 return; 874 875 DCHECK(!GetActiveMouseView()); 876 877 MenuPart part = GetMenuPart(source, event.location()); 878 if (part.is_scroll()) 879 return; // Ignore presses on scroll buttons. 880 881 // When this menu is opened through a touch event, a simulated right-click 882 // is sent before the menu appears. Ignore it. 883 if ((event.flags() & ui::EF_RIGHT_MOUSE_BUTTON) && 884 (event.flags() & ui::EF_FROM_TOUCH)) 885 return; 886 887 if (part.type == MenuPart::NONE || 888 (part.type == MenuPart::MENU_ITEM && part.menu && 889 part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { 890 // Remember the time when we repost the event. The owner can then use this 891 // to figure out if this menu was finished with the same click which is 892 // sent to it thereafter. Note that the time stamp front he event cannot be 893 // used since the reposting will set a new timestamp when the event gets 894 // processed. As such it is better to take the current time which will be 895 // closer to the time when it arrives again in the menu handler. 896 closing_event_time_ = ui::EventTimeForNow(); 897 898 // Mouse wasn't pressed over any menu, or the active menu, cancel. 899 900#if defined(OS_WIN) 901 // We're going to close and we own the mouse capture. We need to repost the 902 // mouse down, otherwise the window the user clicked on won't get the event. 903 RepostEvent(source, event); 904#endif 905 906 // And close. 907 ExitType exit_type = EXIT_ALL; 908 if (!menu_stack_.empty()) { 909 // We're running nested menus. Only exit all if the mouse wasn't over one 910 // of the menus from the last run. 911 gfx::Point screen_loc(event.location()); 912 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 913 MenuPart last_part = GetMenuPartByScreenCoordinateUsingMenu( 914 menu_stack_.back().item, screen_loc); 915 if (last_part.type != MenuPart::NONE) 916 exit_type = EXIT_OUTERMOST; 917 } 918 Cancel(exit_type); 919 920#if defined(OS_CHROMEOS) 921 // We're going to exit the menu and want to repost the event so that is 922 // is handled normally after the context menu has exited. We call 923 // RepostEvent after Cancel so that mouse capture has been released so 924 // that finding the event target is unaffected by the current capture. 925 RepostEvent(source, event); 926#endif 927 // Do not repost events for Linux Aura because this behavior is more 928 // consistent with the behavior of other Linux apps. 929 return; 930 } 931 932 // On a press we immediately commit the selection, that way a submenu 933 // pops up immediately rather than after a delay. 934 int selection_types = SELECTION_UPDATE_IMMEDIATELY; 935 if (!part.menu) { 936 part.menu = part.parent; 937 selection_types |= SELECTION_OPEN_SUBMENU; 938 } else { 939 if (part.menu->GetDelegate()->CanDrag(part.menu)) { 940 possible_drag_ = true; 941 press_pt_ = event.location(); 942 } 943 if (part.menu->HasSubmenu()) 944 selection_types |= SELECTION_OPEN_SUBMENU; 945 } 946 SetSelection(part.menu, selection_types); 947} 948 949void MenuController::StartDrag(SubmenuView* source, 950 const gfx::Point& location) { 951 MenuItemView* item = state_.item; 952 DCHECK(item); 953 // Points are in the coordinates of the submenu, need to map to that of 954 // the selected item. Additionally source may not be the parent of 955 // the selected item, so need to map to screen first then to item. 956 gfx::Point press_loc(location); 957 View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc); 958 View::ConvertPointFromScreen(item, &press_loc); 959 gfx::Point widget_loc(press_loc); 960 View::ConvertPointToWidget(item, &widget_loc); 961 scoped_ptr<gfx::Canvas> canvas(GetCanvasForDragImage( 962 source->GetWidget(), gfx::Size(item->width(), item->height()))); 963 item->PaintButton(canvas.get(), MenuItemView::PB_FOR_DRAG); 964 965 OSExchangeData data; 966 item->GetDelegate()->WriteDragData(item, &data); 967 drag_utils::SetDragImageOnDataObject(*canvas, item->size(), 968 press_loc.OffsetFromOrigin(), 969 &data); 970 StopScrolling(); 971 int drag_ops = item->GetDelegate()->GetDragOperations(item); 972 drag_in_progress_ = true; 973 // TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below. 974 item->GetWidget()->RunShellDrag(NULL, data, widget_loc, drag_ops, 975 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE); 976 drag_in_progress_ = false; 977 978 if (GetActiveInstance() == this) { 979 if (showing_) { 980 // We're still showing, close all menus. 981 CloseAllNestedMenus(); 982 Cancel(EXIT_ALL); 983 } // else case, drop was on us. 984 } // else case, someone canceled us, don't do anything 985} 986 987bool MenuController::OnKeyDown(ui::KeyboardCode key_code) { 988 DCHECK(blocking_run_); 989 990 switch (key_code) { 991 case ui::VKEY_UP: 992 IncrementSelection(-1); 993 break; 994 995 case ui::VKEY_DOWN: 996 IncrementSelection(1); 997 break; 998 999 // Handling of VK_RIGHT and VK_LEFT is different depending on the UI 1000 // layout. 1001 case ui::VKEY_RIGHT: 1002 if (base::i18n::IsRTL()) 1003 CloseSubmenu(); 1004 else 1005 OpenSubmenuChangeSelectionIfCan(); 1006 break; 1007 1008 case ui::VKEY_LEFT: 1009 if (base::i18n::IsRTL()) 1010 OpenSubmenuChangeSelectionIfCan(); 1011 else 1012 CloseSubmenu(); 1013 break; 1014 1015 case ui::VKEY_SPACE: 1016 if (SendAcceleratorToHotTrackedView() == ACCELERATOR_PROCESSED_EXIT) 1017 return false; 1018 break; 1019 1020 case ui::VKEY_F4: 1021 if (!is_combobox_) 1022 break; 1023 // Fallthrough to accept on F4, so combobox menus match Windows behavior. 1024 case ui::VKEY_RETURN: 1025 if (pending_state_.item) { 1026 if (pending_state_.item->HasSubmenu()) { 1027 OpenSubmenuChangeSelectionIfCan(); 1028 } else { 1029 SendAcceleratorResultType result = SendAcceleratorToHotTrackedView(); 1030 if (result == ACCELERATOR_NOT_PROCESSED && 1031 pending_state_.item->enabled()) { 1032 Accept(pending_state_.item, 0); 1033 return false; 1034 } else if (result == ACCELERATOR_PROCESSED_EXIT) { 1035 return false; 1036 } 1037 } 1038 } 1039 break; 1040 1041 case ui::VKEY_ESCAPE: 1042 if (!state_.item->GetParentMenuItem() || 1043 (!state_.item->GetParentMenuItem()->GetParentMenuItem() && 1044 (!state_.item->HasSubmenu() || 1045 !state_.item->GetSubmenu()->IsShowing()))) { 1046 // User pressed escape and only one menu is shown, cancel it. 1047 Cancel(EXIT_OUTERMOST); 1048 return false; 1049 } 1050 CloseSubmenu(); 1051 break; 1052 1053 default: 1054 break; 1055 } 1056 return true; 1057} 1058 1059MenuController::MenuController(ui::NativeTheme* theme, 1060 bool blocking, 1061 internal::MenuControllerDelegate* delegate) 1062 : blocking_run_(blocking), 1063 showing_(false), 1064 exit_type_(EXIT_NONE), 1065 did_capture_(false), 1066 result_(NULL), 1067 accept_event_flags_(0), 1068 drop_target_(NULL), 1069 drop_position_(MenuDelegate::DROP_UNKNOWN), 1070 owner_(NULL), 1071 possible_drag_(false), 1072 drag_in_progress_(false), 1073 valid_drop_coordinates_(false), 1074 last_drop_operation_(MenuDelegate::DROP_UNKNOWN), 1075 showing_submenu_(false), 1076 menu_button_(NULL), 1077 active_mouse_view_id_(ViewStorage::GetInstance()->CreateStorageID()), 1078 delegate_(delegate), 1079 message_loop_depth_(0), 1080 menu_config_(theme), 1081 closing_event_time_(base::TimeDelta()), 1082 menu_start_time_(base::TimeTicks()), 1083 is_combobox_(false), 1084 item_selected_by_touch_(false), 1085 message_loop_(MenuMessageLoop::Create()) { 1086 active_instance_ = this; 1087} 1088 1089MenuController::~MenuController() { 1090 DCHECK(!showing_); 1091 if (owner_) 1092 owner_->RemoveObserver(this); 1093 if (active_instance_ == this) 1094 active_instance_ = NULL; 1095 StopShowTimer(); 1096 StopCancelAllTimer(); 1097} 1098 1099void MenuController::RunMessageLoop(bool nested_menu) { 1100 message_loop_->Run(this, owner_, nested_menu); 1101} 1102 1103MenuController::SendAcceleratorResultType 1104 MenuController::SendAcceleratorToHotTrackedView() { 1105 CustomButton* hot_view = GetFirstHotTrackedView(pending_state_.item); 1106 if (!hot_view) 1107 return ACCELERATOR_NOT_PROCESSED; 1108 1109 ui::Accelerator accelerator(ui::VKEY_RETURN, ui::EF_NONE); 1110 hot_view->AcceleratorPressed(accelerator); 1111 CustomButton* button = static_cast<CustomButton*>(hot_view); 1112 button->SetHotTracked(true); 1113 return (exit_type_ == EXIT_NONE) ? 1114 ACCELERATOR_PROCESSED : ACCELERATOR_PROCESSED_EXIT; 1115} 1116 1117void MenuController::UpdateInitialLocation(const gfx::Rect& bounds, 1118 MenuAnchorPosition position, 1119 bool context_menu) { 1120 pending_state_.context_menu = context_menu; 1121 pending_state_.initial_bounds = bounds; 1122 if (bounds.height() > 1) { 1123 // Inset the bounds slightly, otherwise drag coordinates don't line up 1124 // nicely and menus close prematurely. 1125 pending_state_.initial_bounds.Inset(0, 1); 1126 } 1127 1128 // Reverse anchor position for RTL languages. 1129 if (base::i18n::IsRTL() && 1130 (position == MENU_ANCHOR_TOPRIGHT || position == MENU_ANCHOR_TOPLEFT)) { 1131 pending_state_.anchor = position == MENU_ANCHOR_TOPRIGHT 1132 ? MENU_ANCHOR_TOPLEFT 1133 : MENU_ANCHOR_TOPRIGHT; 1134 } else { 1135 pending_state_.anchor = position; 1136 } 1137 1138 // Calculate the bounds of the monitor we'll show menus on. Do this once to 1139 // avoid repeated system queries for the info. 1140 pending_state_.monitor_bounds = GetScreen()->GetDisplayNearestPoint( 1141 bounds.origin()).work_area(); 1142#if defined(USE_ASH) 1143 if (!pending_state_.monitor_bounds.Contains(bounds)) { 1144 // Use the monitor area if the work area doesn't contain the bounds. This 1145 // handles showing a menu from the launcher. 1146 gfx::Rect monitor_area = GetScreen()->GetDisplayNearestPoint( 1147 bounds.origin()).bounds(); 1148 if (monitor_area.Contains(bounds)) 1149 pending_state_.monitor_bounds = monitor_area; 1150 } 1151#endif 1152} 1153 1154void MenuController::Accept(MenuItemView* item, int event_flags) { 1155 DCHECK(IsBlockingRun()); 1156 result_ = item; 1157 if (item && !menu_stack_.empty() && 1158 !item->GetDelegate()->ShouldCloseAllMenusOnExecute(item->GetCommand())) { 1159 SetExitType(EXIT_OUTERMOST); 1160 } else { 1161 SetExitType(EXIT_ALL); 1162 } 1163 accept_event_flags_ = event_flags; 1164} 1165 1166bool MenuController::ShowSiblingMenu(SubmenuView* source, 1167 const gfx::Point& mouse_location) { 1168 if (!menu_stack_.empty() || !menu_button_) 1169 return false; 1170 1171 View* source_view = source->GetScrollViewContainer(); 1172 if (mouse_location.x() >= 0 && 1173 mouse_location.x() < source_view->width() && 1174 mouse_location.y() >= 0 && 1175 mouse_location.y() < source_view->height()) { 1176 // The mouse is over the menu, no need to continue. 1177 return false; 1178 } 1179 1180 gfx::NativeWindow window_under_mouse = GetScreen()->GetWindowUnderCursor(); 1181 // TODO(oshima): Replace with views only API. 1182 if (!owner_ || window_under_mouse != owner_->GetNativeWindow()) 1183 return false; 1184 1185 // The user moved the mouse outside the menu and over the owning window. See 1186 // if there is a sibling menu we should show. 1187 gfx::Point screen_point(mouse_location); 1188 View::ConvertPointToScreen(source_view, &screen_point); 1189 MenuAnchorPosition anchor; 1190 bool has_mnemonics; 1191 MenuButton* button = NULL; 1192 MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()-> 1193 GetSiblingMenu(source->GetMenuItem()->GetRootMenuItem(), 1194 screen_point, &anchor, &has_mnemonics, &button); 1195 if (!alt_menu || (state_.item && state_.item->GetRootMenuItem() == alt_menu)) 1196 return false; 1197 1198 delegate_->SiblingMenuCreated(alt_menu); 1199 1200 if (!button) { 1201 // If the delegate returns a menu, they must also return a button. 1202 NOTREACHED(); 1203 return false; 1204 } 1205 1206 // There is a sibling menu, update the button state, hide the current menu 1207 // and show the new one. 1208 menu_button_->SetState(CustomButton::STATE_NORMAL); 1209 menu_button_->SchedulePaint(); 1210 menu_button_ = button; 1211 menu_button_->SetState(CustomButton::STATE_PRESSED); 1212 menu_button_->SchedulePaint(); 1213 1214 // Need to reset capture when we show the menu again, otherwise we aren't 1215 // going to get any events. 1216 did_capture_ = false; 1217 gfx::Point screen_menu_loc; 1218 View::ConvertPointToScreen(button, &screen_menu_loc); 1219 1220 // It is currently not possible to show a submenu recursively in a bubble. 1221 DCHECK(!MenuItemView::IsBubble(anchor)); 1222 // Subtract 1 from the height to make the popup flush with the button border. 1223 UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(), 1224 button->width(), button->height() - 1), 1225 anchor, state_.context_menu); 1226 alt_menu->PrepareForRun( 1227 false, has_mnemonics, 1228 source->GetMenuItem()->GetRootMenuItem()->show_mnemonics_); 1229 alt_menu->controller_ = this; 1230 SetSelection(alt_menu, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1231 return true; 1232} 1233 1234bool MenuController::ShowContextMenu(MenuItemView* menu_item, 1235 SubmenuView* source, 1236 const ui::LocatedEvent& event, 1237 ui::MenuSourceType source_type) { 1238 // Set the selection immediately, making sure the submenu is only open 1239 // if it already was. 1240 int selection_types = SELECTION_UPDATE_IMMEDIATELY; 1241 if (state_.item == pending_state_.item && state_.submenu_open) 1242 selection_types |= SELECTION_OPEN_SUBMENU; 1243 SetSelection(pending_state_.item, selection_types); 1244 gfx::Point loc(event.location()); 1245 View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); 1246 1247 if (menu_item->GetDelegate()->ShowContextMenu( 1248 menu_item, menu_item->GetCommand(), loc, source_type)) { 1249 SendMouseCaptureLostToActiveView(); 1250 return true; 1251 } 1252 return false; 1253} 1254 1255void MenuController::CloseAllNestedMenus() { 1256 for (std::list<State>::iterator i = menu_stack_.begin(); 1257 i != menu_stack_.end(); ++i) { 1258 MenuItemView* last_item = i->item; 1259 for (MenuItemView* item = last_item; item; 1260 item = item->GetParentMenuItem()) { 1261 CloseMenu(item); 1262 last_item = item; 1263 } 1264 i->submenu_open = false; 1265 i->item = last_item; 1266 } 1267} 1268 1269MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { 1270 // Walk the view hierarchy until we find a menu item (or the root). 1271 View* child_under_mouse = source->GetEventHandlerForPoint(gfx::Point(x, y)); 1272 while (child_under_mouse && 1273 child_under_mouse->id() != MenuItemView::kMenuItemViewID) { 1274 child_under_mouse = child_under_mouse->parent(); 1275 } 1276 if (child_under_mouse && child_under_mouse->enabled() && 1277 child_under_mouse->id() == MenuItemView::kMenuItemViewID) { 1278 return static_cast<MenuItemView*>(child_under_mouse); 1279 } 1280 return NULL; 1281} 1282 1283MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { 1284 View* child_under_mouse = source->GetEventHandlerForPoint(gfx::Point(x, y)); 1285 if (child_under_mouse && 1286 child_under_mouse->id() == MenuItemView::kEmptyMenuItemViewID) { 1287 return static_cast<MenuItemView*>(child_under_mouse); 1288 } 1289 return NULL; 1290} 1291 1292bool MenuController::IsScrollButtonAt(SubmenuView* source, 1293 int x, 1294 int y, 1295 MenuPart::Type* part) { 1296 MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer(); 1297 View* child_under_mouse = 1298 scroll_view->GetEventHandlerForPoint(gfx::Point(x, y)); 1299 if (child_under_mouse && child_under_mouse->enabled()) { 1300 if (child_under_mouse == scroll_view->scroll_up_button()) { 1301 *part = MenuPart::SCROLL_UP; 1302 return true; 1303 } 1304 if (child_under_mouse == scroll_view->scroll_down_button()) { 1305 *part = MenuPart::SCROLL_DOWN; 1306 return true; 1307 } 1308 } 1309 return false; 1310} 1311 1312MenuController::MenuPart MenuController::GetMenuPart( 1313 SubmenuView* source, 1314 const gfx::Point& source_loc) { 1315 gfx::Point screen_loc(source_loc); 1316 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 1317 return GetMenuPartByScreenCoordinateUsingMenu(state_.item, screen_loc); 1318} 1319 1320MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinateUsingMenu( 1321 MenuItemView* item, 1322 const gfx::Point& screen_loc) { 1323 MenuPart part; 1324 for (; item; item = item->GetParentMenuItem()) { 1325 if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && 1326 GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc, 1327 &part)) { 1328 return part; 1329 } 1330 } 1331 return part; 1332} 1333 1334bool MenuController::GetMenuPartByScreenCoordinateImpl( 1335 SubmenuView* menu, 1336 const gfx::Point& screen_loc, 1337 MenuPart* part) { 1338 // Is the mouse over the scroll buttons? 1339 gfx::Point scroll_view_loc = screen_loc; 1340 View* scroll_view_container = menu->GetScrollViewContainer(); 1341 View::ConvertPointFromScreen(scroll_view_container, &scroll_view_loc); 1342 if (scroll_view_loc.x() < 0 || 1343 scroll_view_loc.x() >= scroll_view_container->width() || 1344 scroll_view_loc.y() < 0 || 1345 scroll_view_loc.y() >= scroll_view_container->height()) { 1346 // Point isn't contained in menu. 1347 return false; 1348 } 1349 if (IsScrollButtonAt(menu, scroll_view_loc.x(), scroll_view_loc.y(), 1350 &(part->type))) { 1351 part->submenu = menu; 1352 return true; 1353 } 1354 1355 // Not over the scroll button. Check the actual menu. 1356 if (DoesSubmenuContainLocation(menu, screen_loc)) { 1357 gfx::Point menu_loc = screen_loc; 1358 View::ConvertPointFromScreen(menu, &menu_loc); 1359 part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y()); 1360 part->type = MenuPart::MENU_ITEM; 1361 part->submenu = menu; 1362 if (!part->menu) 1363 part->parent = menu->GetMenuItem(); 1364 return true; 1365 } 1366 1367 // While the mouse isn't over a menu item or the scroll buttons of menu, it 1368 // is contained by menu and so we return true. If we didn't return true other 1369 // menus would be searched, even though they are likely obscured by us. 1370 return true; 1371} 1372 1373bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu, 1374 const gfx::Point& screen_loc) { 1375 gfx::Point view_loc = screen_loc; 1376 View::ConvertPointFromScreen(submenu, &view_loc); 1377 gfx::Rect vis_rect = submenu->GetVisibleBounds(); 1378 return vis_rect.Contains(view_loc.x(), view_loc.y()); 1379} 1380 1381void MenuController::CommitPendingSelection() { 1382 StopShowTimer(); 1383 1384 size_t paths_differ_at = 0; 1385 std::vector<MenuItemView*> current_path; 1386 std::vector<MenuItemView*> new_path; 1387 BuildPathsAndCalculateDiff(state_.item, pending_state_.item, ¤t_path, 1388 &new_path, &paths_differ_at); 1389 1390 // Hide the old menu. 1391 for (size_t i = paths_differ_at; i < current_path.size(); ++i) { 1392 if (current_path[i]->HasSubmenu()) { 1393 current_path[i]->GetSubmenu()->Hide(); 1394 } 1395 } 1396 1397 // Copy pending to state_, making sure to preserve the direction menus were 1398 // opened. 1399 std::list<bool> pending_open_direction; 1400 state_.open_leading.swap(pending_open_direction); 1401 state_ = pending_state_; 1402 state_.open_leading.swap(pending_open_direction); 1403 1404 int menu_depth = MenuDepth(state_.item); 1405 if (menu_depth == 0) { 1406 state_.open_leading.clear(); 1407 } else { 1408 int cached_size = static_cast<int>(state_.open_leading.size()); 1409 DCHECK_GE(menu_depth, 0); 1410 while (cached_size-- >= menu_depth) 1411 state_.open_leading.pop_back(); 1412 } 1413 1414 if (!state_.item) { 1415 // Nothing to select. 1416 StopScrolling(); 1417 return; 1418 } 1419 1420 // Open all the submenus preceeding the last menu item (last menu item is 1421 // handled next). 1422 if (new_path.size() > 1) { 1423 for (std::vector<MenuItemView*>::iterator i = new_path.begin(); 1424 i != new_path.end() - 1; ++i) { 1425 OpenMenu(*i); 1426 } 1427 } 1428 1429 if (state_.submenu_open) { 1430 // The submenu should be open, open the submenu if the item has a submenu. 1431 if (state_.item->HasSubmenu()) { 1432 OpenMenu(state_.item); 1433 } else { 1434 state_.submenu_open = false; 1435 } 1436 } else if (state_.item->HasSubmenu() && 1437 state_.item->GetSubmenu()->IsShowing()) { 1438 state_.item->GetSubmenu()->Hide(); 1439 } 1440 1441 if (scroll_task_.get() && scroll_task_->submenu()) { 1442 // Stop the scrolling if none of the elements of the selection contain 1443 // the menu being scrolled. 1444 bool found = false; 1445 for (MenuItemView* item = state_.item; item && !found; 1446 item = item->GetParentMenuItem()) { 1447 found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && 1448 item->GetSubmenu() == scroll_task_->submenu()); 1449 } 1450 if (!found) 1451 StopScrolling(); 1452 } 1453} 1454 1455void MenuController::CloseMenu(MenuItemView* item) { 1456 DCHECK(item); 1457 if (!item->HasSubmenu()) 1458 return; 1459 item->GetSubmenu()->Hide(); 1460} 1461 1462void MenuController::OpenMenu(MenuItemView* item) { 1463 DCHECK(item); 1464 if (item->GetSubmenu()->IsShowing()) { 1465 return; 1466 } 1467 1468 OpenMenuImpl(item, true); 1469 did_capture_ = true; 1470} 1471 1472void MenuController::OpenMenuImpl(MenuItemView* item, bool show) { 1473 // TODO(oshima|sky): Don't show the menu if drag is in progress and 1474 // this menu doesn't support drag drop. See crbug.com/110495. 1475 if (show) { 1476 int old_count = item->GetSubmenu()->child_count(); 1477 item->GetDelegate()->WillShowMenu(item); 1478 if (old_count != item->GetSubmenu()->child_count()) { 1479 // If the number of children changed then we may need to add empty items. 1480 item->AddEmptyMenus(); 1481 } 1482 } 1483 bool prefer_leading = 1484 state_.open_leading.empty() ? true : state_.open_leading.back(); 1485 bool resulting_direction; 1486 gfx::Rect bounds = MenuItemView::IsBubble(state_.anchor) ? 1487 CalculateBubbleMenuBounds(item, prefer_leading, &resulting_direction) : 1488 CalculateMenuBounds(item, prefer_leading, &resulting_direction); 1489 state_.open_leading.push_back(resulting_direction); 1490 bool do_capture = (!did_capture_ && blocking_run_); 1491 showing_submenu_ = true; 1492 if (show) { 1493 // Menus are the only place using kGroupingPropertyKey, so any value (other 1494 // than 0) is fine. 1495 const int kGroupingId = 1001; 1496 item->GetSubmenu()->ShowAt(owner_, bounds, do_capture); 1497 item->GetSubmenu()->GetWidget()->SetNativeWindowProperty( 1498 TooltipManager::kGroupingPropertyKey, 1499 reinterpret_cast<void*>(kGroupingId)); 1500 } else { 1501 item->GetSubmenu()->Reposition(bounds); 1502 } 1503 showing_submenu_ = false; 1504} 1505 1506void MenuController::MenuChildrenChanged(MenuItemView* item) { 1507 DCHECK(item); 1508 // Menu shouldn't be updated during drag operation. 1509 DCHECK(!GetActiveMouseView()); 1510 1511 // If the current item or pending item is a descendant of the item 1512 // that changed, move the selection back to the changed item. 1513 const MenuItemView* ancestor = state_.item; 1514 while (ancestor && ancestor != item) 1515 ancestor = ancestor->GetParentMenuItem(); 1516 if (!ancestor) { 1517 ancestor = pending_state_.item; 1518 while (ancestor && ancestor != item) 1519 ancestor = ancestor->GetParentMenuItem(); 1520 if (!ancestor) 1521 return; 1522 } 1523 SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1524 if (item->HasSubmenu()) 1525 OpenMenuImpl(item, false); 1526} 1527 1528void MenuController::BuildPathsAndCalculateDiff( 1529 MenuItemView* old_item, 1530 MenuItemView* new_item, 1531 std::vector<MenuItemView*>* old_path, 1532 std::vector<MenuItemView*>* new_path, 1533 size_t* first_diff_at) { 1534 DCHECK(old_path && new_path && first_diff_at); 1535 BuildMenuItemPath(old_item, old_path); 1536 BuildMenuItemPath(new_item, new_path); 1537 1538 size_t common_size = std::min(old_path->size(), new_path->size()); 1539 1540 // Find the first difference between the two paths, when the loop 1541 // returns, diff_i is the first index where the two paths differ. 1542 for (size_t i = 0; i < common_size; ++i) { 1543 if ((*old_path)[i] != (*new_path)[i]) { 1544 *first_diff_at = i; 1545 return; 1546 } 1547 } 1548 1549 *first_diff_at = common_size; 1550} 1551 1552void MenuController::BuildMenuItemPath(MenuItemView* item, 1553 std::vector<MenuItemView*>* path) { 1554 if (!item) 1555 return; 1556 BuildMenuItemPath(item->GetParentMenuItem(), path); 1557 path->push_back(item); 1558} 1559 1560void MenuController::StartShowTimer() { 1561 show_timer_.Start(FROM_HERE, 1562 TimeDelta::FromMilliseconds(menu_config_.show_delay), 1563 this, &MenuController::CommitPendingSelection); 1564} 1565 1566void MenuController::StopShowTimer() { 1567 show_timer_.Stop(); 1568} 1569 1570void MenuController::StartCancelAllTimer() { 1571 cancel_all_timer_.Start(FROM_HERE, 1572 TimeDelta::FromMilliseconds(kCloseOnExitTime), 1573 this, &MenuController::CancelAll); 1574} 1575 1576void MenuController::StopCancelAllTimer() { 1577 cancel_all_timer_.Stop(); 1578} 1579 1580gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, 1581 bool prefer_leading, 1582 bool* is_leading) { 1583 DCHECK(item); 1584 1585 SubmenuView* submenu = item->GetSubmenu(); 1586 DCHECK(submenu); 1587 1588 gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); 1589 1590 // Don't let the menu go too wide. 1591 pref.set_width(std::min(pref.width(), 1592 item->GetDelegate()->GetMaxWidthForMenu(item))); 1593 if (!state_.monitor_bounds.IsEmpty()) 1594 pref.set_width(std::min(pref.width(), state_.monitor_bounds.width())); 1595 1596 // Assume we can honor prefer_leading. 1597 *is_leading = prefer_leading; 1598 1599 int x, y; 1600 1601 const MenuConfig& menu_config = item->GetMenuConfig(); 1602 1603 if (!item->GetParentMenuItem()) { 1604 // First item, position relative to initial location. 1605 x = state_.initial_bounds.x(); 1606 1607 // Offsets for context menu prevent menu items being selected by 1608 // simply opening the menu (bug 142992). 1609 if (menu_config.offset_context_menus && state_.context_menu) 1610 x += 1; 1611 1612 y = state_.initial_bounds.bottom(); 1613 if (state_.anchor == MENU_ANCHOR_TOPRIGHT) { 1614 x = x + state_.initial_bounds.width() - pref.width(); 1615 if (menu_config.offset_context_menus && state_.context_menu) 1616 x -= 1; 1617 } else if (state_.anchor == MENU_ANCHOR_BOTTOMCENTER) { 1618 x = x - (pref.width() - state_.initial_bounds.width()) / 2; 1619 if (pref.height() > 1620 state_.initial_bounds.y() + kCenteredContextMenuYOffset) { 1621 // Menu does not fit above the anchor. We move it to below. 1622 y = state_.initial_bounds.y() - kCenteredContextMenuYOffset; 1623 } else { 1624 y = std::max(0, state_.initial_bounds.y() - pref.height()) + 1625 kCenteredContextMenuYOffset; 1626 } 1627 } 1628 1629 if (!state_.monitor_bounds.IsEmpty() && 1630 y + pref.height() > state_.monitor_bounds.bottom()) { 1631 // The menu doesn't fit fully below the button on the screen. The menu 1632 // position with respect to the bounds will be preserved if it has 1633 // already been drawn. When the requested positioning is below the bounds 1634 // it will shrink the menu to make it fit below. 1635 // If the requested positioning is best fit, it will first try to fit the 1636 // menu below. If that does not fit it will try to place it above. If 1637 // that will not fit it will place it at the bottom of the work area and 1638 // moving it off the initial_bounds region to avoid overlap. 1639 // In all other requested position styles it will be flipped above and 1640 // the height will be shrunken to the usable height. 1641 if (item->actual_menu_position() == MenuItemView::POSITION_BELOW_BOUNDS) { 1642 pref.set_height(std::min(pref.height(), 1643 state_.monitor_bounds.bottom() - y)); 1644 } else if (item->actual_menu_position() == 1645 MenuItemView::POSITION_BEST_FIT) { 1646 MenuItemView::MenuPosition orientation = 1647 MenuItemView::POSITION_BELOW_BOUNDS; 1648 if (state_.monitor_bounds.height() < pref.height()) { 1649 // Handle very tall menus. 1650 pref.set_height(state_.monitor_bounds.height()); 1651 y = state_.monitor_bounds.y(); 1652 } else if (state_.monitor_bounds.y() + pref.height() < 1653 state_.initial_bounds.y()) { 1654 // Flipping upwards if there is enough space. 1655 y = state_.initial_bounds.y() - pref.height(); 1656 orientation = MenuItemView::POSITION_ABOVE_BOUNDS; 1657 } else { 1658 // It is allowed to move the menu a bit around in order to get the 1659 // best fit and to avoid showing scroll elements. 1660 y = state_.monitor_bounds.bottom() - pref.height(); 1661 } 1662 if (orientation == MenuItemView::POSITION_BELOW_BOUNDS) { 1663 // The menu should never overlap the owning button. So move it. 1664 // We use the anchor view style to determine the preferred position 1665 // relative to the owning button. 1666 if (state_.anchor == MENU_ANCHOR_TOPLEFT) { 1667 // The menu starts with the same x coordinate as the owning button. 1668 if (x + state_.initial_bounds.width() + pref.width() > 1669 state_.monitor_bounds.right()) 1670 x -= pref.width(); // Move the menu to the left of the button. 1671 else 1672 x += state_.initial_bounds.width(); // Move the menu right. 1673 } else { 1674 // The menu should end with the same x coordinate as the owning 1675 // button. 1676 if (state_.monitor_bounds.x() > 1677 state_.initial_bounds.x() - pref.width()) 1678 x = state_.initial_bounds.right(); // Move right of the button. 1679 else 1680 x = state_.initial_bounds.x() - pref.width(); // Move left. 1681 } 1682 } 1683 item->set_actual_menu_position(orientation); 1684 } else { 1685 pref.set_height(std::min(pref.height(), 1686 state_.initial_bounds.y() - state_.monitor_bounds.y())); 1687 y = state_.initial_bounds.y() - pref.height(); 1688 item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS); 1689 } 1690 } else if (item->actual_menu_position() == 1691 MenuItemView::POSITION_ABOVE_BOUNDS) { 1692 pref.set_height(std::min(pref.height(), 1693 state_.initial_bounds.y() - state_.monitor_bounds.y())); 1694 y = state_.initial_bounds.y() - pref.height(); 1695 } else { 1696 item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS); 1697 } 1698 if (state_.monitor_bounds.width() != 0 && 1699 menu_config.offset_context_menus && state_.context_menu) { 1700 if (x + pref.width() > state_.monitor_bounds.right()) 1701 x = state_.initial_bounds.x() - pref.width() - 1; 1702 if (x < state_.monitor_bounds.x()) 1703 x = state_.monitor_bounds.x(); 1704 } 1705 } else { 1706 // Not the first menu; position it relative to the bounds of the menu 1707 // item. 1708 gfx::Point item_loc; 1709 View::ConvertPointToScreen(item, &item_loc); 1710 1711 // We must make sure we take into account the UI layout. If the layout is 1712 // RTL, then a 'leading' menu is positioned to the left of the parent menu 1713 // item and not to the right. 1714 bool layout_is_rtl = base::i18n::IsRTL(); 1715 bool create_on_the_right = (prefer_leading && !layout_is_rtl) || 1716 (!prefer_leading && layout_is_rtl); 1717 int submenu_horizontal_inset = menu_config.submenu_horizontal_inset; 1718 1719 if (create_on_the_right) { 1720 x = item_loc.x() + item->width() - submenu_horizontal_inset; 1721 if (state_.monitor_bounds.width() != 0 && 1722 x + pref.width() > state_.monitor_bounds.right()) { 1723 if (layout_is_rtl) 1724 *is_leading = true; 1725 else 1726 *is_leading = false; 1727 x = item_loc.x() - pref.width() + submenu_horizontal_inset; 1728 } 1729 } else { 1730 x = item_loc.x() - pref.width() + submenu_horizontal_inset; 1731 if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { 1732 if (layout_is_rtl) 1733 *is_leading = false; 1734 else 1735 *is_leading = true; 1736 x = item_loc.x() + item->width() - submenu_horizontal_inset; 1737 } 1738 } 1739 y = item_loc.y() - menu_config.menu_vertical_border_size; 1740 if (state_.monitor_bounds.width() != 0) { 1741 pref.set_height(std::min(pref.height(), state_.monitor_bounds.height())); 1742 if (y + pref.height() > state_.monitor_bounds.bottom()) 1743 y = state_.monitor_bounds.bottom() - pref.height(); 1744 if (y < state_.monitor_bounds.y()) 1745 y = state_.monitor_bounds.y(); 1746 } 1747 } 1748 1749 if (state_.monitor_bounds.width() != 0) { 1750 if (x + pref.width() > state_.monitor_bounds.right()) 1751 x = state_.monitor_bounds.right() - pref.width(); 1752 if (x < state_.monitor_bounds.x()) 1753 x = state_.monitor_bounds.x(); 1754 } 1755 return gfx::Rect(x, y, pref.width(), pref.height()); 1756} 1757 1758gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, 1759 bool prefer_leading, 1760 bool* is_leading) { 1761 DCHECK(item); 1762 DCHECK(!item->GetParentMenuItem()); 1763 1764 // Assume we can honor prefer_leading. 1765 *is_leading = prefer_leading; 1766 1767 SubmenuView* submenu = item->GetSubmenu(); 1768 DCHECK(submenu); 1769 1770 gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); 1771 const gfx::Rect& owner_bounds = pending_state_.initial_bounds; 1772 1773 // First the size gets reduced to the possible space. 1774 if (!state_.monitor_bounds.IsEmpty()) { 1775 int max_width = state_.monitor_bounds.width(); 1776 int max_height = state_.monitor_bounds.height(); 1777 // In case of bubbles, the maximum width is limited by the space 1778 // between the display corner and the target area + the tip size. 1779 if (state_.anchor == MENU_ANCHOR_BUBBLE_LEFT) { 1780 max_width = owner_bounds.x() - state_.monitor_bounds.x() + 1781 kBubbleTipSizeLeftRight; 1782 } else if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) { 1783 max_width = state_.monitor_bounds.right() - owner_bounds.right() + 1784 kBubbleTipSizeLeftRight; 1785 } else if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) { 1786 max_height = owner_bounds.y() - state_.monitor_bounds.y() + 1787 kBubbleTipSizeTopBottom; 1788 } else if (state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { 1789 max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() + 1790 kBubbleTipSizeTopBottom; 1791 } 1792 // The space for the menu to cover should never get empty. 1793 DCHECK_GE(max_width, kBubbleTipSizeLeftRight); 1794 DCHECK_GE(max_height, kBubbleTipSizeTopBottom); 1795 pref.set_width(std::min(pref.width(), max_width)); 1796 pref.set_height(std::min(pref.height(), max_height)); 1797 } 1798 // Also make sure that the menu does not go too wide. 1799 pref.set_width(std::min(pref.width(), 1800 item->GetDelegate()->GetMaxWidthForMenu(item))); 1801 1802 int x, y; 1803 if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE || 1804 state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { 1805 if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE) 1806 y = owner_bounds.y() - pref.height() + kBubbleTipSizeTopBottom; 1807 else 1808 y = owner_bounds.bottom() - kBubbleTipSizeTopBottom; 1809 1810 x = owner_bounds.CenterPoint().x() - pref.width() / 2; 1811 int x_old = x; 1812 if (x < state_.monitor_bounds.x()) { 1813 x = state_.monitor_bounds.x(); 1814 } else if (x + pref.width() > state_.monitor_bounds.right()) { 1815 x = state_.monitor_bounds.right() - pref.width(); 1816 } 1817 submenu->GetScrollViewContainer()->SetBubbleArrowOffset( 1818 pref.width() / 2 - x + x_old); 1819 } else { 1820 if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) 1821 x = owner_bounds.right() - kBubbleTipSizeLeftRight; 1822 else 1823 x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight; 1824 1825 y = owner_bounds.CenterPoint().y() - pref.height() / 2; 1826 int y_old = y; 1827 if (y < state_.monitor_bounds.y()) { 1828 y = state_.monitor_bounds.y(); 1829 } else if (y + pref.height() > state_.monitor_bounds.bottom()) { 1830 y = state_.monitor_bounds.bottom() - pref.height(); 1831 } 1832 submenu->GetScrollViewContainer()->SetBubbleArrowOffset( 1833 pref.height() / 2 - y + y_old); 1834 } 1835 return gfx::Rect(x, y, pref.width(), pref.height()); 1836} 1837 1838// static 1839int MenuController::MenuDepth(MenuItemView* item) { 1840 return item ? (MenuDepth(item->GetParentMenuItem()) + 1) : 0; 1841} 1842 1843void MenuController::IncrementSelection(int delta) { 1844 MenuItemView* item = pending_state_.item; 1845 DCHECK(item); 1846 if (pending_state_.submenu_open && item->HasSubmenu() && 1847 item->GetSubmenu()->IsShowing()) { 1848 // A menu is selected and open, but none of its children are selected, 1849 // select the first menu item. 1850 if (item->GetSubmenu()->GetMenuItemCount()) { 1851 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), SELECTION_DEFAULT); 1852 return; 1853 } 1854 } 1855 1856 if (item->has_children()) { 1857 CustomButton* button = GetFirstHotTrackedView(item); 1858 if (button) { 1859 button->SetHotTracked(false); 1860 View* to_make_hot = GetNextFocusableView(item, button, delta == 1); 1861 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1862 if (button_hot) { 1863 button_hot->SetHotTracked(true); 1864 return; 1865 } 1866 } else { 1867 View* to_make_hot = GetInitialFocusableView(item, delta == 1); 1868 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1869 if (button_hot) { 1870 button_hot->SetHotTracked(true); 1871 return; 1872 } 1873 } 1874 } 1875 1876 MenuItemView* parent = item->GetParentMenuItem(); 1877 if (parent) { 1878 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); 1879 if (parent_count > 1) { 1880 for (int i = 0; i < parent_count; ++i) { 1881 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { 1882 MenuItemView* to_select = 1883 FindNextSelectableMenuItem(parent, i, delta); 1884 if (!to_select) 1885 break; 1886 SetSelection(to_select, SELECTION_DEFAULT); 1887 View* to_make_hot = GetInitialFocusableView(to_select, delta == 1); 1888 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1889 if (button_hot) 1890 button_hot->SetHotTracked(true); 1891 break; 1892 } 1893 } 1894 } 1895 } 1896} 1897 1898MenuItemView* MenuController::FindNextSelectableMenuItem(MenuItemView* parent, 1899 int index, 1900 int delta) { 1901 int start_index = index; 1902 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); 1903 // Loop through the menu items skipping any invisible menus. The loop stops 1904 // when we wrap or find a visible child. 1905 do { 1906 index = (index + delta + parent_count) % parent_count; 1907 if (index == start_index) 1908 return NULL; 1909 MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); 1910 if (child->visible()) 1911 return child; 1912 } while (index != start_index); 1913 return NULL; 1914} 1915 1916void MenuController::OpenSubmenuChangeSelectionIfCan() { 1917 MenuItemView* item = pending_state_.item; 1918 if (item->HasSubmenu() && item->enabled()) { 1919 if (item->GetSubmenu()->GetMenuItemCount() > 0) { 1920 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), 1921 SELECTION_UPDATE_IMMEDIATELY); 1922 } else { 1923 // No menu items, just show the sub-menu. 1924 SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1925 } 1926 } 1927} 1928 1929void MenuController::CloseSubmenu() { 1930 MenuItemView* item = state_.item; 1931 DCHECK(item); 1932 if (!item->GetParentMenuItem()) 1933 return; 1934 if (item->HasSubmenu() && item->GetSubmenu()->IsShowing()) 1935 SetSelection(item, SELECTION_UPDATE_IMMEDIATELY); 1936 else if (item->GetParentMenuItem()->GetParentMenuItem()) 1937 SetSelection(item->GetParentMenuItem(), SELECTION_UPDATE_IMMEDIATELY); 1938} 1939 1940MenuController::SelectByCharDetails MenuController::FindChildForMnemonic( 1941 MenuItemView* parent, 1942 base::char16 key, 1943 bool (*match_function)(MenuItemView* menu, base::char16 mnemonic)) { 1944 SubmenuView* submenu = parent->GetSubmenu(); 1945 DCHECK(submenu); 1946 SelectByCharDetails details; 1947 1948 for (int i = 0, menu_item_count = submenu->GetMenuItemCount(); 1949 i < menu_item_count; ++i) { 1950 MenuItemView* child = submenu->GetMenuItemAt(i); 1951 if (child->enabled() && child->visible()) { 1952 if (child == pending_state_.item) 1953 details.index_of_item = i; 1954 if (match_function(child, key)) { 1955 if (details.first_match == -1) 1956 details.first_match = i; 1957 else 1958 details.has_multiple = true; 1959 if (details.next_match == -1 && details.index_of_item != -1 && 1960 i > details.index_of_item) 1961 details.next_match = i; 1962 } 1963 } 1964 } 1965 return details; 1966} 1967 1968bool MenuController::AcceptOrSelect(MenuItemView* parent, 1969 const SelectByCharDetails& details) { 1970 // This should only be invoked if there is a match. 1971 DCHECK(details.first_match != -1); 1972 DCHECK(parent->HasSubmenu()); 1973 SubmenuView* submenu = parent->GetSubmenu(); 1974 DCHECK(submenu); 1975 if (!details.has_multiple) { 1976 // There's only one match, activate it (or open if it has a submenu). 1977 if (submenu->GetMenuItemAt(details.first_match)->HasSubmenu()) { 1978 SetSelection(submenu->GetMenuItemAt(details.first_match), 1979 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1980 } else { 1981 Accept(submenu->GetMenuItemAt(details.first_match), 0); 1982 return true; 1983 } 1984 } else if (details.index_of_item == -1 || details.next_match == -1) { 1985 SetSelection(submenu->GetMenuItemAt(details.first_match), 1986 SELECTION_DEFAULT); 1987 } else { 1988 SetSelection(submenu->GetMenuItemAt(details.next_match), 1989 SELECTION_DEFAULT); 1990 } 1991 return false; 1992} 1993 1994bool MenuController::SelectByChar(base::char16 character) { 1995 base::char16 char_array[] = { character, 0 }; 1996 base::char16 key = base::i18n::ToLower(char_array)[0]; 1997 MenuItemView* item = pending_state_.item; 1998 if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing()) 1999 item = item->GetParentMenuItem(); 2000 DCHECK(item); 2001 DCHECK(item->HasSubmenu()); 2002 DCHECK(item->GetSubmenu()); 2003 if (item->GetSubmenu()->GetMenuItemCount() == 0) 2004 return false; 2005 2006 // Look for matches based on mnemonic first. 2007 SelectByCharDetails details = 2008 FindChildForMnemonic(item, key, &MatchesMnemonic); 2009 if (details.first_match != -1) 2010 return AcceptOrSelect(item, details); 2011 2012 if (is_combobox_) { 2013 item->GetSubmenu()->GetTextInputClient()->InsertChar(character, 0); 2014 } else { 2015 // If no mnemonics found, look at first character of titles. 2016 details = FindChildForMnemonic(item, key, &TitleMatchesMnemonic); 2017 if (details.first_match != -1) 2018 return AcceptOrSelect(item, details); 2019 } 2020 2021 return false; 2022} 2023 2024void MenuController::RepostEvent(SubmenuView* source, 2025 const ui::LocatedEvent& event) { 2026 if (!event.IsMouseEvent()) { 2027 // TODO(rbyers): Gesture event repost is tricky to get right 2028 // crbug.com/170987. 2029 DCHECK(event.IsGestureEvent()); 2030 return; 2031 } 2032 2033#if defined(OS_WIN) 2034 if (!state_.item) { 2035 // We some times get an event after closing all the menus. Ignore it. Make 2036 // sure the menu is in fact not visible. If the menu is visible, then 2037 // we're in a bad state where we think the menu isn't visibile but it is. 2038 DCHECK(!source->GetWidget()->IsVisible()); 2039 return; 2040 } 2041 2042 state_.item->GetRootMenuItem()->GetSubmenu()->ReleaseCapture(); 2043#endif 2044 2045 gfx::Point screen_loc(event.location()); 2046 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 2047 gfx::NativeView native_view = source->GetWidget()->GetNativeView(); 2048 if (!native_view) 2049 return; 2050 2051 gfx::Screen* screen = gfx::Screen::GetScreenFor(native_view); 2052 gfx::NativeWindow window = screen->GetWindowAtScreenPoint(screen_loc); 2053 2054#if defined(OS_WIN) 2055 // PostMessage() to metro windows isn't allowed (access will be denied). Don't 2056 // try to repost with Win32 if the window under the mouse press is in metro. 2057 if (!ViewsDelegate::views_delegate || 2058 !ViewsDelegate::views_delegate->IsWindowInMetro(window)) { 2059 HWND target_window = window ? HWNDForNativeWindow(window) : 2060 WindowFromPoint(screen_loc.ToPOINT()); 2061 HWND source_window = HWNDForNativeView(native_view); 2062 if (!target_window || !source_window || 2063 GetWindowThreadProcessId(source_window, NULL) != 2064 GetWindowThreadProcessId(target_window, NULL)) { 2065 // Even though we have mouse capture, windows generates a mouse event if 2066 // the other window is in a separate thread. Only repost an event if 2067 // |target_window| and |source_window| were created on the same thread, 2068 // else double events can occur and lead to bad behavior. 2069 return; 2070 } 2071 2072 // Determine whether the click was in the client area or not. 2073 // NOTE: WM_NCHITTEST coordinates are relative to the screen. 2074 LPARAM coords = MAKELPARAM(screen_loc.x(), screen_loc.y()); 2075 LRESULT nc_hit_result = SendMessage(target_window, WM_NCHITTEST, 0, coords); 2076 const bool client_area = nc_hit_result == HTCLIENT; 2077 2078 // TODO(sky): this isn't right. The event to generate should correspond with 2079 // the event we just got. MouseEvent only tells us what is down, which may 2080 // differ. Need to add ability to get changed button from MouseEvent. 2081 int event_type; 2082 int flags = event.flags(); 2083 if (flags & ui::EF_LEFT_MOUSE_BUTTON) { 2084 event_type = client_area ? WM_LBUTTONDOWN : WM_NCLBUTTONDOWN; 2085 } else if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) { 2086 event_type = client_area ? WM_MBUTTONDOWN : WM_NCMBUTTONDOWN; 2087 } else if (flags & ui::EF_RIGHT_MOUSE_BUTTON) { 2088 event_type = client_area ? WM_RBUTTONDOWN : WM_NCRBUTTONDOWN; 2089 } else { 2090 NOTREACHED(); 2091 return; 2092 } 2093 2094 int window_x = screen_loc.x(); 2095 int window_y = screen_loc.y(); 2096 if (client_area) { 2097 POINT pt = { window_x, window_y }; 2098 ScreenToClient(target_window, &pt); 2099 window_x = pt.x; 2100 window_y = pt.y; 2101 } 2102 2103 WPARAM target = client_area ? event.native_event().wParam : nc_hit_result; 2104 LPARAM window_coords = MAKELPARAM(window_x, window_y); 2105 PostMessage(target_window, event_type, target, window_coords); 2106 return; 2107 } 2108#endif 2109 // Non-Windows Aura or |window| is in metro mode. 2110 if (!window) 2111 return; 2112 2113 message_loop_->RepostEventToWindow(event, window, screen_loc); 2114} 2115 2116void MenuController::SetDropMenuItem( 2117 MenuItemView* new_target, 2118 MenuDelegate::DropPosition new_position) { 2119 if (new_target == drop_target_ && new_position == drop_position_) 2120 return; 2121 2122 if (drop_target_) { 2123 drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( 2124 NULL, MenuDelegate::DROP_NONE); 2125 } 2126 drop_target_ = new_target; 2127 drop_position_ = new_position; 2128 if (drop_target_) { 2129 drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( 2130 drop_target_, drop_position_); 2131 } 2132} 2133 2134void MenuController::UpdateScrolling(const MenuPart& part) { 2135 if (!part.is_scroll() && !scroll_task_.get()) 2136 return; 2137 2138 if (!scroll_task_.get()) 2139 scroll_task_.reset(new MenuScrollTask()); 2140 scroll_task_->Update(part); 2141} 2142 2143void MenuController::StopScrolling() { 2144 scroll_task_.reset(NULL); 2145} 2146 2147void MenuController::UpdateActiveMouseView(SubmenuView* event_source, 2148 const ui::MouseEvent& event, 2149 View* target_menu) { 2150 View* target = NULL; 2151 gfx::Point target_menu_loc(event.location()); 2152 if (target_menu && target_menu->has_children()) { 2153 // Locate the deepest child view to send events to. This code assumes we 2154 // don't have to walk up the tree to find a view interested in events. This 2155 // is currently true for the cases we are embedding views, but if we embed 2156 // more complex hierarchies it'll need to change. 2157 View::ConvertPointToScreen(event_source->GetScrollViewContainer(), 2158 &target_menu_loc); 2159 View::ConvertPointFromScreen(target_menu, &target_menu_loc); 2160 target = target_menu->GetEventHandlerForPoint(target_menu_loc); 2161 if (target == target_menu || !target->enabled()) 2162 target = NULL; 2163 } 2164 View* active_mouse_view = GetActiveMouseView(); 2165 if (target != active_mouse_view) { 2166 SendMouseCaptureLostToActiveView(); 2167 active_mouse_view = target; 2168 SetActiveMouseView(active_mouse_view); 2169 if (active_mouse_view) { 2170 gfx::Point target_point(target_menu_loc); 2171 View::ConvertPointToTarget( 2172 target_menu, active_mouse_view, &target_point); 2173 ui::MouseEvent mouse_entered_event(ui::ET_MOUSE_ENTERED, 2174 target_point, target_point, 2175 0, 0); 2176 active_mouse_view->OnMouseEntered(mouse_entered_event); 2177 2178 ui::MouseEvent mouse_pressed_event(ui::ET_MOUSE_PRESSED, 2179 target_point, target_point, 2180 event.flags(), 2181 event.changed_button_flags()); 2182 active_mouse_view->OnMousePressed(mouse_pressed_event); 2183 } 2184 } 2185 2186 if (active_mouse_view) { 2187 gfx::Point target_point(target_menu_loc); 2188 View::ConvertPointToTarget(target_menu, active_mouse_view, &target_point); 2189 ui::MouseEvent mouse_dragged_event(ui::ET_MOUSE_DRAGGED, 2190 target_point, target_point, 2191 event.flags(), 2192 event.changed_button_flags()); 2193 active_mouse_view->OnMouseDragged(mouse_dragged_event); 2194 } 2195} 2196 2197void MenuController::SendMouseReleaseToActiveView(SubmenuView* event_source, 2198 const ui::MouseEvent& event) { 2199 View* active_mouse_view = GetActiveMouseView(); 2200 if (!active_mouse_view) 2201 return; 2202 2203 gfx::Point target_loc(event.location()); 2204 View::ConvertPointToScreen(event_source->GetScrollViewContainer(), 2205 &target_loc); 2206 View::ConvertPointFromScreen(active_mouse_view, &target_loc); 2207 ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, target_loc, target_loc, 2208 event.flags(), event.changed_button_flags()); 2209 // Reset active mouse view before sending mouse released. That way if it calls 2210 // back to us, we aren't in a weird state. 2211 SetActiveMouseView(NULL); 2212 active_mouse_view->OnMouseReleased(release_event); 2213} 2214 2215void MenuController::SendMouseCaptureLostToActiveView() { 2216 View* active_mouse_view = GetActiveMouseView(); 2217 if (!active_mouse_view) 2218 return; 2219 2220 // Reset the active_mouse_view_ before sending mouse capture lost. That way if 2221 // it calls back to us, we aren't in a weird state. 2222 SetActiveMouseView(NULL); 2223 active_mouse_view->OnMouseCaptureLost(); 2224} 2225 2226void MenuController::SetActiveMouseView(View* view) { 2227 if (view) 2228 ViewStorage::GetInstance()->StoreView(active_mouse_view_id_, view); 2229 else 2230 ViewStorage::GetInstance()->RemoveView(active_mouse_view_id_); 2231} 2232 2233View* MenuController::GetActiveMouseView() { 2234 return ViewStorage::GetInstance()->RetrieveView(active_mouse_view_id_); 2235} 2236 2237void MenuController::SetExitType(ExitType type) { 2238 exit_type_ = type; 2239 // Exit nested message loops as soon as possible. We do this as 2240 // MessagePumpDispatcher is only invoked before native events, which means 2241 // its entirely possible for a Widget::CloseNow() task to be processed before 2242 // the next native message. We quite the nested message loop as soon as 2243 // possible to avoid having deleted views classes (such as widgets and 2244 // rootviews) on the stack when the nested message loop stops. 2245 // 2246 // It's safe to invoke QuitNestedMessageLoop() multiple times, it only effects 2247 // the current loop. 2248 bool quit_now = message_loop_->ShouldQuitNow() && exit_type_ != EXIT_NONE && 2249 message_loop_depth_; 2250 if (quit_now) 2251 TerminateNestedMessageLoop(); 2252} 2253 2254void MenuController::TerminateNestedMessageLoop() { 2255 message_loop_->QuitNow(); 2256} 2257 2258void MenuController::HandleMouseLocation(SubmenuView* source, 2259 const gfx::Point& mouse_location) { 2260 if (showing_submenu_) 2261 return; 2262 2263 // Ignore mouse events if we're closing the menu. 2264 if (exit_type_ != EXIT_NONE) 2265 return; 2266 2267 MenuPart part = GetMenuPart(source, mouse_location); 2268 2269 UpdateScrolling(part); 2270 2271 if (!blocking_run_) 2272 return; 2273 2274 if (part.type == MenuPart::NONE && ShowSiblingMenu(source, mouse_location)) 2275 return; 2276 2277 if (part.type == MenuPart::MENU_ITEM && part.menu) { 2278 SetSelection(part.menu, SELECTION_OPEN_SUBMENU); 2279 } else if (!part.is_scroll() && pending_state_.item && 2280 pending_state_.item->GetParentMenuItem() && 2281 (!pending_state_.item->HasSubmenu() || 2282 !pending_state_.item->GetSubmenu()->IsShowing())) { 2283 // On exit if the user hasn't selected an item with a submenu, move the 2284 // selection back to the parent menu item. 2285 SetSelection(pending_state_.item->GetParentMenuItem(), 2286 SELECTION_OPEN_SUBMENU); 2287 } 2288} 2289 2290gfx::Screen* MenuController::GetScreen() { 2291 Widget* root = owner_ ? owner_->GetTopLevelWidget() : NULL; 2292 return root ? gfx::Screen::GetScreenFor(root->GetNativeView()) 2293 : gfx::Screen::GetNativeScreen(); 2294} 2295 2296} // namespace views 2297