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.
5 #include "ui/views/controls/menu/menu_controller.h"
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"
41 #include "ui/base/win/internal_constants.h"
42 #include "ui/gfx/win/dpi.h"
43 #include "ui/views/win/hwnd_util.h"
47 using base::TimeDelta
;
48 using ui::OSExchangeData
;
50 // Period of the scroll timer (in milliseconds).
51 static const int kScrollTimerMS
= 30;
53 // Amount of time from when the drop exits the menu and the menu is hidden.
54 static const int kCloseOnExitTime
= 1200;
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;
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
;
69 // The spacing offset for the bubble tip.
70 const int kBubbleTipSizeLeftRight
= 12;
71 const int kBubbleTipSizeTopBottom
= 11;
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
76 const float kMaximumLengthMovedToActivate
= 4.0f
;
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
;
83 // Returns true if |menu| doesn't have a mnemonic and first character of the its
85 bool TitleMatchesMnemonic(MenuItemView
* menu
, base::char16 key
) {
86 if (menu
->GetMnemonic())
89 base::string16 lower_title
= base::i18n::ToLower(menu
->title());
90 return !lower_title
.empty() && lower_title
[0] == key
;
95 // Returns the first descendant of |view| that is hot tracked.
96 static CustomButton
* GetFirstHotTrackedView(View
* view
) {
99 CustomButton
* button
= CustomButton::AsCustomButton(view
);
101 if (button
->IsHotTracked())
105 for (int i
= 0; i
< view
->child_count(); ++i
) {
106 CustomButton
* hot_view
= GetFirstHotTrackedView(view
->child_at(i
));
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
118 static View
* GetFirstFocusableView(View
* view
, int start
, bool forward
) {
120 for (int i
= start
== -1 ? 0 : start
; i
< view
->child_count(); ++i
) {
121 View
* deepest
= GetFirstFocusableView(view
->child_at(i
), -1, forward
);
126 for (int i
= start
== -1 ? view
->child_count() - 1 : start
; i
>= 0; --i
) {
127 View
* deepest
= GetFirstFocusableView(view
->child_at(i
), -1, forward
);
132 return view
->IsFocusable() ? view
: NULL
;
135 // Returns the first child of |start| that is focusable.
136 static View
* GetInitialFocusableView(View
* start
, bool forward
) {
137 return GetFirstFocusableView(start
, -1, forward
);
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
,
145 DCHECK(ancestor
->Contains(start_at
));
146 View
* parent
= start_at
;
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
);
157 } while (parent
!= ancestor
);
161 // MenuScrollTask --------------------------------------------------------------
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
168 class MenuController::MenuScrollTask
{
170 MenuScrollTask() : submenu_(NULL
), is_scrolling_up_(false), start_y_(0) {
171 pixels_per_second_
= MenuItemView::pref_menu_height() * 20;
174 void Update(const MenuController::MenuPart
& part
) {
175 if (!part
.is_scroll()) {
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
)
185 start_scroll_time_
= base::Time::Now();
186 start_y_
= part
.submenu
->GetVisibleBounds().y();
188 is_scrolling_up_
= new_is_up
;
190 if (!scrolling_timer_
.IsRunning()) {
191 scrolling_timer_
.Start(FROM_HERE
,
192 TimeDelta::FromMilliseconds(kScrollTimerMS
),
193 this, &MenuScrollTask::Run
);
197 void StopScrolling() {
198 if (scrolling_timer_
.IsRunning()) {
199 scrolling_timer_
.Stop();
204 // The menu being scrolled. Returns null if not scrolling.
205 SubmenuView
* submenu() const { return 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
);
220 // SubmenuView being scrolled.
221 SubmenuView
* submenu_
;
223 // Direction scrolling.
224 bool is_scrolling_up_
;
226 // Timer to periodically scroll.
227 base::RepeatingTimer
<MenuScrollTask
> scrolling_timer_
;
229 // Time we started scrolling at.
230 base::Time start_scroll_time_
;
232 // How many pixels to scroll per second.
233 int pixels_per_second_
;
235 // Y-coordinate of submenu_view_ when scrolling started.
238 DISALLOW_COPY_AND_ASSIGN(MenuScrollTask
);
241 // MenuController:SelectByCharDetails ----------------------------------------
243 struct MenuController::SelectByCharDetails
{
244 SelectByCharDetails()
251 // Index of the first menu with the specified mnemonic.
254 // If true there are multiple menu items with the same mnemonic.
257 // Index of the selected item; may remain -1.
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.
266 // MenuController:State ------------------------------------------------------
268 MenuController::State::State()
271 anchor(MENU_ANCHOR_TOPLEFT
),
272 context_menu(false) {
275 MenuController::State::~State() {}
277 // MenuController ------------------------------------------------------------
280 MenuController
* MenuController::active_instance_
= NULL
;
283 MenuController
* MenuController::GetActiveInstance() {
284 return active_instance_
;
287 MenuItemView
* MenuController::Run(Widget
* parent
,
290 const gfx::Rect
& bounds
,
291 MenuAnchorPosition position
,
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();
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.
305 View
* root_view
= parent
->GetRootView();
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
;
320 bool nested_menu
= showing_
;
322 // Only support nesting of blocking_run menus, nesting of
323 // blocking/non-blocking shouldn't be needed.
324 DCHECK(blocking_run_
);
326 // We're already showing, push the current state.
327 menu_stack_
.push_back(state_
);
329 // The context menu should be owned by the same parent.
330 DCHECK_EQ(owner_
, parent
);
335 // Reset current state.
336 pending_state_
= State();
338 UpdateInitialLocation(bounds
, position
, context_menu
);
341 owner_
->RemoveObserver(this);
344 owner_
->AddObserver(this);
346 // Set the selection, which opens the initial menu.
347 SetSelection(root
, SELECTION_OPEN_SUBMENU
| SELECTION_UPDATE_IMMEDIATELY
);
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();
357 menu_button_
= button
;
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();
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_
--;
372 if (ViewsDelegate::views_delegate
)
373 ViewsDelegate::views_delegate
->ReleaseRef();
375 // Close any open menus.
376 SetSelection(NULL
, SELECTION_UPDATE_IMMEDIATELY
| SELECTION_EXIT
);
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
386 if (item_selected_by_touch_
) {
387 item_selected_by_touch_
= false;
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));
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();
408 did_capture_
= false;
411 MenuItemView
* result
= result_
;
412 // In case we're nested, reset result_.
415 if (result_event_flags
)
416 *result_event_flags
= accept_event_flags_
;
418 if (exit_type_
== EXIT_OUTERMOST
) {
419 SetExitType(EXIT_NONE
);
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
);
428 // Set exit_all_, which makes sure all nested loops exit immediately.
429 if (exit_type_
!= EXIT_DESTROYED
)
430 SetExitType(EXIT_ALL
);
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();
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
447 if (exit_type_
== EXIT_DESTROYED
|| exit_type_
== type
)
451 // This occurs if we're in the process of notifying the delegate for a drop
452 // and the delegate cancels us.
456 MenuItemView
* selected
= state_
.item
;
459 SendMouseCaptureLostToActiveView();
461 // Hide windows immediately.
462 SetSelection(NULL
, SELECTION_UPDATE_IMMEDIATELY
| SELECTION_EXIT
);
464 if (!blocking_run_
) {
465 // If we didn't block the caller we need to notify the menu, which
466 // triggers deleting us.
469 delegate_
->DropMenuClosed(
470 internal::MenuControllerDelegate::NOTIFY_DELEGATE
,
471 selected
->GetRootMenuItem());
472 // WARNING: the call to MenuClosed deletes us.
477 void MenuController::OnMousePressed(SubmenuView
* source
,
478 const ui::MouseEvent
& event
) {
479 SetSelectionOnPointerDown(source
, event
);
482 void MenuController::OnMouseDragged(SubmenuView
* source
,
483 const ui::MouseEvent
& event
) {
484 MenuPart part
= GetMenuPart(source
, event
.location());
485 UpdateScrolling(part
);
490 if (possible_drag_
) {
491 if (View::ExceededDragThreshold(event
.location() - press_pt_
))
492 StartDrag(source
, press_pt_
);
495 MenuItemView
* mouse_menu
= NULL
;
496 if (part
.type
== MenuPart::MENU_ITEM
) {
498 part
.menu
= source
->GetMenuItem();
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());
505 UpdateActiveMouseView(source
, event
, mouse_menu
);
508 void MenuController::OnMouseReleased(SubmenuView
* source
,
509 const ui::MouseEvent
& event
) {
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.
522 part
.submenu
->child_count() == 1 &&
523 part
.submenu
->child_at(0)->id() == MenuItemView::kEmptyMenuItemViewID
) {
527 if (menu
!= NULL
&& ShowContextMenu(menu
, source
, event
,
528 ui::MENU_SOURCE_MOUSE
))
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
);
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.
555 if (part
.menu
->GetDelegate()->ShouldExecuteCommandWithoutClosingMenu(
556 part
.menu
->GetCommand(), event
)) {
557 part
.menu
->GetDelegate()->ExecuteCommand(part
.menu
->GetCommand(),
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());
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
);
575 SendMouseCaptureLostToActiveView();
578 void MenuController::OnMouseMoved(SubmenuView
* source
,
579 const ui::MouseEvent
& event
) {
580 HandleMouseLocation(source
, event
.location());
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
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
);
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();
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;
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();
621 } else if (event
->type() == ui::ET_GESTURE_TAP_CANCEL
&&
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();
632 if (event
->stopped_propagation())
637 part
.submenu
->OnGestureEvent(event
);
640 bool MenuController::GetDropFormats(
643 std::set
<OSExchangeData::CustomFormat
>* custom_formats
) {
644 return source
->GetMenuItem()->GetDelegate()->GetDropFormats(
645 source
->GetMenuItem(), formats
, custom_formats
);
648 bool MenuController::AreDropTypesRequired(SubmenuView
* source
) {
649 return source
->GetMenuItem()->GetDelegate()->AreDropTypesRequired(
650 source
->GetMenuItem());
653 bool MenuController::CanDrop(SubmenuView
* source
, const OSExchangeData
& data
) {
654 return source
->GetMenuItem()->GetDelegate()->CanDrop(source
->GetMenuItem(),
658 void MenuController::OnDragEntered(SubmenuView
* source
,
659 const ui::DropTargetEvent
& event
) {
660 valid_drop_coordinates_
= false;
663 int MenuController::OnDragUpdated(SubmenuView
* source
,
664 const ui::DropTargetEvent
& event
) {
665 StopCancelAllTimer();
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;
674 MenuItemView
* menu_item
= GetMenuItemAt(source
, event
.x(), event
.y());
675 bool over_empty_menu
= false;
677 // See if we're over an empty menu.
678 menu_item
= GetEmptyMenuItemAt(source
, event
.x(), event
.y());
680 over_empty_menu
= true;
682 MenuDelegate::DropPosition drop_position
= MenuDelegate::DROP_NONE
;
683 int drop_operation
= ui::DragDropTypes::DRAG_NONE
;
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
;
695 drop_position
= (menu_item_loc
.y() < menu_item_height
/ 2) ?
696 MenuDelegate::DROP_BEFORE
: MenuDelegate::DROP_AFTER
;
698 query_menu_item
= menu_item
;
700 query_menu_item
= menu_item
->GetParentMenuItem();
701 drop_position
= MenuDelegate::DROP_ON
;
703 drop_operation
= menu_item
->GetDelegate()->GetDropOperation(
704 query_menu_item
, event
, &drop_position
);
706 // If the menu has a submenu, schedule the submenu to open.
707 SetSelection(menu_item
, menu_item
->HasSubmenu() ? SELECTION_OPEN_SUBMENU
:
710 if (drop_position
== MenuDelegate::DROP_NONE
||
711 drop_operation
== ui::DragDropTypes::DRAG_NONE
)
714 SetSelection(source
->GetMenuItem(), SELECTION_OPEN_SUBMENU
);
716 SetDropMenuItem(menu_item
, drop_position
);
717 last_drop_operation_
= drop_operation
;
718 return drop_operation
;
721 void MenuController::OnDragExited(SubmenuView
* source
) {
722 StartCancelAllTimer();
726 SetDropMenuItem(NULL
, MenuDelegate::DROP_NONE
);
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.
736 MenuItemView
* item
= state_
.item
;
739 MenuItemView
* drop_target
= drop_target_
;
740 MenuDelegate::DropPosition drop_position
= drop_position_
;
742 // Close all menus, including any nested menus.
743 SetSelection(NULL
, SELECTION_UPDATE_IMMEDIATELY
| SELECTION_EXIT
);
744 CloseAllNestedMenus();
746 // Set state such that we exit.
748 SetExitType(EXIT_ALL
);
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();
754 if (!IsBlockingRun()) {
755 delegate_
->DropMenuClosed(
756 internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE
,
757 item
->GetRootMenuItem());
760 // WARNING: the call to MenuClosed deletes us.
762 return drop_target
->GetDelegate()->OnPerformDrop(
763 drop_target
, drop_position
, event
);
766 void MenuController::OnDragEnteredScrollButton(SubmenuView
* source
,
769 part
.type
= is_up
? MenuPart::SCROLL_UP
: MenuPart::SCROLL_DOWN
;
770 part
.submenu
= source
;
771 UpdateScrolling(part
);
773 // Do this to force the selection to hide.
774 SetDropMenuItem(source
->GetMenuItemAt(0), MenuDelegate::DROP_NONE
);
776 StopCancelAllTimer();
779 void MenuController::OnDragExitedScrollButton(SubmenuView
* source
) {
780 StartCancelAllTimer();
781 SetDropMenuItem(NULL
, MenuDelegate::DROP_NONE
);
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
);
796 void MenuController::OnWidgetDestroying(Widget
* widget
) {
797 DCHECK_EQ(owner_
, widget
);
798 owner_
->RemoveObserver(this);
800 message_loop_
->ClearOwner();
804 void MenuController::TurnOffMenuSelectionHoldForTest() {
805 menu_selection_hold_time_ms
= -1;
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
);
816 size_t current_size
= current_path
.size();
817 size_t new_size
= new_path
.size();
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
);
823 button
->SetHotTracked(false);
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
]);
834 current_path
[i
]->SetSelected(false);
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);
843 if (menu_item
&& menu_item
->GetDelegate())
844 menu_item
->GetDelegate()->SelectionChanged(menu_item
);
846 DCHECK(menu_item
|| (selection_types
& SELECTION_EXIT
) != 0);
848 pending_state_
.item
= menu_item
;
849 pending_state_
.submenu_open
= (selection_types
& SELECTION_OPEN_SUBMENU
) != 0;
852 StopCancelAllTimer();
853 // Resets show timer only when pending menu item is changed.
854 if (pending_item_changed
)
857 if (selection_types
& SELECTION_UPDATE_IMMEDIATELY
)
858 CommitPendingSelection();
859 else if (pending_item_changed
)
862 // Notify an accessibility focus event on all menu items except for the root.
864 (MenuDepth(menu_item
) != 1 ||
865 menu_item
->GetType() != MenuItemView::SUBMENU
)) {
866 menu_item
->NotifyAccessibilityEvent(
867 ui::AX_EVENT_FOCUS
, true);
871 void MenuController::SetSelectionOnPointerDown(SubmenuView
* source
,
872 const ui::LocatedEvent
& event
) {
876 DCHECK(!GetActiveMouseView());
878 MenuPart part
= GetMenuPart(source
, event
.location());
879 if (part
.is_scroll())
880 return; // Ignore presses on scroll buttons.
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
))
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();
899 // Mouse wasn't pressed over any menu, or the active menu, cancel.
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
);
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
;
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
);
928 // Do not repost events for Linux Aura because this behavior is more
929 // consistent with the behavior of other Linux apps.
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
;
937 part
.menu
= part
.parent
;
938 selection_types
|= SELECTION_OPEN_SUBMENU
;
940 if (part
.menu
->GetDelegate()->CanDrag(part
.menu
)) {
941 possible_drag_
= true;
942 press_pt_
= event
.location();
944 if (part
.menu
->HasSubmenu())
945 selection_types
|= SELECTION_OPEN_SUBMENU
;
947 SetSelection(part
.menu
, selection_types
);
950 void MenuController::StartDrag(SubmenuView
* source
,
951 const gfx::Point
& location
) {
952 MenuItemView
* item
= state_
.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
);
967 item
->GetDelegate()->WriteDragData(item
, &data
);
968 drag_utils::SetDragImageOnDataObject(*canvas
,
969 press_loc
.OffsetFromOrigin(),
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;
979 if (GetActiveInstance() == this) {
981 // We're still showing, close all menus.
982 CloseAllNestedMenus();
984 } // else case, drop was on us.
985 } // else case, someone canceled us, don't do anything
988 bool MenuController::OnKeyDown(ui::KeyboardCode key_code
) {
989 DCHECK(blocking_run_
);
993 IncrementSelection(-1);
997 IncrementSelection(1);
1000 // Handling of VK_RIGHT and VK_LEFT is different depending on the UI
1002 case ui::VKEY_RIGHT
:
1003 if (base::i18n::IsRTL())
1006 OpenSubmenuChangeSelectionIfCan();
1010 if (base::i18n::IsRTL())
1011 OpenSubmenuChangeSelectionIfCan();
1016 case ui::VKEY_SPACE
:
1017 if (SendAcceleratorToHotTrackedView() == ACCELERATOR_PROCESSED_EXIT
)
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();
1030 SendAcceleratorResultType result
= SendAcceleratorToHotTrackedView();
1031 if (result
== ACCELERATOR_NOT_PROCESSED
&&
1032 pending_state_
.item
->enabled()) {
1033 Accept(pending_state_
.item
, 0);
1035 } else if (result
== ACCELERATOR_PROCESSED_EXIT
) {
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
);
1060 MenuController::MenuController(ui::NativeTheme
* theme
,
1062 internal::MenuControllerDelegate
* delegate
)
1063 : blocking_run_(blocking
),
1065 exit_type_(EXIT_NONE
),
1066 did_capture_(false),
1068 accept_event_flags_(0),
1070 drop_position_(MenuDelegate::DROP_UNKNOWN
),
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),
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;
1090 MenuController::~MenuController() {
1093 owner_
->RemoveObserver(this);
1094 if (active_instance_
== this)
1095 active_instance_
= NULL
;
1097 StopCancelAllTimer();
1100 void MenuController::RunMessageLoop(bool nested_menu
) {
1101 message_loop_
->Run(this, owner_
, nested_menu
);
1104 MenuController::SendAcceleratorResultType
1105 MenuController::SendAcceleratorToHotTrackedView() {
1106 CustomButton
* hot_view
= GetFirstHotTrackedView(pending_state_
.item
);
1108 return ACCELERATOR_NOT_PROCESSED
;
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
;
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);
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
;
1136 pending_state_
.anchor
= position
;
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();
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
;
1154 void MenuController::Accept(MenuItemView
* item
, int event_flags
) {
1155 DCHECK(IsBlockingRun());
1157 if (item
&& !menu_stack_
.empty() &&
1158 !item
->GetDelegate()->ShouldCloseAllMenusOnExecute(item
->GetCommand())) {
1159 SetExitType(EXIT_OUTERMOST
);
1161 SetExitType(EXIT_ALL
);
1163 accept_event_flags_
= event_flags
;
1166 bool MenuController::ShowSiblingMenu(SubmenuView
* source
,
1167 const gfx::Point
& mouse_location
) {
1168 if (!menu_stack_
.empty() || !menu_button_
)
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.
1180 gfx::NativeWindow window_under_mouse
= GetScreen()->GetWindowUnderCursor();
1181 // TODO(oshima): Replace with views only API.
1182 if (!owner_
|| window_under_mouse
!= owner_
->GetNativeWindow())
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
;
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
))
1198 delegate_
->SiblingMenuCreated(alt_menu
);
1201 // If the delegate returns a menu, they must also return a button.
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();
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
);
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
);
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
);
1247 if (menu_item
->GetDelegate()->ShowContextMenu(
1248 menu_item
, menu_item
->GetCommand(), loc
, source_type
)) {
1249 SendMouseCaptureLostToActiveView();
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()) {
1264 i
->submenu_open
= false;
1265 i
->item
= last_item
;
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();
1276 if (child_under_mouse
&& child_under_mouse
->enabled() &&
1277 child_under_mouse
->id() == MenuItemView::kMenuItemViewID
) {
1278 return static_cast<MenuItemView
*>(child_under_mouse
);
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
);
1292 bool MenuController::IsScrollButtonAt(SubmenuView
* source
,
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
;
1304 if (child_under_mouse
== scroll_view
->scroll_down_button()) {
1305 *part
= MenuPart::SCROLL_DOWN
;
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
);
1320 MenuController::MenuPart
MenuController::GetMenuPartByScreenCoordinateUsingMenu(
1322 const gfx::Point
& screen_loc
) {
1324 for (; item
; item
= item
->GetParentMenuItem()) {
1325 if (item
->HasSubmenu() && item
->GetSubmenu()->IsShowing() &&
1326 GetMenuPartByScreenCoordinateImpl(item
->GetSubmenu(), screen_loc
,
1334 bool MenuController::GetMenuPartByScreenCoordinateImpl(
1336 const gfx::Point
& screen_loc
,
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.
1349 if (IsScrollButtonAt(menu
, scroll_view_loc
.x(), scroll_view_loc
.y(),
1351 part
->submenu
= menu
;
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
;
1363 part
->parent
= menu
->GetMenuItem();
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.
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());
1381 void MenuController::CommitPendingSelection() {
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
);
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();
1397 // Copy pending to state_, making sure to preserve the direction menus were
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
);
1404 int menu_depth
= MenuDepth(state_
.item
);
1405 if (menu_depth
== 0) {
1406 state_
.open_leading
.clear();
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();
1415 // Nothing to select.
1420 // Open all the submenus preceeding the last menu item (last menu item is
1422 if (new_path
.size() > 1) {
1423 for (std::vector
<MenuItemView
*>::iterator i
= new_path
.begin();
1424 i
!= new_path
.end() - 1; ++i
) {
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
);
1434 state_
.submenu_open
= false;
1436 } else if (state_
.item
->HasSubmenu() &&
1437 state_
.item
->GetSubmenu()->IsShowing()) {
1438 state_
.item
->GetSubmenu()->Hide();
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.
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());
1455 void MenuController::CloseMenu(MenuItemView
* item
) {
1457 if (!item
->HasSubmenu())
1459 item
->GetSubmenu()->Hide();
1462 void MenuController::OpenMenu(MenuItemView
* item
) {
1464 if (item
->GetSubmenu()->IsShowing()) {
1468 OpenMenuImpl(item
, true);
1469 did_capture_
= true;
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.
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();
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;
1493 // Menus are the only place using kGroupingPropertyKey, so any value (other
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
));
1501 item
->GetSubmenu()->Reposition(bounds
);
1503 showing_submenu_
= false;
1506 void MenuController::MenuChildrenChanged(MenuItemView
* item
) {
1508 // Menu shouldn't be updated during drag operation.
1509 DCHECK(!GetActiveMouseView());
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();
1517 ancestor
= pending_state_
.item
;
1518 while (ancestor
&& ancestor
!= item
)
1519 ancestor
= ancestor
->GetParentMenuItem();
1523 SetSelection(item
, SELECTION_OPEN_SUBMENU
| SELECTION_UPDATE_IMMEDIATELY
);
1524 if (item
->HasSubmenu())
1525 OpenMenuImpl(item
, false);
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
);
1538 size_t common_size
= std::min(old_path
->size(), new_path
->size());
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
]) {
1549 *first_diff_at
= common_size
;
1552 void MenuController::BuildMenuItemPath(MenuItemView
* item
,
1553 std::vector
<MenuItemView
*>* path
) {
1556 BuildMenuItemPath(item
->GetParentMenuItem(), path
);
1557 path
->push_back(item
);
1560 void MenuController::StartShowTimer() {
1561 show_timer_
.Start(FROM_HERE
,
1562 TimeDelta::FromMilliseconds(menu_config_
.show_delay
),
1563 this, &MenuController::CommitPendingSelection
);
1566 void MenuController::StopShowTimer() {
1570 void MenuController::StartCancelAllTimer() {
1571 cancel_all_timer_
.Start(FROM_HERE
,
1572 TimeDelta::FromMilliseconds(kCloseOnExitTime
),
1573 this, &MenuController::CancelAll
);
1576 void MenuController::StopCancelAllTimer() {
1577 cancel_all_timer_
.Stop();
1580 gfx::Rect
MenuController::CalculateMenuBounds(MenuItemView
* item
,
1581 bool prefer_leading
,
1585 SubmenuView
* submenu
= item
->GetSubmenu();
1588 gfx::Size pref
= submenu
->GetScrollViewContainer()->GetPreferredSize();
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()));
1596 // Assume we can honor prefer_leading.
1597 *is_leading
= prefer_leading
;
1601 const MenuConfig
& menu_config
= item
->GetMenuConfig();
1603 if (!item
->GetParentMenuItem()) {
1604 // First item, position relative to initial location.
1605 x
= state_
.initial_bounds
.x();
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
)
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
)
1617 } else if (state_
.anchor
== MENU_ANCHOR_BOTTOMCENTER
) {
1618 x
= x
- (pref
.width() - state_
.initial_bounds
.width()) / 2;
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
;
1624 y
= std::max(0, state_
.initial_bounds
.y() - pref
.height()) +
1625 kCenteredContextMenuYOffset
;
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
;
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();
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.
1672 x
+= state_
.initial_bounds
.width(); // Move the menu right.
1674 // The menu should end with the same x coordinate as the owning
1676 if (state_
.monitor_bounds
.x() >
1677 state_
.initial_bounds
.x() - pref
.width())
1678 x
= state_
.initial_bounds
.right(); // Move right of the button.
1680 x
= state_
.initial_bounds
.x() - pref
.width(); // Move left.
1683 item
->set_actual_menu_position(orientation
);
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
);
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();
1696 item
->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS
);
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();
1706 // Not the first menu; position it relative to the bounds of the menu
1708 gfx::Point item_loc
;
1709 View::ConvertPointToScreen(item
, &item_loc
);
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
;
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()) {
1726 *is_leading
= false;
1727 x
= item_loc
.x() - pref
.width() + submenu_horizontal_inset
;
1730 x
= item_loc
.x() - pref
.width() + submenu_horizontal_inset
;
1731 if (state_
.monitor_bounds
.width() != 0 && x
< state_
.monitor_bounds
.x()) {
1733 *is_leading
= false;
1736 x
= item_loc
.x() + item
->width() - submenu_horizontal_inset
;
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();
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();
1755 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
1758 gfx::Rect
MenuController::CalculateBubbleMenuBounds(MenuItemView
* item
,
1759 bool prefer_leading
,
1762 DCHECK(!item
->GetParentMenuItem());
1764 // Assume we can honor prefer_leading.
1765 *is_leading
= prefer_leading
;
1767 SubmenuView
* submenu
= item
->GetSubmenu();
1770 gfx::Size pref
= submenu
->GetScrollViewContainer()->GetPreferredSize();
1771 const gfx::Rect
& owner_bounds
= pending_state_
.initial_bounds
;
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
;
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
));
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
)));
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
;
1808 y
= owner_bounds
.bottom() - kBubbleTipSizeTopBottom
;
1810 x
= owner_bounds
.CenterPoint().x() - pref
.width() / 2;
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();
1817 submenu
->GetScrollViewContainer()->SetBubbleArrowOffset(
1818 pref
.width() / 2 - x
+ x_old
);
1820 if (state_
.anchor
== MENU_ANCHOR_BUBBLE_RIGHT
)
1821 x
= owner_bounds
.right() - kBubbleTipSizeLeftRight
;
1823 x
= owner_bounds
.x() - pref
.width() + kBubbleTipSizeLeftRight
;
1825 y
= owner_bounds
.CenterPoint().y() - pref
.height() / 2;
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();
1832 submenu
->GetScrollViewContainer()->SetBubbleArrowOffset(
1833 pref
.height() / 2 - y
+ y_old
);
1835 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
1839 int MenuController::MenuDepth(MenuItemView
* item
) {
1840 return item
? (MenuDepth(item
->GetParentMenuItem()) + 1) : 0;
1843 void MenuController::IncrementSelection(int delta
) {
1844 MenuItemView
* item
= pending_state_
.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
);
1856 if (item
->has_children()) {
1857 CustomButton
* button
= GetFirstHotTrackedView(item
);
1859 button
->SetHotTracked(false);
1860 View
* to_make_hot
= GetNextFocusableView(item
, button
, delta
== 1);
1861 CustomButton
* button_hot
= CustomButton::AsCustomButton(to_make_hot
);
1863 button_hot
->SetHotTracked(true);
1867 View
* to_make_hot
= GetInitialFocusableView(item
, delta
== 1);
1868 CustomButton
* button_hot
= CustomButton::AsCustomButton(to_make_hot
);
1870 button_hot
->SetHotTracked(true);
1876 MenuItemView
* parent
= item
->GetParentMenuItem();
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
);
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
);
1890 button_hot
->SetHotTracked(true);
1898 MenuItemView
* MenuController::FindNextSelectableMenuItem(MenuItemView
* parent
,
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.
1906 index
= (index
+ delta
+ parent_count
) % parent_count
;
1907 if (index
== start_index
)
1909 MenuItemView
* child
= parent
->GetSubmenu()->GetMenuItemAt(index
);
1910 if (child
->visible())
1912 } while (index
!= start_index
);
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
);
1923 // No menu items, just show the sub-menu.
1924 SetSelection(item
, SELECTION_OPEN_SUBMENU
| SELECTION_UPDATE_IMMEDIATELY
);
1929 void MenuController::CloseSubmenu() {
1930 MenuItemView
* item
= state_
.item
;
1932 if (!item
->GetParentMenuItem())
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
);
1940 MenuController::SelectByCharDetails
MenuController::FindChildForMnemonic(
1941 MenuItemView
* parent
,
1943 bool (*match_function
)(MenuItemView
* menu
, base::char16 mnemonic
)) {
1944 SubmenuView
* submenu
= parent
->GetSubmenu();
1946 SelectByCharDetails details
;
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
;
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
;
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();
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
);
1981 Accept(submenu
->GetMenuItemAt(details
.first_match
), 0);
1984 } else if (details
.index_of_item
== -1 || details
.next_match
== -1) {
1985 SetSelection(submenu
->GetMenuItemAt(details
.first_match
),
1988 SetSelection(submenu
->GetMenuItemAt(details
.next_match
),
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();
2001 DCHECK(item
->HasSubmenu());
2002 DCHECK(item
->GetSubmenu());
2003 if (item
->GetSubmenu()->GetMenuItemCount() == 0)
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
);
2013 item
->GetSubmenu()->GetTextInputClient()->InsertChar(character
, 0);
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
);
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());
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());
2042 state_
.item
->GetRootMenuItem()->GetSubmenu()->ReleaseCapture();
2045 gfx::Point
screen_loc(event
.location());
2046 View::ConvertPointToScreen(source
->GetScrollViewContainer(), &screen_loc
);
2047 gfx::NativeView native_view
= source
->GetWidget()->GetNativeView();
2051 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(native_view
);
2052 gfx::NativeWindow window
= screen
->GetWindowAtScreenPoint(screen_loc
);
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.
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
;
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.
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
;
2098 int window_x
= screen_loc_pixels
.x();
2099 int window_y
= screen_loc_pixels
.y();
2101 POINT pt
= { window_x
, window_y
};
2102 ScreenToClient(target_window
, &pt
);
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
);
2113 // Non-Windows Aura or |window| is in metro mode.
2117 message_loop_
->RepostEventToWindow(event
, window
, screen_loc
);
2120 void MenuController::SetDropMenuItem(
2121 MenuItemView
* new_target
,
2122 MenuDelegate::DropPosition new_position
) {
2123 if (new_target
== drop_target_
&& new_position
== drop_position_
)
2127 drop_target_
->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem(
2128 NULL
, MenuDelegate::DROP_NONE
);
2130 drop_target_
= new_target
;
2131 drop_position_
= new_position
;
2133 drop_target_
->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem(
2134 drop_target_
, drop_position_
);
2138 void MenuController::UpdateScrolling(const MenuPart
& part
) {
2139 if (!part
.is_scroll() && !scroll_task_
.get())
2142 if (!scroll_task_
.get())
2143 scroll_task_
.reset(new MenuScrollTask());
2144 scroll_task_
->Update(part
);
2147 void MenuController::StopScrolling() {
2148 scroll_task_
.reset(NULL
);
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(),
2163 View::ConvertPointFromScreen(target_menu
, &target_menu_loc
);
2164 target
= target_menu
->GetEventHandlerForPoint(target_menu_loc
);
2165 if (target
== target_menu
|| !target
->enabled())
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
,
2180 active_mouse_view
->OnMouseEntered(mouse_entered_event
);
2182 ui::MouseEvent
mouse_pressed_event(ui::ET_MOUSE_PRESSED
,
2183 target_point
, target_point
,
2185 event
.changed_button_flags());
2186 active_mouse_view
->OnMousePressed(mouse_pressed_event
);
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
,
2196 event
.changed_button_flags());
2197 active_mouse_view
->OnMouseDragged(mouse_dragged_event
);
2201 void MenuController::SendMouseReleaseToActiveView(SubmenuView
* event_source
,
2202 const ui::MouseEvent
& event
) {
2203 View
* active_mouse_view
= GetActiveMouseView();
2204 if (!active_mouse_view
)
2207 gfx::Point
target_loc(event
.location());
2208 View::ConvertPointToScreen(event_source
->GetScrollViewContainer(),
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
);
2219 void MenuController::SendMouseCaptureLostToActiveView() {
2220 View
* active_mouse_view
= GetActiveMouseView();
2221 if (!active_mouse_view
)
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();
2230 void MenuController::SetActiveMouseView(View
* view
) {
2232 ViewStorage::GetInstance()->StoreView(active_mouse_view_id_
, view
);
2234 ViewStorage::GetInstance()->RemoveView(active_mouse_view_id_
);
2237 View
* MenuController::GetActiveMouseView() {
2238 return ViewStorage::GetInstance()->RetrieveView(active_mouse_view_id_
);
2241 void MenuController::SetExitType(ExitType 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.
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_
;
2255 TerminateNestedMessageLoop();
2258 void MenuController::TerminateNestedMessageLoop() {
2259 message_loop_
->QuitNow();
2262 void MenuController::HandleMouseLocation(SubmenuView
* source
,
2263 const gfx::Point
& mouse_location
) {
2264 if (showing_submenu_
)
2267 // Ignore mouse events if we're closing the menu.
2268 if (exit_type_
!= EXIT_NONE
)
2271 MenuPart part
= GetMenuPart(source
, mouse_location
);
2273 UpdateScrolling(part
);
2278 if (part
.type
== MenuPart::NONE
&& ShowSiblingMenu(source
, mouse_location
))
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
);
2294 gfx::Screen
* MenuController::GetScreen() {
2295 Widget
* root
= owner_
? owner_
->GetTopLevelWidget() : NULL
;
2296 return root
? gfx::Screen::GetScreenFor(root
->GetNativeView())
2297 : gfx::Screen::GetNativeScreen();
2300 } // namespace views