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 57 using base::Time; 58 using base::TimeDelta; 59 using ui::OSExchangeData; 60 61 // Period of the scroll timer (in milliseconds). 62 static const int kScrollTimerMS = 30; 63 64 // Amount of time from when the drop exits the menu and the menu is hidden. 65 static 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. 69 static const int kCenteredContextMenuYOffset = -15; 70 71 namespace views { 72 73 namespace { 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. 78 static int menu_selection_hold_time_ms = kMinimumMsPressedToActivate; 79 80 // The spacing offset for the bubble tip. 81 const int kBubbleTipSizeLeftRight = 12; 82 const 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). 87 const float kMaximumLengthMovedToActivate = 4.0f; 88 89 // Returns true if the mnemonic of |menu| matches key. 90 bool MatchesMnemonic(MenuItemView* menu, 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|. 96 bool TitleMatchesMnemonic(MenuItemView* menu, char16 key) { 97 if (menu->GetMnemonic()) 98 return false; 99 100 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. 107 static 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. 129 static 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. 147 static 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|. 153 static 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 179 class 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 254 struct 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 279 MenuController::State::State() 280 : item(NULL), 281 submenu_open(false), 282 anchor(MenuItemView::TOPLEFT), 283 context_menu(false) {} 284 285 MenuController::State::~State() {} 286 287 // MenuController ------------------------------------------------------------ 288 289 // static 290 MenuController* MenuController::active_instance_ = NULL; 291 292 // static 293 MenuController* MenuController::GetActiveInstance() { 294 return active_instance_; 295 } 296 297 MenuItemView* 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) && defined(USE_AURA) 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 453 void 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 487 void MenuController::OnMousePressed(SubmenuView* source, 488 const ui::MouseEvent& event) { 489 SetSelectionOnPointerDown(source, event); 490 } 491 492 void 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 518 void 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 588 void MenuController::OnMouseMoved(SubmenuView* source, 589 const ui::MouseEvent& event) { 590 HandleMouseLocation(source, event.location()); 591 } 592 593 void 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 599 #if defined(USE_AURA) 600 bool MenuController::OnMouseWheel(SubmenuView* source, 601 const ui::MouseWheelEvent& event) { 602 MenuPart part = GetMenuPart(source, event.location()); 603 return part.submenu && part.submenu->OnMouseWheel(event); 604 } 605 #endif 606 607 void MenuController::OnGestureEvent(SubmenuView* source, 608 ui::GestureEvent* event) { 609 MenuPart part = GetMenuPart(source, event->location()); 610 if (event->type() == ui::ET_GESTURE_TAP_DOWN) { 611 SetSelectionOnPointerDown(source, *event); 612 event->StopPropagation(); 613 } else if (event->type() == ui::ET_GESTURE_LONG_PRESS) { 614 if (part.type == MenuPart::MENU_ITEM && part.menu) { 615 if (ShowContextMenu(part.menu, source, *event, ui::MENU_SOURCE_TOUCH)) 616 event->StopPropagation(); 617 } 618 } else if (event->type() == ui::ET_GESTURE_TAP) { 619 if (!part.is_scroll() && part.menu && 620 !(part.menu->HasSubmenu())) { 621 if (part.menu->GetDelegate()->IsTriggerableEvent( 622 part.menu, *event)) { 623 Accept(part.menu, event->flags()); 624 item_selected_by_touch_ = true; 625 } 626 event->StopPropagation(); 627 } else if (part.type == MenuPart::MENU_ITEM) { 628 // User either tapped on empty space, or a menu that has children. 629 SetSelection(part.menu ? part.menu : state_.item, 630 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 631 event->StopPropagation(); 632 } 633 } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL && 634 part.menu && 635 part.type == MenuPart::MENU_ITEM) { 636 // Move the selection to the parent menu so that the selection in the 637 // current menu is unset. Make sure the submenu remains open by sending the 638 // appropriate SetSelectionTypes flags. 639 SetSelection(part.menu->GetParentMenuItem(), 640 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 641 event->StopPropagation(); 642 } 643 644 if (event->stopped_propagation()) 645 return; 646 647 if (!part.submenu) 648 return; 649 part.submenu->OnGestureEvent(event); 650 } 651 652 bool MenuController::GetDropFormats( 653 SubmenuView* source, 654 int* formats, 655 std::set<OSExchangeData::CustomFormat>* custom_formats) { 656 return source->GetMenuItem()->GetDelegate()->GetDropFormats( 657 source->GetMenuItem(), formats, custom_formats); 658 } 659 660 bool MenuController::AreDropTypesRequired(SubmenuView* source) { 661 return source->GetMenuItem()->GetDelegate()->AreDropTypesRequired( 662 source->GetMenuItem()); 663 } 664 665 bool MenuController::CanDrop(SubmenuView* source, const OSExchangeData& data) { 666 return source->GetMenuItem()->GetDelegate()->CanDrop(source->GetMenuItem(), 667 data); 668 } 669 670 void MenuController::OnDragEntered(SubmenuView* source, 671 const ui::DropTargetEvent& event) { 672 valid_drop_coordinates_ = false; 673 } 674 675 int MenuController::OnDragUpdated(SubmenuView* source, 676 const ui::DropTargetEvent& event) { 677 StopCancelAllTimer(); 678 679 gfx::Point screen_loc(event.location()); 680 View::ConvertPointToScreen(source, &screen_loc); 681 if (valid_drop_coordinates_ && screen_loc == drop_pt_) 682 return last_drop_operation_; 683 drop_pt_ = screen_loc; 684 valid_drop_coordinates_ = true; 685 686 MenuItemView* menu_item = GetMenuItemAt(source, event.x(), event.y()); 687 bool over_empty_menu = false; 688 if (!menu_item) { 689 // See if we're over an empty menu. 690 menu_item = GetEmptyMenuItemAt(source, event.x(), event.y()); 691 if (menu_item) 692 over_empty_menu = true; 693 } 694 MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE; 695 int drop_operation = ui::DragDropTypes::DRAG_NONE; 696 if (menu_item) { 697 gfx::Point menu_item_loc(event.location()); 698 View::ConvertPointToTarget(source, menu_item, &menu_item_loc); 699 MenuItemView* query_menu_item; 700 if (!over_empty_menu) { 701 int menu_item_height = menu_item->height(); 702 if (menu_item->HasSubmenu() && 703 (menu_item_loc.y() > kDropBetweenPixels && 704 menu_item_loc.y() < (menu_item_height - kDropBetweenPixels))) { 705 drop_position = MenuDelegate::DROP_ON; 706 } else { 707 drop_position = (menu_item_loc.y() < menu_item_height / 2) ? 708 MenuDelegate::DROP_BEFORE : MenuDelegate::DROP_AFTER; 709 } 710 query_menu_item = menu_item; 711 } else { 712 query_menu_item = menu_item->GetParentMenuItem(); 713 drop_position = MenuDelegate::DROP_ON; 714 } 715 drop_operation = menu_item->GetDelegate()->GetDropOperation( 716 query_menu_item, event, &drop_position); 717 718 // If the menu has a submenu, schedule the submenu to open. 719 SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU : 720 SELECTION_DEFAULT); 721 722 if (drop_position == MenuDelegate::DROP_NONE || 723 drop_operation == ui::DragDropTypes::DRAG_NONE) 724 menu_item = NULL; 725 } else { 726 SetSelection(source->GetMenuItem(), SELECTION_OPEN_SUBMENU); 727 } 728 SetDropMenuItem(menu_item, drop_position); 729 last_drop_operation_ = drop_operation; 730 return drop_operation; 731 } 732 733 void MenuController::OnDragExited(SubmenuView* source) { 734 StartCancelAllTimer(); 735 736 if (drop_target_) { 737 StopShowTimer(); 738 SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); 739 } 740 } 741 742 int MenuController::OnPerformDrop(SubmenuView* source, 743 const ui::DropTargetEvent& event) { 744 DCHECK(drop_target_); 745 // NOTE: the delegate may delete us after invoking OnPerformDrop, as such 746 // we don't call cancel here. 747 748 MenuItemView* item = state_.item; 749 DCHECK(item); 750 751 MenuItemView* drop_target = drop_target_; 752 MenuDelegate::DropPosition drop_position = drop_position_; 753 754 // Close all menus, including any nested menus. 755 SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); 756 CloseAllNestedMenus(); 757 758 // Set state such that we exit. 759 showing_ = false; 760 SetExitType(EXIT_ALL); 761 762 // If over an empty menu item, drop occurs on the parent. 763 if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID) 764 drop_target = drop_target->GetParentMenuItem(); 765 766 if (!IsBlockingRun()) { 767 delegate_->DropMenuClosed( 768 internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE, 769 item->GetRootMenuItem()); 770 } 771 772 // WARNING: the call to MenuClosed deletes us. 773 774 return drop_target->GetDelegate()->OnPerformDrop( 775 drop_target, drop_position, event); 776 } 777 778 void MenuController::OnDragEnteredScrollButton(SubmenuView* source, 779 bool is_up) { 780 MenuPart part; 781 part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN; 782 part.submenu = source; 783 UpdateScrolling(part); 784 785 // Do this to force the selection to hide. 786 SetDropMenuItem(source->GetMenuItemAt(0), MenuDelegate::DROP_NONE); 787 788 StopCancelAllTimer(); 789 } 790 791 void MenuController::OnDragExitedScrollButton(SubmenuView* source) { 792 StartCancelAllTimer(); 793 SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); 794 StopScrolling(); 795 } 796 797 void MenuController::UpdateSubmenuSelection(SubmenuView* submenu) { 798 if (submenu->IsShowing()) { 799 gfx::Point point = GetScreen()->GetCursorScreenPoint(); 800 const SubmenuView* root_submenu = 801 submenu->GetMenuItem()->GetRootMenuItem()->GetSubmenu(); 802 View::ConvertPointFromScreen( 803 root_submenu->GetWidget()->GetRootView(), &point); 804 HandleMouseLocation(submenu, point); 805 } 806 } 807 808 void MenuController::OnWidgetDestroying(Widget* widget) { 809 DCHECK_EQ(owner_, widget); 810 owner_->RemoveObserver(this); 811 owner_ = NULL; 812 } 813 814 // static 815 void MenuController::TurnOffMenuSelectionHoldForTest() { 816 menu_selection_hold_time_ms = -1; 817 } 818 819 void MenuController::SetSelection(MenuItemView* menu_item, 820 int selection_types) { 821 size_t paths_differ_at = 0; 822 std::vector<MenuItemView*> current_path; 823 std::vector<MenuItemView*> new_path; 824 BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, 825 &new_path, &paths_differ_at); 826 827 size_t current_size = current_path.size(); 828 size_t new_size = new_path.size(); 829 830 bool pending_item_changed = pending_state_.item != menu_item; 831 if (pending_item_changed && pending_state_.item) { 832 CustomButton* button = GetFirstHotTrackedView(pending_state_.item); 833 if (button) 834 button->SetHotTracked(false); 835 } 836 837 // Notify the old path it isn't selected. 838 MenuDelegate* current_delegate = 839 current_path.empty() ? NULL : current_path.front()->GetDelegate(); 840 for (size_t i = paths_differ_at; i < current_size; ++i) { 841 if (current_delegate && 842 current_path[i]->GetType() == MenuItemView::SUBMENU) { 843 current_delegate->WillHideMenu(current_path[i]); 844 } 845 current_path[i]->SetSelected(false); 846 } 847 848 // Notify the new path it is selected. 849 for (size_t i = paths_differ_at; i < new_size; ++i) { 850 new_path[i]->ScrollRectToVisible(new_path[i]->GetLocalBounds()); 851 new_path[i]->SetSelected(true); 852 } 853 854 if (menu_item && menu_item->GetDelegate()) 855 menu_item->GetDelegate()->SelectionChanged(menu_item); 856 857 DCHECK(menu_item || (selection_types & SELECTION_EXIT) != 0); 858 859 pending_state_.item = menu_item; 860 pending_state_.submenu_open = (selection_types & SELECTION_OPEN_SUBMENU) != 0; 861 862 // Stop timers. 863 StopCancelAllTimer(); 864 // Resets show timer only when pending menu item is changed. 865 if (pending_item_changed) 866 StopShowTimer(); 867 868 if (selection_types & SELECTION_UPDATE_IMMEDIATELY) 869 CommitPendingSelection(); 870 else if (pending_item_changed) 871 StartShowTimer(); 872 873 // Notify an accessibility focus event on all menu items except for the root. 874 if (menu_item && 875 (MenuDepth(menu_item) != 1 || 876 menu_item->GetType() != MenuItemView::SUBMENU)) { 877 menu_item->NotifyAccessibilityEvent( 878 ui::AccessibilityTypes::EVENT_FOCUS, true); 879 } 880 } 881 882 void MenuController::SetSelectionOnPointerDown(SubmenuView* source, 883 const ui::LocatedEvent& event) { 884 if (!blocking_run_) 885 return; 886 887 DCHECK(!GetActiveMouseView()); 888 889 MenuPart part = GetMenuPart(source, event.location()); 890 if (part.is_scroll()) 891 return; // Ignore presses on scroll buttons. 892 893 // When this menu is opened through a touch event, a simulated right-click 894 // is sent before the menu appears. Ignore it. 895 if ((event.flags() & ui::EF_RIGHT_MOUSE_BUTTON) && 896 (event.flags() & ui::EF_FROM_TOUCH)) 897 return; 898 899 if (part.type == MenuPart::NONE || 900 (part.type == MenuPart::MENU_ITEM && part.menu && 901 part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { 902 // Remember the time when we repost the event. The owner can then use this 903 // to figure out if this menu was finished with the same click which is 904 // sent to it thereafter. Note that the time stamp front he event cannot be 905 // used since the reposting will set a new timestamp when the event gets 906 // processed. As such it is better to take the current time which will be 907 // closer to the time when it arrives again in the menu handler. 908 closing_event_time_ = ui::EventTimeForNow(); 909 910 // Mouse wasn't pressed over any menu, or the active menu, cancel. 911 912 #if defined(OS_WIN) 913 // We're going to close and we own the mouse capture. We need to repost the 914 // mouse down, otherwise the window the user clicked on won't get the event. 915 if (!state_.item) { 916 // We some times get an event after closing all the menus. Ignore it. Make 917 // sure the menu is in fact not visible. If the menu is visible, then 918 // we're in a bad state where we think the menu isn't visibile but it is. 919 DCHECK(!source->GetWidget()->IsVisible()); 920 } else { 921 RepostEvent(source, event); 922 } 923 #endif 924 925 // And close. 926 ExitType exit_type = EXIT_ALL; 927 if (!menu_stack_.empty()) { 928 // We're running nested menus. Only exit all if the mouse wasn't over one 929 // of the menus from the last run. 930 gfx::Point screen_loc(event.location()); 931 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 932 MenuPart last_part = GetMenuPartByScreenCoordinateUsingMenu( 933 menu_stack_.back().item, screen_loc); 934 if (last_part.type != MenuPart::NONE) 935 exit_type = EXIT_OUTERMOST; 936 } 937 Cancel(exit_type); 938 939 #if defined(USE_AURA) && !defined(OS_WIN) 940 // We're going to exit the menu and want to repost the event so that is 941 // is handled normally after the context menu has exited. We call 942 // RepostEvent after Cancel so that mouse capture has been released so 943 // that finding the event target is unaffected by the current capture. 944 RepostEvent(source, event); 945 #endif 946 947 return; 948 } 949 950 // On a press we immediately commit the selection, that way a submenu 951 // pops up immediately rather than after a delay. 952 int selection_types = SELECTION_UPDATE_IMMEDIATELY; 953 if (!part.menu) { 954 part.menu = part.parent; 955 selection_types |= SELECTION_OPEN_SUBMENU; 956 } else { 957 if (part.menu->GetDelegate()->CanDrag(part.menu)) { 958 possible_drag_ = true; 959 press_pt_ = event.location(); 960 } 961 if (part.menu->HasSubmenu()) 962 selection_types |= SELECTION_OPEN_SUBMENU; 963 } 964 SetSelection(part.menu, selection_types); 965 } 966 967 void MenuController::StartDrag(SubmenuView* source, 968 const gfx::Point& location) { 969 MenuItemView* item = state_.item; 970 DCHECK(item); 971 // Points are in the coordinates of the submenu, need to map to that of 972 // the selected item. Additionally source may not be the parent of 973 // the selected item, so need to map to screen first then to item. 974 gfx::Point press_loc(location); 975 View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc); 976 View::ConvertPointFromScreen(item, &press_loc); 977 gfx::Point widget_loc(press_loc); 978 View::ConvertPointToWidget(item, &widget_loc); 979 scoped_ptr<gfx::Canvas> canvas(GetCanvasForDragImage( 980 source->GetWidget(), gfx::Size(item->width(), item->height()))); 981 item->PaintButton(canvas.get(), MenuItemView::PB_FOR_DRAG); 982 983 OSExchangeData data; 984 item->GetDelegate()->WriteDragData(item, &data); 985 drag_utils::SetDragImageOnDataObject(*canvas, item->size(), 986 press_loc.OffsetFromOrigin(), 987 &data); 988 StopScrolling(); 989 int drag_ops = item->GetDelegate()->GetDragOperations(item); 990 drag_in_progress_ = true; 991 // TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below. 992 item->GetWidget()->RunShellDrag(NULL, data, widget_loc, drag_ops, 993 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE); 994 drag_in_progress_ = false; 995 996 if (GetActiveInstance() == this) { 997 if (showing_) { 998 // We're still showing, close all menus. 999 CloseAllNestedMenus(); 1000 Cancel(EXIT_ALL); 1001 } // else case, drop was on us. 1002 } // else case, someone canceled us, don't do anything 1003 } 1004 1005 #if defined(OS_WIN) 1006 bool MenuController::Dispatch(const MSG& msg) { 1007 DCHECK(blocking_run_); 1008 1009 if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) { 1010 // We must translate/dispatch the message here, otherwise we would drop 1011 // the message on the floor. 1012 TranslateMessage(&msg); 1013 DispatchMessage(&msg); 1014 return false; 1015 } 1016 1017 // NOTE: we don't get WM_ACTIVATE or anything else interesting in here. 1018 switch (msg.message) { 1019 case WM_CONTEXTMENU: { 1020 MenuItemView* item = pending_state_.item; 1021 if (item && item->GetRootMenuItem() != item) { 1022 gfx::Point screen_loc(0, item->height()); 1023 View::ConvertPointToScreen(item, &screen_loc); 1024 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; 1025 if (GET_X_LPARAM(msg.lParam) == -1 && GET_Y_LPARAM(msg.lParam) == -1) 1026 source_type = ui::MENU_SOURCE_KEYBOARD; 1027 item->GetDelegate()->ShowContextMenu(item, item->GetCommand(), 1028 screen_loc, source_type); 1029 } 1030 return true; 1031 } 1032 1033 // NOTE: focus wasn't changed when the menu was shown. As such, don't 1034 // dispatch key events otherwise the focused window will get the events. 1035 case WM_KEYDOWN: { 1036 bool result = OnKeyDown(ui::KeyboardCodeFromNative(msg)); 1037 TranslateMessage(&msg); 1038 return result; 1039 } 1040 case WM_CHAR: 1041 return !SelectByChar(static_cast<char16>(msg.wParam)); 1042 case WM_KEYUP: 1043 return true; 1044 1045 case WM_SYSKEYUP: 1046 // We may have been shown on a system key, as such don't do anything 1047 // here. If another system key is pushed we'll get a WM_SYSKEYDOWN and 1048 // close the menu. 1049 return true; 1050 1051 case WM_CANCELMODE: 1052 case WM_SYSKEYDOWN: 1053 // Exit immediately on system keys. 1054 Cancel(EXIT_ALL); 1055 return false; 1056 1057 default: 1058 break; 1059 } 1060 TranslateMessage(&msg); 1061 DispatchMessage(&msg); 1062 return exit_type_ == EXIT_NONE; 1063 } 1064 #elif defined(USE_AURA) 1065 bool MenuController::Dispatch(const base::NativeEvent& event) { 1066 if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) { 1067 aura::Env::GetInstance()->GetDispatcher()->Dispatch(event); 1068 return false; 1069 } 1070 // Activates mnemonics only when it it pressed without modifiers except for 1071 // caps and shift. 1072 int flags = ui::EventFlagsFromNative(event) & 1073 ~ui::EF_CAPS_LOCK_DOWN & ~ui::EF_SHIFT_DOWN; 1074 if (flags == ui::EF_NONE) { 1075 switch (ui::EventTypeFromNative(event)) { 1076 case ui::ET_KEY_PRESSED: 1077 if (!OnKeyDown(ui::KeyboardCodeFromNative(event))) 1078 return false; 1079 1080 return !SelectByChar(ui::KeyboardCodeFromNative(event)); 1081 case ui::ET_KEY_RELEASED: 1082 return true; 1083 default: 1084 break; 1085 } 1086 } 1087 1088 aura::Env::GetInstance()->GetDispatcher()->Dispatch(event); 1089 return exit_type_ == EXIT_NONE; 1090 } 1091 #endif 1092 1093 bool MenuController::OnKeyDown(ui::KeyboardCode key_code) { 1094 DCHECK(blocking_run_); 1095 1096 switch (key_code) { 1097 case ui::VKEY_UP: 1098 IncrementSelection(-1); 1099 break; 1100 1101 case ui::VKEY_DOWN: 1102 IncrementSelection(1); 1103 break; 1104 1105 // Handling of VK_RIGHT and VK_LEFT is different depending on the UI 1106 // layout. 1107 case ui::VKEY_RIGHT: 1108 if (base::i18n::IsRTL()) 1109 CloseSubmenu(); 1110 else 1111 OpenSubmenuChangeSelectionIfCan(); 1112 break; 1113 1114 case ui::VKEY_LEFT: 1115 if (base::i18n::IsRTL()) 1116 OpenSubmenuChangeSelectionIfCan(); 1117 else 1118 CloseSubmenu(); 1119 break; 1120 1121 case ui::VKEY_SPACE: 1122 if (SendAcceleratorToHotTrackedView() == ACCELERATOR_PROCESSED_EXIT) 1123 return false; 1124 break; 1125 1126 case ui::VKEY_F4: 1127 if (!accept_on_f4_) 1128 break; 1129 // Fallthrough to accept on F4, so combobox menus match Windows behavior. 1130 case ui::VKEY_RETURN: 1131 if (pending_state_.item) { 1132 if (pending_state_.item->HasSubmenu()) { 1133 OpenSubmenuChangeSelectionIfCan(); 1134 } else { 1135 SendAcceleratorResultType result = SendAcceleratorToHotTrackedView(); 1136 if (result == ACCELERATOR_NOT_PROCESSED && 1137 pending_state_.item->enabled()) { 1138 Accept(pending_state_.item, 0); 1139 return false; 1140 } else if (result == ACCELERATOR_PROCESSED_EXIT) { 1141 return false; 1142 } 1143 } 1144 } 1145 break; 1146 1147 case ui::VKEY_ESCAPE: 1148 if (!state_.item->GetParentMenuItem() || 1149 (!state_.item->GetParentMenuItem()->GetParentMenuItem() && 1150 (!state_.item->HasSubmenu() || 1151 !state_.item->GetSubmenu()->IsShowing()))) { 1152 // User pressed escape and only one menu is shown, cancel it. 1153 Cancel(EXIT_OUTERMOST); 1154 return false; 1155 } 1156 CloseSubmenu(); 1157 break; 1158 1159 #if defined(OS_WIN) 1160 case VK_APPS: 1161 break; 1162 #endif 1163 1164 default: 1165 break; 1166 } 1167 return true; 1168 } 1169 1170 MenuController::MenuController(ui::NativeTheme* theme, 1171 bool blocking, 1172 internal::MenuControllerDelegate* delegate) 1173 : blocking_run_(blocking), 1174 showing_(false), 1175 exit_type_(EXIT_NONE), 1176 did_capture_(false), 1177 result_(NULL), 1178 accept_event_flags_(0), 1179 drop_target_(NULL), 1180 drop_position_(MenuDelegate::DROP_UNKNOWN), 1181 owner_(NULL), 1182 possible_drag_(false), 1183 drag_in_progress_(false), 1184 valid_drop_coordinates_(false), 1185 last_drop_operation_(MenuDelegate::DROP_UNKNOWN), 1186 showing_submenu_(false), 1187 menu_button_(NULL), 1188 active_mouse_view_id_(ViewStorage::GetInstance()->CreateStorageID()), 1189 delegate_(delegate), 1190 message_loop_depth_(0), 1191 menu_config_(theme), 1192 closing_event_time_(base::TimeDelta()), 1193 menu_start_time_(base::TimeTicks()), 1194 accept_on_f4_(false), 1195 item_selected_by_touch_(false) { 1196 active_instance_ = this; 1197 } 1198 1199 MenuController::~MenuController() { 1200 DCHECK(!showing_); 1201 if (owner_) 1202 owner_->RemoveObserver(this); 1203 if (active_instance_ == this) 1204 active_instance_ = NULL; 1205 StopShowTimer(); 1206 StopCancelAllTimer(); 1207 } 1208 1209 MenuController::SendAcceleratorResultType 1210 MenuController::SendAcceleratorToHotTrackedView() { 1211 CustomButton* hot_view = GetFirstHotTrackedView(pending_state_.item); 1212 if (!hot_view) 1213 return ACCELERATOR_NOT_PROCESSED; 1214 1215 ui::Accelerator accelerator(ui::VKEY_RETURN, ui::EF_NONE); 1216 hot_view->AcceleratorPressed(accelerator); 1217 CustomButton* button = static_cast<CustomButton*>(hot_view); 1218 button->SetHotTracked(true); 1219 return (exit_type_ == EXIT_NONE) ? 1220 ACCELERATOR_PROCESSED : ACCELERATOR_PROCESSED_EXIT; 1221 } 1222 1223 void MenuController::UpdateInitialLocation( 1224 const gfx::Rect& bounds, 1225 MenuItemView::AnchorPosition position, 1226 bool context_menu) { 1227 pending_state_.context_menu = context_menu; 1228 pending_state_.initial_bounds = bounds; 1229 if (bounds.height() > 1) { 1230 // Inset the bounds slightly, otherwise drag coordinates don't line up 1231 // nicely and menus close prematurely. 1232 pending_state_.initial_bounds.Inset(0, 1); 1233 } 1234 1235 // Reverse anchor position for RTL languages. 1236 if (base::i18n::IsRTL() && 1237 (position == MenuItemView::TOPRIGHT || 1238 position == MenuItemView::TOPLEFT)) { 1239 pending_state_.anchor = position == MenuItemView::TOPRIGHT ? 1240 MenuItemView::TOPLEFT : MenuItemView::TOPRIGHT; 1241 } else { 1242 pending_state_.anchor = position; 1243 } 1244 1245 // Calculate the bounds of the monitor we'll show menus on. Do this once to 1246 // avoid repeated system queries for the info. 1247 pending_state_.monitor_bounds = GetScreen()->GetDisplayNearestPoint( 1248 bounds.origin()).work_area(); 1249 #if defined(USE_ASH) 1250 if (!pending_state_.monitor_bounds.Contains(bounds)) { 1251 // Use the monitor area if the work area doesn't contain the bounds. This 1252 // handles showing a menu from the launcher. 1253 gfx::Rect monitor_area = GetScreen()->GetDisplayNearestPoint( 1254 bounds.origin()).bounds(); 1255 if (monitor_area.Contains(bounds)) 1256 pending_state_.monitor_bounds = monitor_area; 1257 } 1258 #endif 1259 } 1260 1261 void MenuController::Accept(MenuItemView* item, int event_flags) { 1262 DCHECK(IsBlockingRun()); 1263 result_ = item; 1264 if (item && !menu_stack_.empty() && 1265 !item->GetDelegate()->ShouldCloseAllMenusOnExecute(item->GetCommand())) { 1266 SetExitType(EXIT_OUTERMOST); 1267 } else { 1268 SetExitType(EXIT_ALL); 1269 } 1270 accept_event_flags_ = event_flags; 1271 } 1272 1273 bool MenuController::ShowSiblingMenu(SubmenuView* source, 1274 const gfx::Point& mouse_location) { 1275 if (!menu_stack_.empty() || !menu_button_) 1276 return false; 1277 1278 View* source_view = source->GetScrollViewContainer(); 1279 if (mouse_location.x() >= 0 && 1280 mouse_location.x() < source_view->width() && 1281 mouse_location.y() >= 0 && 1282 mouse_location.y() < source_view->height()) { 1283 // The mouse is over the menu, no need to continue. 1284 return false; 1285 } 1286 1287 gfx::NativeWindow window_under_mouse = GetScreen()->GetWindowUnderCursor(); 1288 // TODO(oshima): Replace with views only API. 1289 if (!owner_ || window_under_mouse != owner_->GetNativeWindow()) 1290 return false; 1291 1292 // The user moved the mouse outside the menu and over the owning window. See 1293 // if there is a sibling menu we should show. 1294 gfx::Point screen_point(mouse_location); 1295 View::ConvertPointToScreen(source_view, &screen_point); 1296 MenuItemView::AnchorPosition anchor; 1297 bool has_mnemonics; 1298 MenuButton* button = NULL; 1299 MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()-> 1300 GetSiblingMenu(source->GetMenuItem()->GetRootMenuItem(), 1301 screen_point, &anchor, &has_mnemonics, &button); 1302 if (!alt_menu || (state_.item && state_.item->GetRootMenuItem() == alt_menu)) 1303 return false; 1304 1305 delegate_->SiblingMenuCreated(alt_menu); 1306 1307 if (!button) { 1308 // If the delegate returns a menu, they must also return a button. 1309 NOTREACHED(); 1310 return false; 1311 } 1312 1313 // There is a sibling menu, update the button state, hide the current menu 1314 // and show the new one. 1315 menu_button_->SetState(CustomButton::STATE_NORMAL); 1316 menu_button_->SchedulePaint(); 1317 menu_button_ = button; 1318 menu_button_->SetState(CustomButton::STATE_PRESSED); 1319 menu_button_->SchedulePaint(); 1320 1321 // Need to reset capture when we show the menu again, otherwise we aren't 1322 // going to get any events. 1323 did_capture_ = false; 1324 gfx::Point screen_menu_loc; 1325 View::ConvertPointToScreen(button, &screen_menu_loc); 1326 1327 // It is currently not possible to show a submenu recursively in a bubble. 1328 DCHECK(!MenuItemView::IsBubble(anchor)); 1329 // Subtract 1 from the height to make the popup flush with the button border. 1330 UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(), 1331 button->width(), button->height() - 1), 1332 anchor, state_.context_menu); 1333 alt_menu->PrepareForRun( 1334 false, has_mnemonics, 1335 source->GetMenuItem()->GetRootMenuItem()->show_mnemonics_); 1336 alt_menu->controller_ = this; 1337 SetSelection(alt_menu, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1338 return true; 1339 } 1340 1341 bool MenuController::ShowContextMenu(MenuItemView* menu_item, 1342 SubmenuView* source, 1343 const ui::LocatedEvent& event, 1344 ui::MenuSourceType source_type) { 1345 // Set the selection immediately, making sure the submenu is only open 1346 // if it already was. 1347 int selection_types = SELECTION_UPDATE_IMMEDIATELY; 1348 if (state_.item == pending_state_.item && state_.submenu_open) 1349 selection_types |= SELECTION_OPEN_SUBMENU; 1350 SetSelection(pending_state_.item, selection_types); 1351 gfx::Point loc(event.location()); 1352 View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); 1353 1354 if (menu_item->GetDelegate()->ShowContextMenu( 1355 menu_item, menu_item->GetCommand(), loc, source_type)) { 1356 SendMouseCaptureLostToActiveView(); 1357 return true; 1358 } 1359 return false; 1360 } 1361 1362 void MenuController::CloseAllNestedMenus() { 1363 for (std::list<State>::iterator i = menu_stack_.begin(); 1364 i != menu_stack_.end(); ++i) { 1365 MenuItemView* last_item = i->item; 1366 for (MenuItemView* item = last_item; item; 1367 item = item->GetParentMenuItem()) { 1368 CloseMenu(item); 1369 last_item = item; 1370 } 1371 i->submenu_open = false; 1372 i->item = last_item; 1373 } 1374 } 1375 1376 MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { 1377 // Walk the view hierarchy until we find a menu item (or the root). 1378 View* child_under_mouse = source->GetEventHandlerForPoint(gfx::Point(x, y)); 1379 while (child_under_mouse && 1380 child_under_mouse->id() != MenuItemView::kMenuItemViewID) { 1381 child_under_mouse = child_under_mouse->parent(); 1382 } 1383 if (child_under_mouse && child_under_mouse->enabled() && 1384 child_under_mouse->id() == MenuItemView::kMenuItemViewID) { 1385 return static_cast<MenuItemView*>(child_under_mouse); 1386 } 1387 return NULL; 1388 } 1389 1390 MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { 1391 View* child_under_mouse = source->GetEventHandlerForPoint(gfx::Point(x, y)); 1392 if (child_under_mouse && 1393 child_under_mouse->id() == MenuItemView::kEmptyMenuItemViewID) { 1394 return static_cast<MenuItemView*>(child_under_mouse); 1395 } 1396 return NULL; 1397 } 1398 1399 bool MenuController::IsScrollButtonAt(SubmenuView* source, 1400 int x, 1401 int y, 1402 MenuPart::Type* part) { 1403 MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer(); 1404 View* child_under_mouse = 1405 scroll_view->GetEventHandlerForPoint(gfx::Point(x, y)); 1406 if (child_under_mouse && child_under_mouse->enabled()) { 1407 if (child_under_mouse == scroll_view->scroll_up_button()) { 1408 *part = MenuPart::SCROLL_UP; 1409 return true; 1410 } 1411 if (child_under_mouse == scroll_view->scroll_down_button()) { 1412 *part = MenuPart::SCROLL_DOWN; 1413 return true; 1414 } 1415 } 1416 return false; 1417 } 1418 1419 MenuController::MenuPart MenuController::GetMenuPart( 1420 SubmenuView* source, 1421 const gfx::Point& source_loc) { 1422 gfx::Point screen_loc(source_loc); 1423 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 1424 return GetMenuPartByScreenCoordinateUsingMenu(state_.item, screen_loc); 1425 } 1426 1427 MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinateUsingMenu( 1428 MenuItemView* item, 1429 const gfx::Point& screen_loc) { 1430 MenuPart part; 1431 for (; item; item = item->GetParentMenuItem()) { 1432 if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && 1433 GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc, 1434 &part)) { 1435 return part; 1436 } 1437 } 1438 return part; 1439 } 1440 1441 bool MenuController::GetMenuPartByScreenCoordinateImpl( 1442 SubmenuView* menu, 1443 const gfx::Point& screen_loc, 1444 MenuPart* part) { 1445 // Is the mouse over the scroll buttons? 1446 gfx::Point scroll_view_loc = screen_loc; 1447 View* scroll_view_container = menu->GetScrollViewContainer(); 1448 View::ConvertPointFromScreen(scroll_view_container, &scroll_view_loc); 1449 if (scroll_view_loc.x() < 0 || 1450 scroll_view_loc.x() >= scroll_view_container->width() || 1451 scroll_view_loc.y() < 0 || 1452 scroll_view_loc.y() >= scroll_view_container->height()) { 1453 // Point isn't contained in menu. 1454 return false; 1455 } 1456 if (IsScrollButtonAt(menu, scroll_view_loc.x(), scroll_view_loc.y(), 1457 &(part->type))) { 1458 part->submenu = menu; 1459 return true; 1460 } 1461 1462 // Not over the scroll button. Check the actual menu. 1463 if (DoesSubmenuContainLocation(menu, screen_loc)) { 1464 gfx::Point menu_loc = screen_loc; 1465 View::ConvertPointFromScreen(menu, &menu_loc); 1466 part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y()); 1467 part->type = MenuPart::MENU_ITEM; 1468 part->submenu = menu; 1469 if (!part->menu) 1470 part->parent = menu->GetMenuItem(); 1471 return true; 1472 } 1473 1474 // While the mouse isn't over a menu item or the scroll buttons of menu, it 1475 // is contained by menu and so we return true. If we didn't return true other 1476 // menus would be searched, even though they are likely obscured by us. 1477 return true; 1478 } 1479 1480 bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu, 1481 const gfx::Point& screen_loc) { 1482 gfx::Point view_loc = screen_loc; 1483 View::ConvertPointFromScreen(submenu, &view_loc); 1484 gfx::Rect vis_rect = submenu->GetVisibleBounds(); 1485 return vis_rect.Contains(view_loc.x(), view_loc.y()); 1486 } 1487 1488 void MenuController::CommitPendingSelection() { 1489 StopShowTimer(); 1490 1491 size_t paths_differ_at = 0; 1492 std::vector<MenuItemView*> current_path; 1493 std::vector<MenuItemView*> new_path; 1494 BuildPathsAndCalculateDiff(state_.item, pending_state_.item, ¤t_path, 1495 &new_path, &paths_differ_at); 1496 1497 // Hide the old menu. 1498 for (size_t i = paths_differ_at; i < current_path.size(); ++i) { 1499 if (current_path[i]->HasSubmenu()) { 1500 current_path[i]->GetSubmenu()->Hide(); 1501 } 1502 } 1503 1504 // Copy pending to state_, making sure to preserve the direction menus were 1505 // opened. 1506 std::list<bool> pending_open_direction; 1507 state_.open_leading.swap(pending_open_direction); 1508 state_ = pending_state_; 1509 state_.open_leading.swap(pending_open_direction); 1510 1511 int menu_depth = MenuDepth(state_.item); 1512 if (menu_depth == 0) { 1513 state_.open_leading.clear(); 1514 } else { 1515 int cached_size = static_cast<int>(state_.open_leading.size()); 1516 DCHECK_GE(menu_depth, 0); 1517 while (cached_size-- >= menu_depth) 1518 state_.open_leading.pop_back(); 1519 } 1520 1521 if (!state_.item) { 1522 // Nothing to select. 1523 StopScrolling(); 1524 return; 1525 } 1526 1527 // Open all the submenus preceeding the last menu item (last menu item is 1528 // handled next). 1529 if (new_path.size() > 1) { 1530 for (std::vector<MenuItemView*>::iterator i = new_path.begin(); 1531 i != new_path.end() - 1; ++i) { 1532 OpenMenu(*i); 1533 } 1534 } 1535 1536 if (state_.submenu_open) { 1537 // The submenu should be open, open the submenu if the item has a submenu. 1538 if (state_.item->HasSubmenu()) { 1539 OpenMenu(state_.item); 1540 } else { 1541 state_.submenu_open = false; 1542 } 1543 } else if (state_.item->HasSubmenu() && 1544 state_.item->GetSubmenu()->IsShowing()) { 1545 state_.item->GetSubmenu()->Hide(); 1546 } 1547 1548 if (scroll_task_.get() && scroll_task_->submenu()) { 1549 // Stop the scrolling if none of the elements of the selection contain 1550 // the menu being scrolled. 1551 bool found = false; 1552 for (MenuItemView* item = state_.item; item && !found; 1553 item = item->GetParentMenuItem()) { 1554 found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && 1555 item->GetSubmenu() == scroll_task_->submenu()); 1556 } 1557 if (!found) 1558 StopScrolling(); 1559 } 1560 } 1561 1562 void MenuController::CloseMenu(MenuItemView* item) { 1563 DCHECK(item); 1564 if (!item->HasSubmenu()) 1565 return; 1566 item->GetSubmenu()->Hide(); 1567 } 1568 1569 void MenuController::OpenMenu(MenuItemView* item) { 1570 DCHECK(item); 1571 if (item->GetSubmenu()->IsShowing()) { 1572 return; 1573 } 1574 1575 OpenMenuImpl(item, true); 1576 did_capture_ = true; 1577 } 1578 1579 void MenuController::OpenMenuImpl(MenuItemView* item, bool show) { 1580 // TODO(oshima|sky): Don't show the menu if drag is in progress and 1581 // this menu doesn't support drag drop. See crbug.com/110495. 1582 if (show) { 1583 int old_count = item->GetSubmenu()->child_count(); 1584 item->GetDelegate()->WillShowMenu(item); 1585 if (old_count != item->GetSubmenu()->child_count()) { 1586 // If the number of children changed then we may need to add empty items. 1587 item->AddEmptyMenus(); 1588 } 1589 } 1590 bool prefer_leading = 1591 state_.open_leading.empty() ? true : state_.open_leading.back(); 1592 bool resulting_direction; 1593 gfx::Rect bounds = MenuItemView::IsBubble(state_.anchor) ? 1594 CalculateBubbleMenuBounds(item, prefer_leading, &resulting_direction) : 1595 CalculateMenuBounds(item, prefer_leading, &resulting_direction); 1596 state_.open_leading.push_back(resulting_direction); 1597 bool do_capture = (!did_capture_ && blocking_run_); 1598 showing_submenu_ = true; 1599 if (show) { 1600 // Menus are the only place using kGroupingPropertyKey, so any value (other 1601 // than 0) is fine. 1602 const int kGroupingId = 1001; 1603 item->GetSubmenu()->ShowAt(owner_, bounds, do_capture); 1604 item->GetSubmenu()->GetWidget()->SetNativeWindowProperty( 1605 TooltipManager::kGroupingPropertyKey, 1606 reinterpret_cast<void*>(kGroupingId)); 1607 } else { 1608 item->GetSubmenu()->Reposition(bounds); 1609 } 1610 showing_submenu_ = false; 1611 } 1612 1613 void MenuController::MenuChildrenChanged(MenuItemView* item) { 1614 DCHECK(item); 1615 // Menu shouldn't be updated during drag operation. 1616 DCHECK(!GetActiveMouseView()); 1617 1618 // If the current item or pending item is a descendant of the item 1619 // that changed, move the selection back to the changed item. 1620 const MenuItemView* ancestor = state_.item; 1621 while (ancestor && ancestor != item) 1622 ancestor = ancestor->GetParentMenuItem(); 1623 if (!ancestor) { 1624 ancestor = pending_state_.item; 1625 while (ancestor && ancestor != item) 1626 ancestor = ancestor->GetParentMenuItem(); 1627 if (!ancestor) 1628 return; 1629 } 1630 SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 1631 if (item->HasSubmenu()) 1632 OpenMenuImpl(item, false); 1633 } 1634 1635 void MenuController::BuildPathsAndCalculateDiff( 1636 MenuItemView* old_item, 1637 MenuItemView* new_item, 1638 std::vector<MenuItemView*>* old_path, 1639 std::vector<MenuItemView*>* new_path, 1640 size_t* first_diff_at) { 1641 DCHECK(old_path && new_path && first_diff_at); 1642 BuildMenuItemPath(old_item, old_path); 1643 BuildMenuItemPath(new_item, new_path); 1644 1645 size_t common_size = std::min(old_path->size(), new_path->size()); 1646 1647 // Find the first difference between the two paths, when the loop 1648 // returns, diff_i is the first index where the two paths differ. 1649 for (size_t i = 0; i < common_size; ++i) { 1650 if ((*old_path)[i] != (*new_path)[i]) { 1651 *first_diff_at = i; 1652 return; 1653 } 1654 } 1655 1656 *first_diff_at = common_size; 1657 } 1658 1659 void MenuController::BuildMenuItemPath(MenuItemView* item, 1660 std::vector<MenuItemView*>* path) { 1661 if (!item) 1662 return; 1663 BuildMenuItemPath(item->GetParentMenuItem(), path); 1664 path->push_back(item); 1665 } 1666 1667 void MenuController::StartShowTimer() { 1668 show_timer_.Start(FROM_HERE, 1669 TimeDelta::FromMilliseconds(menu_config_.show_delay), 1670 this, &MenuController::CommitPendingSelection); 1671 } 1672 1673 void MenuController::StopShowTimer() { 1674 show_timer_.Stop(); 1675 } 1676 1677 void MenuController::StartCancelAllTimer() { 1678 cancel_all_timer_.Start(FROM_HERE, 1679 TimeDelta::FromMilliseconds(kCloseOnExitTime), 1680 this, &MenuController::CancelAll); 1681 } 1682 1683 void MenuController::StopCancelAllTimer() { 1684 cancel_all_timer_.Stop(); 1685 } 1686 1687 gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, 1688 bool prefer_leading, 1689 bool* is_leading) { 1690 DCHECK(item); 1691 1692 SubmenuView* submenu = item->GetSubmenu(); 1693 DCHECK(submenu); 1694 1695 gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); 1696 1697 // Don't let the menu go too wide. 1698 pref.set_width(std::min(pref.width(), 1699 item->GetDelegate()->GetMaxWidthForMenu(item))); 1700 if (!state_.monitor_bounds.IsEmpty()) 1701 pref.set_width(std::min(pref.width(), state_.monitor_bounds.width())); 1702 1703 // Assume we can honor prefer_leading. 1704 *is_leading = prefer_leading; 1705 1706 int x, y; 1707 1708 const MenuConfig& menu_config = item->GetMenuConfig(); 1709 1710 if (!item->GetParentMenuItem()) { 1711 // First item, position relative to initial location. 1712 x = state_.initial_bounds.x(); 1713 1714 // Offsets for context menu prevent menu items being selected by 1715 // simply opening the menu (bug 142992). 1716 if (menu_config.offset_context_menus && state_.context_menu) 1717 x += 1; 1718 1719 y = state_.initial_bounds.bottom(); 1720 if (state_.anchor == MenuItemView::TOPRIGHT) { 1721 x = x + state_.initial_bounds.width() - pref.width(); 1722 if (menu_config.offset_context_menus && state_.context_menu) 1723 x -= 1; 1724 } else if (state_.anchor == MenuItemView::BOTTOMCENTER) { 1725 x = x - (pref.width() - state_.initial_bounds.width()) / 2; 1726 if (pref.height() > 1727 state_.initial_bounds.y() + kCenteredContextMenuYOffset) { 1728 // Menu does not fit above the anchor. We move it to below. 1729 y = state_.initial_bounds.y() - kCenteredContextMenuYOffset; 1730 } else { 1731 y = std::max(0, state_.initial_bounds.y() - pref.height()) + 1732 kCenteredContextMenuYOffset; 1733 } 1734 } 1735 1736 if (!state_.monitor_bounds.IsEmpty() && 1737 y + pref.height() > state_.monitor_bounds.bottom()) { 1738 // The menu doesn't fit fully below the button on the screen. The menu 1739 // position with respect to the bounds will be preserved if it has 1740 // already been drawn. When the requested positioning is below the bounds 1741 // it will shrink the menu to make it fit below. 1742 // If the requested positioning is best fit, it will first try to fit the 1743 // menu below. If that does not fit it will try to place it above. If 1744 // that will not fit it will place it at the bottom of the work area and 1745 // moving it off the initial_bounds region to avoid overlap. 1746 // In all other requested position styles it will be flipped above and 1747 // the height will be shrunken to the usable height. 1748 if (item->actual_menu_position() == MenuItemView::POSITION_BELOW_BOUNDS) { 1749 pref.set_height(std::min(pref.height(), 1750 state_.monitor_bounds.bottom() - y)); 1751 } else if (item->actual_menu_position() == 1752 MenuItemView::POSITION_BEST_FIT) { 1753 MenuItemView::MenuPosition orientation = 1754 MenuItemView::POSITION_BELOW_BOUNDS; 1755 if (state_.monitor_bounds.height() < pref.height()) { 1756 // Handle very tall menus. 1757 pref.set_height(state_.monitor_bounds.height()); 1758 y = state_.monitor_bounds.y(); 1759 } else if (state_.monitor_bounds.y() + pref.height() < 1760 state_.initial_bounds.y()) { 1761 // Flipping upwards if there is enough space. 1762 y = state_.initial_bounds.y() - pref.height(); 1763 orientation = MenuItemView::POSITION_ABOVE_BOUNDS; 1764 } else { 1765 // It is allowed to move the menu a bit around in order to get the 1766 // best fit and to avoid showing scroll elements. 1767 y = state_.monitor_bounds.bottom() - pref.height(); 1768 } 1769 if (orientation == MenuItemView::POSITION_BELOW_BOUNDS) { 1770 // The menu should never overlap the owning button. So move it. 1771 // We use the anchor view style to determine the preferred position 1772 // relative to the owning button. 1773 if (state_.anchor == MenuItemView::TOPLEFT) { 1774 // The menu starts with the same x coordinate as the owning button. 1775 if (x + state_.initial_bounds.width() + pref.width() > 1776 state_.monitor_bounds.right()) 1777 x -= pref.width(); // Move the menu to the left of the button. 1778 else 1779 x += state_.initial_bounds.width(); // Move the menu right. 1780 } else { 1781 // The menu should end with the same x coordinate as the owning 1782 // button. 1783 if (state_.monitor_bounds.x() > 1784 state_.initial_bounds.x() - pref.width()) 1785 x = state_.initial_bounds.right(); // Move right of the button. 1786 else 1787 x = state_.initial_bounds.x() - pref.width(); // Move left. 1788 } 1789 } 1790 item->set_actual_menu_position(orientation); 1791 } else { 1792 pref.set_height(std::min(pref.height(), 1793 state_.initial_bounds.y() - state_.monitor_bounds.y())); 1794 y = state_.initial_bounds.y() - pref.height(); 1795 item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS); 1796 } 1797 } else if (item->actual_menu_position() == 1798 MenuItemView::POSITION_ABOVE_BOUNDS) { 1799 pref.set_height(std::min(pref.height(), 1800 state_.initial_bounds.y() - state_.monitor_bounds.y())); 1801 y = state_.initial_bounds.y() - pref.height(); 1802 } else { 1803 item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS); 1804 } 1805 if (state_.monitor_bounds.width() != 0 && 1806 menu_config.offset_context_menus && state_.context_menu) { 1807 if (x + pref.width() > state_.monitor_bounds.right()) 1808 x = state_.initial_bounds.x() - pref.width() - 1; 1809 if (x < state_.monitor_bounds.x()) 1810 x = state_.monitor_bounds.x(); 1811 } 1812 } else { 1813 // Not the first menu; position it relative to the bounds of the menu 1814 // item. 1815 gfx::Point item_loc; 1816 View::ConvertPointToScreen(item, &item_loc); 1817 1818 // We must make sure we take into account the UI layout. If the layout is 1819 // RTL, then a 'leading' menu is positioned to the left of the parent menu 1820 // item and not to the right. 1821 bool layout_is_rtl = base::i18n::IsRTL(); 1822 bool create_on_the_right = (prefer_leading && !layout_is_rtl) || 1823 (!prefer_leading && layout_is_rtl); 1824 int submenu_horizontal_inset = menu_config.submenu_horizontal_inset; 1825 1826 if (create_on_the_right) { 1827 x = item_loc.x() + item->width() - submenu_horizontal_inset; 1828 if (state_.monitor_bounds.width() != 0 && 1829 x + pref.width() > state_.monitor_bounds.right()) { 1830 if (layout_is_rtl) 1831 *is_leading = true; 1832 else 1833 *is_leading = false; 1834 x = item_loc.x() - pref.width() + submenu_horizontal_inset; 1835 } 1836 } else { 1837 x = item_loc.x() - pref.width() + submenu_horizontal_inset; 1838 if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { 1839 if (layout_is_rtl) 1840 *is_leading = false; 1841 else 1842 *is_leading = true; 1843 x = item_loc.x() + item->width() - submenu_horizontal_inset; 1844 } 1845 } 1846 y = item_loc.y() - menu_config.menu_vertical_border_size; 1847 if (state_.monitor_bounds.width() != 0) { 1848 pref.set_height(std::min(pref.height(), state_.monitor_bounds.height())); 1849 if (y + pref.height() > state_.monitor_bounds.bottom()) 1850 y = state_.monitor_bounds.bottom() - pref.height(); 1851 if (y < state_.monitor_bounds.y()) 1852 y = state_.monitor_bounds.y(); 1853 } 1854 } 1855 1856 if (state_.monitor_bounds.width() != 0) { 1857 if (x + pref.width() > state_.monitor_bounds.right()) 1858 x = state_.monitor_bounds.right() - pref.width(); 1859 if (x < state_.monitor_bounds.x()) 1860 x = state_.monitor_bounds.x(); 1861 } 1862 return gfx::Rect(x, y, pref.width(), pref.height()); 1863 } 1864 1865 gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, 1866 bool prefer_leading, 1867 bool* is_leading) { 1868 DCHECK(item); 1869 DCHECK(!item->GetParentMenuItem()); 1870 1871 // Assume we can honor prefer_leading. 1872 *is_leading = prefer_leading; 1873 1874 SubmenuView* submenu = item->GetSubmenu(); 1875 DCHECK(submenu); 1876 1877 gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); 1878 const gfx::Rect& owner_bounds = pending_state_.initial_bounds; 1879 1880 // First the size gets reduced to the possible space. 1881 if (!state_.monitor_bounds.IsEmpty()) { 1882 int max_width = state_.monitor_bounds.width(); 1883 int max_height = state_.monitor_bounds.height(); 1884 // In case of bubbles, the maximum width is limited by the space 1885 // between the display corner and the target area + the tip size. 1886 if (state_.anchor == MenuItemView::BUBBLE_LEFT) { 1887 max_width = owner_bounds.x() - state_.monitor_bounds.x() + 1888 kBubbleTipSizeLeftRight; 1889 } else if (state_.anchor == MenuItemView::BUBBLE_RIGHT) { 1890 max_width = state_.monitor_bounds.right() - owner_bounds.right() + 1891 kBubbleTipSizeLeftRight; 1892 } else if (state_.anchor == MenuItemView::BUBBLE_ABOVE) { 1893 max_height = owner_bounds.y() - state_.monitor_bounds.y() + 1894 kBubbleTipSizeTopBottom; 1895 } else if (state_.anchor == MenuItemView::BUBBLE_BELOW) { 1896 max_height = state_.monitor_bounds.bottom() - owner_bounds.bottom() + 1897 kBubbleTipSizeTopBottom; 1898 } 1899 // The space for the menu to cover should never get empty. 1900 DCHECK_GE(max_width, kBubbleTipSizeLeftRight); 1901 DCHECK_GE(max_height, kBubbleTipSizeTopBottom); 1902 pref.set_width(std::min(pref.width(), max_width)); 1903 pref.set_height(std::min(pref.height(), max_height)); 1904 } 1905 // Also make sure that the menu does not go too wide. 1906 pref.set_width(std::min(pref.width(), 1907 item->GetDelegate()->GetMaxWidthForMenu(item))); 1908 1909 int x, y; 1910 if (state_.anchor == MenuItemView::BUBBLE_ABOVE || 1911 state_.anchor == MenuItemView::BUBBLE_BELOW) { 1912 if (state_.anchor == MenuItemView::BUBBLE_ABOVE) 1913 y = owner_bounds.y() - pref.height() + kBubbleTipSizeTopBottom; 1914 else 1915 y = owner_bounds.bottom() - kBubbleTipSizeTopBottom; 1916 1917 x = owner_bounds.CenterPoint().x() - pref.width() / 2; 1918 int x_old = x; 1919 if (x < state_.monitor_bounds.x()) { 1920 x = state_.monitor_bounds.x(); 1921 } else if (x + pref.width() > state_.monitor_bounds.right()) { 1922 x = state_.monitor_bounds.right() - pref.width(); 1923 } 1924 submenu->GetScrollViewContainer()->SetBubbleArrowOffset( 1925 pref.width() / 2 - x + x_old); 1926 } else { 1927 if (state_.anchor == MenuItemView::BUBBLE_RIGHT) 1928 x = owner_bounds.right() - kBubbleTipSizeLeftRight; 1929 else 1930 x = owner_bounds.x() - pref.width() + kBubbleTipSizeLeftRight; 1931 1932 y = owner_bounds.CenterPoint().y() - pref.height() / 2; 1933 int y_old = y; 1934 if (y < state_.monitor_bounds.y()) { 1935 y = state_.monitor_bounds.y(); 1936 } else if (y + pref.height() > state_.monitor_bounds.bottom()) { 1937 y = state_.monitor_bounds.bottom() - pref.height(); 1938 } 1939 submenu->GetScrollViewContainer()->SetBubbleArrowOffset( 1940 pref.height() / 2 - y + y_old); 1941 } 1942 return gfx::Rect(x, y, pref.width(), pref.height()); 1943 } 1944 1945 // static 1946 int MenuController::MenuDepth(MenuItemView* item) { 1947 return item ? (MenuDepth(item->GetParentMenuItem()) + 1) : 0; 1948 } 1949 1950 void MenuController::IncrementSelection(int delta) { 1951 MenuItemView* item = pending_state_.item; 1952 DCHECK(item); 1953 if (pending_state_.submenu_open && item->HasSubmenu() && 1954 item->GetSubmenu()->IsShowing()) { 1955 // A menu is selected and open, but none of its children are selected, 1956 // select the first menu item. 1957 if (item->GetSubmenu()->GetMenuItemCount()) { 1958 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), SELECTION_DEFAULT); 1959 return; 1960 } 1961 } 1962 1963 if (item->has_children()) { 1964 CustomButton* button = GetFirstHotTrackedView(item); 1965 if (button) { 1966 button->SetHotTracked(false); 1967 View* to_make_hot = GetNextFocusableView(item, button, delta == 1); 1968 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1969 if (button_hot) { 1970 button_hot->SetHotTracked(true); 1971 return; 1972 } 1973 } else { 1974 View* to_make_hot = GetInitialFocusableView(item, delta == 1); 1975 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1976 if (button_hot) { 1977 button_hot->SetHotTracked(true); 1978 return; 1979 } 1980 } 1981 } 1982 1983 MenuItemView* parent = item->GetParentMenuItem(); 1984 if (parent) { 1985 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); 1986 if (parent_count > 1) { 1987 for (int i = 0; i < parent_count; ++i) { 1988 if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { 1989 MenuItemView* to_select = 1990 FindNextSelectableMenuItem(parent, i, delta); 1991 if (!to_select) 1992 break; 1993 SetSelection(to_select, SELECTION_DEFAULT); 1994 View* to_make_hot = GetInitialFocusableView(to_select, delta == 1); 1995 CustomButton* button_hot = CustomButton::AsCustomButton(to_make_hot); 1996 if (button_hot) 1997 button_hot->SetHotTracked(true); 1998 break; 1999 } 2000 } 2001 } 2002 } 2003 } 2004 2005 MenuItemView* MenuController::FindNextSelectableMenuItem(MenuItemView* parent, 2006 int index, 2007 int delta) { 2008 int start_index = index; 2009 int parent_count = parent->GetSubmenu()->GetMenuItemCount(); 2010 // Loop through the menu items skipping any invisible menus. The loop stops 2011 // when we wrap or find a visible child. 2012 do { 2013 index = (index + delta + parent_count) % parent_count; 2014 if (index == start_index) 2015 return NULL; 2016 MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); 2017 if (child->visible()) 2018 return child; 2019 } while (index != start_index); 2020 return NULL; 2021 } 2022 2023 void MenuController::OpenSubmenuChangeSelectionIfCan() { 2024 MenuItemView* item = pending_state_.item; 2025 if (item->HasSubmenu() && item->enabled()) { 2026 if (item->GetSubmenu()->GetMenuItemCount() > 0) { 2027 SetSelection(item->GetSubmenu()->GetMenuItemAt(0), 2028 SELECTION_UPDATE_IMMEDIATELY); 2029 } else { 2030 // No menu items, just show the sub-menu. 2031 SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 2032 } 2033 } 2034 } 2035 2036 void MenuController::CloseSubmenu() { 2037 MenuItemView* item = state_.item; 2038 DCHECK(item); 2039 if (!item->GetParentMenuItem()) 2040 return; 2041 if (item->HasSubmenu() && item->GetSubmenu()->IsShowing()) 2042 SetSelection(item, SELECTION_UPDATE_IMMEDIATELY); 2043 else if (item->GetParentMenuItem()->GetParentMenuItem()) 2044 SetSelection(item->GetParentMenuItem(), SELECTION_UPDATE_IMMEDIATELY); 2045 } 2046 2047 MenuController::SelectByCharDetails MenuController::FindChildForMnemonic( 2048 MenuItemView* parent, 2049 char16 key, 2050 bool (*match_function)(MenuItemView* menu, char16 mnemonic)) { 2051 SubmenuView* submenu = parent->GetSubmenu(); 2052 DCHECK(submenu); 2053 SelectByCharDetails details; 2054 2055 for (int i = 0, menu_item_count = submenu->GetMenuItemCount(); 2056 i < menu_item_count; ++i) { 2057 MenuItemView* child = submenu->GetMenuItemAt(i); 2058 if (child->enabled() && child->visible()) { 2059 if (child == pending_state_.item) 2060 details.index_of_item = i; 2061 if (match_function(child, key)) { 2062 if (details.first_match == -1) 2063 details.first_match = i; 2064 else 2065 details.has_multiple = true; 2066 if (details.next_match == -1 && details.index_of_item != -1 && 2067 i > details.index_of_item) 2068 details.next_match = i; 2069 } 2070 } 2071 } 2072 return details; 2073 } 2074 2075 bool MenuController::AcceptOrSelect(MenuItemView* parent, 2076 const SelectByCharDetails& details) { 2077 // This should only be invoked if there is a match. 2078 DCHECK(details.first_match != -1); 2079 DCHECK(parent->HasSubmenu()); 2080 SubmenuView* submenu = parent->GetSubmenu(); 2081 DCHECK(submenu); 2082 if (!details.has_multiple) { 2083 // There's only one match, activate it (or open if it has a submenu). 2084 if (submenu->GetMenuItemAt(details.first_match)->HasSubmenu()) { 2085 SetSelection(submenu->GetMenuItemAt(details.first_match), 2086 SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); 2087 } else { 2088 Accept(submenu->GetMenuItemAt(details.first_match), 0); 2089 return true; 2090 } 2091 } else if (details.index_of_item == -1 || details.next_match == -1) { 2092 SetSelection(submenu->GetMenuItemAt(details.first_match), 2093 SELECTION_DEFAULT); 2094 } else { 2095 SetSelection(submenu->GetMenuItemAt(details.next_match), 2096 SELECTION_DEFAULT); 2097 } 2098 return false; 2099 } 2100 2101 bool MenuController::SelectByChar(char16 character) { 2102 char16 char_array[] = { character, 0 }; 2103 char16 key = base::i18n::ToLower(char_array)[0]; 2104 MenuItemView* item = pending_state_.item; 2105 if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing()) 2106 item = item->GetParentMenuItem(); 2107 DCHECK(item); 2108 DCHECK(item->HasSubmenu()); 2109 DCHECK(item->GetSubmenu()); 2110 if (item->GetSubmenu()->GetMenuItemCount() == 0) 2111 return false; 2112 2113 // Look for matches based on mnemonic first. 2114 SelectByCharDetails details = 2115 FindChildForMnemonic(item, key, &MatchesMnemonic); 2116 if (details.first_match != -1) 2117 return AcceptOrSelect(item, details); 2118 2119 // If no mnemonics found, look at first character of titles. 2120 details = FindChildForMnemonic(item, key, &TitleMatchesMnemonic); 2121 if (details.first_match != -1) 2122 return AcceptOrSelect(item, details); 2123 2124 return false; 2125 } 2126 2127 void MenuController::RepostEvent(SubmenuView* source, 2128 const ui::LocatedEvent& event) { 2129 gfx::Point screen_loc(event.location()); 2130 View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); 2131 2132 gfx::NativeView native_view = source->GetWidget()->GetNativeView(); 2133 gfx::Screen* screen = gfx::Screen::GetScreenFor(native_view); 2134 gfx::NativeWindow window = screen->GetWindowAtScreenPoint(screen_loc); 2135 2136 if (!window) 2137 return; 2138 2139 #if defined(OS_WIN) 2140 // Release the capture. 2141 SubmenuView* submenu = state_.item->GetRootMenuItem()->GetSubmenu(); 2142 submenu->ReleaseCapture(); 2143 2144 gfx::NativeView view = submenu->GetWidget()->GetNativeView(); 2145 if (view) { 2146 DWORD view_tid = GetWindowThreadProcessId(HWNDForNativeView(view), NULL); 2147 if (view_tid != GetWindowThreadProcessId(HWNDForNativeView(window), NULL)) { 2148 // Even though we have mouse capture, windows generates a mouse event if 2149 // the other window is in a separate thread. Only repost an event if 2150 // |view| was created on the same thread, else the target window can get 2151 // double events leading to bad behavior. 2152 return; 2153 } 2154 } 2155 #endif 2156 2157 scoped_ptr<ui::LocatedEvent> clone; 2158 if (event.IsMouseEvent()) { 2159 clone.reset(new ui::MouseEvent(static_cast<const ui::MouseEvent&>(event))); 2160 } else if (event.IsGestureEvent()) { 2161 // TODO(rbyers): Gesture event repost is tricky to get right 2162 // crbug.com/170987. 2163 return; 2164 } else { 2165 NOTREACHED(); 2166 return; 2167 } 2168 clone->set_location(screen_loc); 2169 2170 RepostLocatedEvent(window, *clone); 2171 } 2172 2173 2174 void MenuController::SetDropMenuItem( 2175 MenuItemView* new_target, 2176 MenuDelegate::DropPosition new_position) { 2177 if (new_target == drop_target_ && new_position == drop_position_) 2178 return; 2179 2180 if (drop_target_) { 2181 drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( 2182 NULL, MenuDelegate::DROP_NONE); 2183 } 2184 drop_target_ = new_target; 2185 drop_position_ = new_position; 2186 if (drop_target_) { 2187 drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( 2188 drop_target_, drop_position_); 2189 } 2190 } 2191 2192 void MenuController::UpdateScrolling(const MenuPart& part) { 2193 if (!part.is_scroll() && !scroll_task_.get()) 2194 return; 2195 2196 if (!scroll_task_.get()) 2197 scroll_task_.reset(new MenuScrollTask()); 2198 scroll_task_->Update(part); 2199 } 2200 2201 void MenuController::StopScrolling() { 2202 scroll_task_.reset(NULL); 2203 } 2204 2205 void MenuController::UpdateActiveMouseView(SubmenuView* event_source, 2206 const ui::MouseEvent& event, 2207 View* target_menu) { 2208 View* target = NULL; 2209 gfx::Point target_menu_loc(event.location()); 2210 if (target_menu && target_menu->has_children()) { 2211 // Locate the deepest child view to send events to. This code assumes we 2212 // don't have to walk up the tree to find a view interested in events. This 2213 // is currently true for the cases we are embedding views, but if we embed 2214 // more complex hierarchies it'll need to change. 2215 View::ConvertPointToScreen(event_source->GetScrollViewContainer(), 2216 &target_menu_loc); 2217 View::ConvertPointFromScreen(target_menu, &target_menu_loc); 2218 target = target_menu->GetEventHandlerForPoint(target_menu_loc); 2219 if (target == target_menu || !target->enabled()) 2220 target = NULL; 2221 } 2222 View* active_mouse_view = GetActiveMouseView(); 2223 if (target != active_mouse_view) { 2224 SendMouseCaptureLostToActiveView(); 2225 active_mouse_view = target; 2226 SetActiveMouseView(active_mouse_view); 2227 if (active_mouse_view) { 2228 gfx::Point target_point(target_menu_loc); 2229 View::ConvertPointToTarget( 2230 target_menu, active_mouse_view, &target_point); 2231 ui::MouseEvent mouse_entered_event(ui::ET_MOUSE_ENTERED, 2232 target_point, target_point, 2233 0); 2234 active_mouse_view->OnMouseEntered(mouse_entered_event); 2235 2236 ui::MouseEvent mouse_pressed_event(ui::ET_MOUSE_PRESSED, 2237 target_point, target_point, 2238 event.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 active_mouse_view->OnMouseDragged(mouse_dragged_event); 2250 } 2251 } 2252 2253 void MenuController::SendMouseReleaseToActiveView(SubmenuView* event_source, 2254 const ui::MouseEvent& event) { 2255 View* active_mouse_view = GetActiveMouseView(); 2256 if (!active_mouse_view) 2257 return; 2258 2259 gfx::Point target_loc(event.location()); 2260 View::ConvertPointToScreen(event_source->GetScrollViewContainer(), 2261 &target_loc); 2262 View::ConvertPointFromScreen(active_mouse_view, &target_loc); 2263 ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, target_loc, target_loc, 2264 event.flags()); 2265 // Reset active mouse view before sending mouse released. That way if it calls 2266 // back to us, we aren't in a weird state. 2267 SetActiveMouseView(NULL); 2268 active_mouse_view->OnMouseReleased(release_event); 2269 } 2270 2271 void MenuController::SendMouseCaptureLostToActiveView() { 2272 View* active_mouse_view = GetActiveMouseView(); 2273 if (!active_mouse_view) 2274 return; 2275 2276 // Reset the active_mouse_view_ before sending mouse capture lost. That way if 2277 // it calls back to us, we aren't in a weird state. 2278 SetActiveMouseView(NULL); 2279 active_mouse_view->OnMouseCaptureLost(); 2280 } 2281 2282 void MenuController::SetActiveMouseView(View* view) { 2283 if (view) 2284 ViewStorage::GetInstance()->StoreView(active_mouse_view_id_, view); 2285 else 2286 ViewStorage::GetInstance()->RemoveView(active_mouse_view_id_); 2287 } 2288 2289 View* MenuController::GetActiveMouseView() { 2290 return ViewStorage::GetInstance()->RetrieveView(active_mouse_view_id_); 2291 } 2292 2293 void MenuController::SetExitType(ExitType type) { 2294 exit_type_ = type; 2295 // Exit nested message loops as soon as possible. We do this as 2296 // MessageLoop::Dispatcher is only invoked before native events, which means 2297 // its entirely possible for a Widget::CloseNow() task to be processed before 2298 // the next native message. By using QuitNow() we ensures the nested message 2299 // loop returns as soon as possible and avoids having deleted views classes 2300 // (such as widgets and rootviews) on the stack when the nested message loop 2301 // stops. 2302 // 2303 // It's safe to invoke QuitNow multiple times, it only effects the current 2304 // loop. 2305 bool quit_now = ShouldQuitNow() && exit_type_ != EXIT_NONE && 2306 message_loop_depth_; 2307 2308 if (quit_now) 2309 base::MessageLoop::current()->QuitNow(); 2310 } 2311 2312 void MenuController::HandleMouseLocation(SubmenuView* source, 2313 const gfx::Point& mouse_location) { 2314 if (showing_submenu_) 2315 return; 2316 2317 // Ignore mouse events if we're closing the menu. 2318 if (exit_type_ != EXIT_NONE) 2319 return; 2320 2321 MenuPart part = GetMenuPart(source, mouse_location); 2322 2323 UpdateScrolling(part); 2324 2325 if (!blocking_run_) 2326 return; 2327 2328 if (part.type == MenuPart::NONE && ShowSiblingMenu(source, mouse_location)) 2329 return; 2330 2331 if (part.type == MenuPart::MENU_ITEM && part.menu) { 2332 SetSelection(part.menu, SELECTION_OPEN_SUBMENU); 2333 } else if (!part.is_scroll() && pending_state_.item && 2334 pending_state_.item->GetParentMenuItem() && 2335 (!pending_state_.item->HasSubmenu() || 2336 !pending_state_.item->GetSubmenu()->IsShowing())) { 2337 // On exit if the user hasn't selected an item with a submenu, move the 2338 // selection back to the parent menu item. 2339 SetSelection(pending_state_.item->GetParentMenuItem(), 2340 SELECTION_OPEN_SUBMENU); 2341 } 2342 } 2343 2344 } // namespace views 2345