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