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