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