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