summaryrefslogtreecommitdiffstats
path: root/views/controls/menu/menu_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'views/controls/menu/menu_controller.cc')
-rw-r--r--views/controls/menu/menu_controller.cc1387
1 files changed, 1387 insertions, 0 deletions
diff --git a/views/controls/menu/menu_controller.cc b/views/controls/menu/menu_controller.cc
new file mode 100644
index 0000000..f8efbbc
--- /dev/null
+++ b/views/controls/menu/menu_controller.cc
@@ -0,0 +1,1387 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "views/controls/menu/menu_controller.h"
+
+#include "app/gfx/canvas.h"
+#include "app/l10n_util.h"
+#include "app/os_exchange_data.h"
+#include "base/base_drag_source.h"
+#include "base/time.h"
+#include "views/controls/menu/menu_scroll_view_container.h"
+#include "views/controls/menu/submenu_view.h"
+#include "views/drag_utils.h"
+#include "views/view_constants.h"
+#include "views/widget/widget.h"
+
+#if defined(OS_WIN)
+#include "app/os_exchange_data_provider_win.h"
+#endif
+
+using base::Time;
+using base::TimeDelta;
+
+// Period of the scroll timer (in milliseconds).
+static const int kScrollTimerMS = 30;
+
+// Delay, in ms, between when menus are selected are moused over and the menu
+// appears.
+static const int kShowDelay = 400;
+
+// Amount of time from when the drop exits the menu and the menu is hidden.
+static const int kCloseOnExitTime = 1200;
+
+// Max width of a menu. There does not appear to be an OS value for this, yet
+// both IE and FF restrict the max width of a menu.
+static const int kMaxMenuWidth = 400;
+
+// Amount to inset submenus.
+static const int kSubmenuHorizontalInset = 3;
+
+namespace views {
+
+// Convenience for scrolling the view such that the origin is visible.
+static void ScrollToVisible(View* view) {
+ view->ScrollRectToVisible(0, 0, view->width(), view->height());
+}
+
+// MenuScrollTask --------------------------------------------------------------
+
+// MenuScrollTask is used when the SubmenuView does not all fit on screen and
+// the mouse is over the scroll up/down buttons. MenuScrollTask schedules
+// itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls
+// appropriately.
+
+class MenuController::MenuScrollTask {
+ public:
+ MenuScrollTask() : submenu_(NULL) {
+ pixels_per_second_ = MenuItemView::pref_menu_height() * 20;
+ }
+
+ void Update(const MenuController::MenuPart& part) {
+ if (!part.is_scroll()) {
+ StopScrolling();
+ return;
+ }
+ DCHECK(part.submenu);
+ SubmenuView* new_menu = part.submenu;
+ bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP);
+ if (new_menu == submenu_ && is_scrolling_up_ == new_is_up)
+ return;
+
+ start_scroll_time_ = Time::Now();
+ start_y_ = part.submenu->GetVisibleBounds().y();
+ submenu_ = new_menu;
+ is_scrolling_up_ = new_is_up;
+
+ if (!scrolling_timer_.IsRunning()) {
+ scrolling_timer_.Start(TimeDelta::FromMilliseconds(kScrollTimerMS), this,
+ &MenuScrollTask::Run);
+ }
+ }
+
+ void StopScrolling() {
+ if (scrolling_timer_.IsRunning()) {
+ scrolling_timer_.Stop();
+ submenu_ = NULL;
+ }
+ }
+
+ // The menu being scrolled. Returns null if not scrolling.
+ SubmenuView* submenu() const { return submenu_; }
+
+ private:
+ void Run() {
+ DCHECK(submenu_);
+ gfx::Rect vis_rect = submenu_->GetVisibleBounds();
+ const int delta_y = static_cast<int>(
+ (Time::Now() - start_scroll_time_).InMilliseconds() *
+ pixels_per_second_ / 1000);
+ int target_y = start_y_;
+ if (is_scrolling_up_)
+ target_y = std::max(0, target_y - delta_y);
+ else
+ target_y = std::min(submenu_->height() - vis_rect.height(),
+ target_y + delta_y);
+ submenu_->ScrollRectToVisible(vis_rect.x(), target_y, vis_rect.width(),
+ vis_rect.height());
+ }
+
+ // SubmenuView being scrolled.
+ SubmenuView* submenu_;
+
+ // Direction scrolling.
+ bool is_scrolling_up_;
+
+ // Timer to periodically scroll.
+ base::RepeatingTimer<MenuScrollTask> scrolling_timer_;
+
+ // Time we started scrolling at.
+ Time start_scroll_time_;
+
+ // How many pixels to scroll per second.
+ int pixels_per_second_;
+
+ // Y-coordinate of submenu_view_ when scrolling started.
+ int start_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollTask);
+};
+
+// MenuController ------------------------------------------------------------
+
+// static
+MenuController* MenuController::active_instance_ = NULL;
+
+// static
+MenuController* MenuController::GetActiveInstance() {
+ return active_instance_;
+}
+
+#ifdef DEBUG_MENU
+static int instance_count = 0;
+static int nested_depth = 0;
+#endif
+
+MenuItemView* MenuController::Run(HWND parent,
+ MenuItemView* root,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position,
+ int* result_mouse_event_flags) {
+ exit_all_ = false;
+ possible_drag_ = false;
+
+ bool nested_menu = showing_;
+ if (showing_) {
+ // Only support nesting of blocking_run menus, nesting of
+ // blocking/non-blocking shouldn't be needed.
+ DCHECK(blocking_run_);
+
+ // We're already showing, push the current state.
+ menu_stack_.push_back(state_);
+
+ // The context menu should be owned by the same parent.
+ DCHECK(owner_ == parent);
+ } else {
+ showing_ = true;
+ }
+
+ // Reset current state.
+ pending_state_ = State();
+ state_ = State();
+ pending_state_.initial_bounds = bounds;
+ if (bounds.height() > 1) {
+ // Inset the bounds slightly, otherwise drag coordinates don't line up
+ // nicely and menus close prematurely.
+ pending_state_.initial_bounds.Inset(0, 1);
+ }
+ pending_state_.anchor = position;
+ owner_ = parent;
+
+ // Calculate the bounds of the monitor we'll show menus on. Do this once to
+ // avoid repeated system queries for the info.
+ POINT initial_loc = { bounds.x(), bounds.y() };
+ HMONITOR monitor = MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST);
+ if (monitor) {
+ MONITORINFO mi = {0};
+ mi.cbSize = sizeof(mi);
+ GetMonitorInfo(monitor, &mi);
+ // Menus appear over the taskbar.
+ pending_state_.monitor_bounds = gfx::Rect(mi.rcMonitor);
+ }
+
+ // Set the selection, which opens the initial menu.
+ SetSelection(root, true, true);
+
+ if (!blocking_run_) {
+ // Start the timer to hide the menu. This is needed as we get no
+ // notification when the drag has finished.
+ StartCancelAllTimer();
+ return NULL;
+ }
+
+#ifdef DEBUG_MENU
+ nested_depth++;
+ DLOG(INFO) << " entering nested loop, depth=" << nested_depth;
+#endif
+
+ MessageLoopForUI* loop = MessageLoopForUI::current();
+ if (MenuItemView::allow_task_nesting_during_run_) {
+ bool did_allow_task_nesting = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+ loop->Run(this);
+ loop->SetNestableTasksAllowed(did_allow_task_nesting);
+ } else {
+ loop->Run(this);
+ }
+
+#ifdef DEBUG_MENU
+ nested_depth--;
+ DLOG(INFO) << " exiting nested loop, depth=" << nested_depth;
+#endif
+
+ // Close any open menus.
+ SetSelection(NULL, false, true);
+
+ if (nested_menu) {
+ DCHECK(!menu_stack_.empty());
+ // We're running from within a menu, restore the previous state.
+ // The menus are already showing, so we don't have to show them.
+ state_ = menu_stack_.back();
+ pending_state_ = menu_stack_.back();
+ menu_stack_.pop_back();
+ } else {
+ showing_ = false;
+ did_capture_ = false;
+ }
+
+ MenuItemView* result = result_;
+ // In case we're nested, reset result_.
+ result_ = NULL;
+
+ if (result_mouse_event_flags)
+ *result_mouse_event_flags = result_mouse_event_flags_;
+
+ if (nested_menu && result) {
+ // We're nested and about to return a value. The caller might enter another
+ // blocking loop. We need to make sure all menus are hidden before that
+ // happens otherwise the menus will stay on screen.
+ CloseAllNestedMenus();
+
+ // Set exit_all_ to true, which makes sure all nested loops exit
+ // immediately.
+ exit_all_ = true;
+ }
+
+ return result;
+}
+
+void MenuController::SetSelection(MenuItemView* menu_item,
+ bool open_submenu,
+ bool update_immediately) {
+ size_t paths_differ_at = 0;
+ std::vector<MenuItemView*> current_path;
+ std::vector<MenuItemView*> new_path;
+ BuildPathsAndCalculateDiff(pending_state_.item, menu_item, &current_path,
+ &new_path, &paths_differ_at);
+
+ size_t current_size = current_path.size();
+ size_t new_size = new_path.size();
+
+ // Notify the old path it isn't selected.
+ for (size_t i = paths_differ_at; i < current_size; ++i)
+ current_path[i]->SetSelected(false);
+
+ // Notify the new path it is selected.
+ for (size_t i = paths_differ_at; i < new_size; ++i)
+ new_path[i]->SetSelected(true);
+
+ if (menu_item && menu_item->GetDelegate())
+ menu_item->GetDelegate()->SelectionChanged(menu_item);
+
+ pending_state_.item = menu_item;
+ pending_state_.submenu_open = open_submenu;
+
+ // Stop timers.
+ StopShowTimer();
+ StopCancelAllTimer();
+
+ if (update_immediately)
+ CommitPendingSelection();
+ else
+ StartShowTimer();
+}
+
+void MenuController::Cancel(bool all) {
+ if (!showing_) {
+ // This occurs if we're in the process of notifying the delegate for a drop
+ // and the delegate cancels us.
+ return;
+ }
+
+ MenuItemView* selected = state_.item;
+ exit_all_ = all;
+
+ // Hide windows immediately.
+ SetSelection(NULL, false, true);
+
+ if (!blocking_run_) {
+ // If we didn't block the caller we need to notify the menu, which
+ // triggers deleting us.
+ DCHECK(selected);
+ showing_ = false;
+ selected->GetRootMenuItem()->DropMenuClosed(true);
+ // WARNING: the call to MenuClosed deletes us.
+ return;
+ }
+}
+
+void MenuController::OnMousePressed(SubmenuView* source,
+ const MouseEvent& event) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "OnMousePressed source=" << source;
+#endif
+ if (!blocking_run_)
+ return;
+
+ MenuPart part =
+ GetMenuPartByScreenCoordinate(source, event.x(), event.y());
+ if (part.is_scroll())
+ return; // Ignore presses on scroll buttons.
+
+ if (part.type == MenuPart::NONE ||
+ (part.type == MenuPart::MENU_ITEM && part.menu &&
+ part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) {
+ // Mouse wasn't pressed over any menu, or the active menu, cancel.
+
+ // We're going to close and we own the mouse capture. We need to repost the
+ // mouse down, otherwise the window the user clicked on won't get the
+ // event.
+ RepostEvent(source, event);
+
+ // And close.
+ Cancel(true);
+ return;
+ }
+
+ bool open_submenu = false;
+ if (!part.menu) {
+ part.menu = part.parent;
+ open_submenu = true;
+ } else {
+ if (part.menu->GetDelegate()->CanDrag(part.menu)) {
+ possible_drag_ = true;
+ press_x_ = event.x();
+ press_y_ = event.y();
+ }
+ if (part.menu->HasSubmenu())
+ open_submenu = true;
+ }
+ // On a press we immediately commit the selection, that way a submenu
+ // pops up immediately rather than after a delay.
+ SetSelection(part.menu, open_submenu, true);
+}
+
+void MenuController::OnMouseDragged(SubmenuView* source,
+ const MouseEvent& event) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "OnMouseDragged source=" << source;
+#endif
+ MenuPart part =
+ GetMenuPartByScreenCoordinate(source, event.x(), event.y());
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (possible_drag_) {
+ if (View::ExceededDragThreshold(event.x() - press_x_,
+ event.y() - press_y_)) {
+ MenuItemView* item = state_.item;
+ DCHECK(item);
+ // Points are in the coordinates of the submenu, need to map to that of
+ // the selected item. Additionally source may not be the parent of
+ // the selected item, so need to map to screen first then to item.
+ gfx::Point press_loc(press_x_, press_y_);
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc);
+ View::ConvertPointToView(NULL, item, &press_loc);
+ gfx::Point drag_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &drag_loc);
+ View::ConvertPointToView(NULL, item, &drag_loc);
+ gfx::Canvas canvas(item->width(), item->height(), false);
+ item->Paint(&canvas, true);
+
+ OSExchangeData data;
+ item->GetDelegate()->WriteDragData(item, &data);
+ drag_utils::SetDragImageOnDataObject(canvas, item->width(),
+ item->height(), press_loc.x(),
+ press_loc.y(), &data);
+
+ scoped_refptr<BaseDragSource> drag_source(new BaseDragSource);
+ int drag_ops = item->GetDelegate()->GetDragOperations(item);
+ DWORD effects;
+ StopScrolling();
+ DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source,
+ DragDropTypes::DragOperationToDropEffect(drag_ops),
+ &effects);
+ if (GetActiveInstance() == this) {
+ if (showing_) {
+ // We're still showing, close all menus.
+ CloseAllNestedMenus();
+ Cancel(true);
+ } // else case, drop was on us.
+ } // else case, someone canceled us, don't do anything
+ }
+ return;
+ }
+ if (part.type == MenuPart::MENU_ITEM) {
+ if (!part.menu)
+ part.menu = source->GetMenuItem();
+ SetSelection(part.menu ? part.menu : state_.item, true, false);
+ }
+}
+
+void MenuController::OnMouseReleased(SubmenuView* source,
+ const MouseEvent& event) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "OnMouseReleased source=" << source;
+#endif
+ if (!blocking_run_)
+ return;
+
+ DCHECK(state_.item);
+ possible_drag_ = false;
+ DCHECK(blocking_run_);
+ MenuPart part =
+ GetMenuPartByScreenCoordinate(source, event.x(), event.y());
+ if (event.IsRightMouseButton() && (part.type == MenuPart::MENU_ITEM &&
+ part.menu)) {
+ // Set the selection immediately, making sure the submenu is only open
+ // if it already was.
+ bool open_submenu = (state_.item == pending_state_.item &&
+ state_.submenu_open);
+ SetSelection(pending_state_.item, open_submenu, true);
+ gfx::Point loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc);
+
+ // If we open a context menu just return now
+ if (part.menu->GetDelegate()->ShowContextMenu(
+ part.menu, part.menu->GetCommand(), loc.x(), loc.y(), true))
+ return;
+ }
+
+ if (!part.is_scroll() && part.menu && !part.menu->HasSubmenu()) {
+ if (part.menu->GetDelegate()->IsTriggerableEvent(event)) {
+ Accept(part.menu, event.GetFlags());
+ return;
+ }
+ } else if (part.type == MenuPart::MENU_ITEM) {
+ // User either clicked on empty space, or a menu that has children.
+ SetSelection(part.menu ? part.menu : state_.item, true, true);
+ }
+}
+
+void MenuController::OnMouseMoved(SubmenuView* source,
+ const MouseEvent& event) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "OnMouseMoved source=" << source;
+#endif
+ if (showing_submenu_)
+ return;
+
+ MenuPart part =
+ GetMenuPartByScreenCoordinate(source, event.x(), event.y());
+
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (part.type == MenuPart::MENU_ITEM && part.menu) {
+ SetSelection(part.menu, true, false);
+ } else if (!part.is_scroll() && pending_state_.item &&
+ (!pending_state_.item->HasSubmenu() ||
+ !pending_state_.item->GetSubmenu()->IsShowing())) {
+ // On exit if the user hasn't selected an item with a submenu, move the
+ // selection back to the parent menu item.
+ SetSelection(pending_state_.item->GetParentMenuItem(), true, false);
+ }
+}
+
+void MenuController::OnMouseEntered(SubmenuView* source,
+ const MouseEvent& event) {
+ // MouseEntered is always followed by a mouse moved, so don't need to
+ // do anything here.
+}
+
+bool MenuController::CanDrop(SubmenuView* source, const OSExchangeData& data) {
+ return source->GetMenuItem()->GetDelegate()->CanDrop(source->GetMenuItem(),
+ data);
+}
+
+void MenuController::OnDragEntered(SubmenuView* source,
+ const DropTargetEvent& event) {
+ valid_drop_coordinates_ = false;
+}
+
+int MenuController::OnDragUpdated(SubmenuView* source,
+ const DropTargetEvent& event) {
+ StopCancelAllTimer();
+
+ gfx::Point screen_loc(event.location());
+ View::ConvertPointToScreen(source, &screen_loc);
+ if (valid_drop_coordinates_ && screen_loc.x() == drop_x_ &&
+ screen_loc.y() == drop_y_) {
+ return last_drop_operation_;
+ }
+ drop_x_ = screen_loc.x();
+ drop_y_ = screen_loc.y();
+ valid_drop_coordinates_ = true;
+
+ MenuItemView* menu_item = GetMenuItemAt(source, event.x(), event.y());
+ bool over_empty_menu = false;
+ if (!menu_item) {
+ // See if we're over an empty menu.
+ menu_item = GetEmptyMenuItemAt(source, event.x(), event.y());
+ if (menu_item)
+ over_empty_menu = true;
+ }
+ MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE;
+ int drop_operation = DragDropTypes::DRAG_NONE;
+ if (menu_item) {
+ gfx::Point menu_item_loc(event.location());
+ View::ConvertPointToView(source, menu_item, &menu_item_loc);
+ MenuItemView* query_menu_item;
+ if (!over_empty_menu) {
+ int menu_item_height = menu_item->height();
+ if (menu_item->HasSubmenu() &&
+ (menu_item_loc.y() > kDropBetweenPixels &&
+ menu_item_loc.y() < (menu_item_height - kDropBetweenPixels))) {
+ drop_position = MenuDelegate::DROP_ON;
+ } else if (menu_item_loc.y() < menu_item_height / 2) {
+ drop_position = MenuDelegate::DROP_BEFORE;
+ } else {
+ drop_position = MenuDelegate::DROP_AFTER;
+ }
+ query_menu_item = menu_item;
+ } else {
+ query_menu_item = menu_item->GetParentMenuItem();
+ drop_position = MenuDelegate::DROP_ON;
+ }
+ drop_operation = menu_item->GetDelegate()->GetDropOperation(
+ query_menu_item, event, &drop_position);
+
+ if (menu_item->HasSubmenu()) {
+ // The menu has a submenu, schedule the submenu to open.
+ SetSelection(menu_item, true, false);
+ } else {
+ SetSelection(menu_item, false, false);
+ }
+
+ if (drop_position == MenuDelegate::DROP_NONE ||
+ drop_operation == DragDropTypes::DRAG_NONE) {
+ menu_item = NULL;
+ }
+ } else {
+ SetSelection(source->GetMenuItem(), true, false);
+ }
+ SetDropMenuItem(menu_item, drop_position);
+ last_drop_operation_ = drop_operation;
+ return drop_operation;
+}
+
+void MenuController::OnDragExited(SubmenuView* source) {
+ StartCancelAllTimer();
+
+ if (drop_target_) {
+ StopShowTimer();
+ SetDropMenuItem(NULL, MenuDelegate::DROP_NONE);
+ }
+}
+
+int MenuController::OnPerformDrop(SubmenuView* source,
+ const DropTargetEvent& event) {
+ DCHECK(drop_target_);
+ // NOTE: the delegate may delete us after invoking OnPerformDrop, as such
+ // we don't call cancel here.
+
+ MenuItemView* item = state_.item;
+ DCHECK(item);
+
+ MenuItemView* drop_target = drop_target_;
+ MenuDelegate::DropPosition drop_position = drop_position_;
+
+ // Close all menus, including any nested menus.
+ SetSelection(NULL, false, true);
+ CloseAllNestedMenus();
+
+ // Set state such that we exit.
+ showing_ = false;
+ exit_all_ = true;
+
+ if (!IsBlockingRun())
+ item->GetRootMenuItem()->DropMenuClosed(false);
+
+ // WARNING: the call to MenuClosed deletes us.
+
+ // If over an empty menu item, drop occurs on the parent.
+ if (drop_target->GetID() == MenuItemView::kEmptyMenuItemViewID)
+ drop_target = drop_target->GetParentMenuItem();
+
+ return drop_target->GetDelegate()->OnPerformDrop(
+ drop_target, drop_position, event);
+}
+
+void MenuController::OnDragEnteredScrollButton(SubmenuView* source,
+ bool is_up) {
+ MenuPart part;
+ part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN;
+ part.submenu = source;
+ UpdateScrolling(part);
+
+ // Do this to force the selection to hide.
+ SetDropMenuItem(source->GetMenuItemAt(0), MenuDelegate::DROP_NONE);
+
+ StopCancelAllTimer();
+}
+
+void MenuController::OnDragExitedScrollButton(SubmenuView* source) {
+ StartCancelAllTimer();
+ SetDropMenuItem(NULL, MenuDelegate::DROP_NONE);
+ StopScrolling();
+}
+
+// static
+void MenuController::SetActiveInstance(MenuController* controller) {
+ active_instance_ = controller;
+}
+
+bool MenuController::Dispatch(const MSG& msg) {
+ DCHECK(blocking_run_);
+
+ if (exit_all_) {
+ // We must translate/dispatch the message here, otherwise we would drop
+ // the message on the floor.
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return false;
+ }
+
+ // NOTE: we don't get WM_ACTIVATE or anything else interesting in here.
+ switch (msg.message) {
+ case WM_CONTEXTMENU: {
+ MenuItemView* item = pending_state_.item;
+ if (item && item->GetRootMenuItem() != item) {
+ gfx::Point screen_loc(0, item->height());
+ View::ConvertPointToScreen(item, &screen_loc);
+ item->GetDelegate()->ShowContextMenu(
+ item, item->GetCommand(), screen_loc.x(), screen_loc.y(), false);
+ }
+ return true;
+ }
+
+ // NOTE: focus wasn't changed when the menu was shown. As such, don't
+ // dispatch key events otherwise the focused window will get the events.
+ case WM_KEYDOWN:
+ return OnKeyDown(msg);
+
+ case WM_CHAR:
+ return OnChar(msg);
+
+ case WM_KEYUP:
+ return true;
+
+ case WM_SYSKEYUP:
+ // We may have been shown on a system key, as such don't do anything
+ // here. If another system key is pushed we'll get a WM_SYSKEYDOWN and
+ // close the menu.
+ return true;
+
+ case WM_CANCELMODE:
+ case WM_SYSKEYDOWN:
+ // Exit immediately on system keys.
+ Cancel(true);
+ return false;
+
+ default:
+ break;
+ }
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return !exit_all_;
+}
+
+bool MenuController::OnKeyDown(const MSG& msg) {
+ DCHECK(blocking_run_);
+
+ switch (msg.wParam) {
+ case VK_UP:
+ IncrementSelection(-1);
+ break;
+
+ case VK_DOWN:
+ IncrementSelection(1);
+ break;
+
+ // Handling of VK_RIGHT and VK_LEFT is different depending on the UI
+ // layout.
+ case VK_RIGHT:
+ if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT)
+ CloseSubmenu();
+ else
+ OpenSubmenuChangeSelectionIfCan();
+ break;
+
+ case VK_LEFT:
+ if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT)
+ OpenSubmenuChangeSelectionIfCan();
+ else
+ CloseSubmenu();
+ break;
+
+ case VK_RETURN:
+ if (pending_state_.item) {
+ if (pending_state_.item->HasSubmenu()) {
+ OpenSubmenuChangeSelectionIfCan();
+ } else if (pending_state_.item->IsEnabled()) {
+ Accept(pending_state_.item, 0);
+ return false;
+ }
+ }
+ break;
+
+ case VK_ESCAPE:
+ if (!state_.item->GetParentMenuItem() ||
+ (!state_.item->GetParentMenuItem()->GetParentMenuItem() &&
+ (!state_.item->HasSubmenu() ||
+ !state_.item->GetSubmenu()->IsShowing()))) {
+ // User pressed escape and only one menu is shown, cancel it.
+ Cancel(false);
+ return false;
+ } else {
+ CloseSubmenu();
+ }
+ break;
+
+ case VK_APPS:
+ break;
+
+ default:
+ TranslateMessage(&msg);
+ break;
+ }
+ return true;
+}
+
+bool MenuController::OnChar(const MSG& msg) {
+ DCHECK(blocking_run_);
+
+ return !SelectByChar(static_cast<wchar_t>(msg.wParam));
+}
+
+MenuController::MenuController(bool blocking)
+ : blocking_run_(blocking),
+ showing_(false),
+ exit_all_(false),
+ did_capture_(false),
+ result_(NULL),
+ drop_target_(NULL),
+ owner_(NULL),
+ possible_drag_(false),
+ valid_drop_coordinates_(false),
+ showing_submenu_(false),
+ result_mouse_event_flags_(0) {
+#ifdef DEBUG_MENU
+ instance_count++;
+ DLOG(INFO) << "created MC, count=" << instance_count;
+#endif
+}
+
+MenuController::~MenuController() {
+ DCHECK(!showing_);
+ StopShowTimer();
+ StopCancelAllTimer();
+#ifdef DEBUG_MENU
+ instance_count--;
+ DLOG(INFO) << "destroyed MC, count=" << instance_count;
+#endif
+}
+
+void MenuController::Accept(MenuItemView* item, int mouse_event_flags) {
+ DCHECK(IsBlockingRun());
+ result_ = item;
+ exit_all_ = true;
+ result_mouse_event_flags_ = mouse_event_flags;
+}
+
+void MenuController::CloseAllNestedMenus() {
+ for (std::list<State>::iterator i = menu_stack_.begin();
+ i != menu_stack_.end(); ++i) {
+ MenuItemView* item = i->item;
+ MenuItemView* last_item = item;
+ while (item) {
+ CloseMenu(item);
+ last_item = item;
+ item = item->GetParentMenuItem();
+ }
+ i->submenu_open = false;
+ i->item = last_item;
+ }
+}
+
+MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) {
+ View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y));
+ if (child_under_mouse && child_under_mouse->IsEnabled() &&
+ child_under_mouse->GetID() == MenuItemView::kMenuItemViewID) {
+ return static_cast<MenuItemView*>(child_under_mouse);
+ }
+ return NULL;
+}
+
+MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) {
+ View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y));
+ if (child_under_mouse &&
+ child_under_mouse->GetID() == MenuItemView::kEmptyMenuItemViewID) {
+ return static_cast<MenuItemView*>(child_under_mouse);
+ }
+ return NULL;
+}
+
+bool MenuController::IsScrollButtonAt(SubmenuView* source,
+ int x,
+ int y,
+ MenuPart::Type* part) {
+ MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer();
+ View* child_under_mouse = scroll_view->GetViewForPoint(gfx::Point(x, y));
+ if (child_under_mouse && child_under_mouse->IsEnabled()) {
+ if (child_under_mouse == scroll_view->scroll_up_button()) {
+ *part = MenuPart::SCROLL_UP;
+ return true;
+ }
+ if (child_under_mouse == scroll_view->scroll_down_button()) {
+ *part = MenuPart::SCROLL_DOWN;
+ return true;
+ }
+ }
+ return false;
+}
+
+MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinate(
+ SubmenuView* source,
+ int source_x,
+ int source_y) {
+ MenuPart part;
+
+ gfx::Point screen_loc(source_x, source_y);
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+
+ MenuItemView* item = state_.item;
+ while (item) {
+ if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() &&
+ GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc,
+ &part)) {
+ return part;
+ }
+ item = item->GetParentMenuItem();
+ }
+
+ return part;
+}
+
+bool MenuController::GetMenuPartByScreenCoordinateImpl(
+ SubmenuView* menu,
+ const gfx::Point& screen_loc,
+ MenuPart* part) {
+ // Is the mouse over the scroll buttons?
+ gfx::Point scroll_view_loc = screen_loc;
+ View* scroll_view_container = menu->GetScrollViewContainer();
+ View::ConvertPointToView(NULL, scroll_view_container, &scroll_view_loc);
+ if (scroll_view_loc.x() < 0 ||
+ scroll_view_loc.x() >= scroll_view_container->width() ||
+ scroll_view_loc.y() < 0 ||
+ scroll_view_loc.y() >= scroll_view_container->height()) {
+ // Point isn't contained in menu.
+ return false;
+ }
+ if (IsScrollButtonAt(menu, scroll_view_loc.x(), scroll_view_loc.y(),
+ &(part->type))) {
+ part->submenu = menu;
+ return true;
+ }
+
+ // Not over the scroll button. Check the actual menu.
+ if (DoesSubmenuContainLocation(menu, screen_loc)) {
+ gfx::Point menu_loc = screen_loc;
+ View::ConvertPointToView(NULL, menu, &menu_loc);
+ part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y());
+ part->type = MenuPart::MENU_ITEM;
+ if (!part->menu)
+ part->parent = menu->GetMenuItem();
+ return true;
+ }
+
+ // While the mouse isn't over a menu item or the scroll buttons of menu, it
+ // is contained by menu and so we return true. If we didn't return true other
+ // menus would be searched, even though they are likely obscured by us.
+ return true;
+}
+
+bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu,
+ const gfx::Point& screen_loc) {
+ gfx::Point view_loc = screen_loc;
+ View::ConvertPointToView(NULL, submenu, &view_loc);
+ gfx::Rect vis_rect = submenu->GetVisibleBounds();
+ return vis_rect.Contains(view_loc.x(), view_loc.y());
+}
+
+void MenuController::CommitPendingSelection() {
+ StopShowTimer();
+
+ size_t paths_differ_at = 0;
+ std::vector<MenuItemView*> current_path;
+ std::vector<MenuItemView*> new_path;
+ BuildPathsAndCalculateDiff(state_.item, pending_state_.item, &current_path,
+ &new_path, &paths_differ_at);
+
+ // Hide the old menu.
+ for (size_t i = paths_differ_at; i < current_path.size(); ++i) {
+ if (current_path[i]->HasSubmenu()) {
+ current_path[i]->GetSubmenu()->Hide();
+ }
+ }
+
+ // Copy pending to state_, making sure to preserve the direction menus were
+ // opened.
+ std::list<bool> pending_open_direction;
+ state_.open_leading.swap(pending_open_direction);
+ state_ = pending_state_;
+ state_.open_leading.swap(pending_open_direction);
+
+ int menu_depth = MenuDepth(state_.item);
+ if (menu_depth == 0) {
+ state_.open_leading.clear();
+ } else {
+ int cached_size = static_cast<int>(state_.open_leading.size());
+ DCHECK(menu_depth >= 0);
+ while (cached_size-- >= menu_depth)
+ state_.open_leading.pop_back();
+ }
+
+ if (!state_.item) {
+ // Nothing to select.
+ StopScrolling();
+ return;
+ }
+
+ // Open all the submenus preceeding the last menu item (last menu item is
+ // handled next).
+ if (new_path.size() > 1) {
+ for (std::vector<MenuItemView*>::iterator i = new_path.begin();
+ i != new_path.end() - 1; ++i) {
+ OpenMenu(*i);
+ }
+ }
+
+ if (state_.submenu_open) {
+ // The submenu should be open, open the submenu if the item has a submenu.
+ if (state_.item->HasSubmenu()) {
+ OpenMenu(state_.item);
+ } else {
+ state_.submenu_open = false;
+ }
+ } else if (state_.item->HasSubmenu() &&
+ state_.item->GetSubmenu()->IsShowing()) {
+ state_.item->GetSubmenu()->Hide();
+ }
+
+ if (scroll_task_.get() && scroll_task_->submenu()) {
+ // Stop the scrolling if none of the elements of the selection contain
+ // the menu being scrolled.
+ bool found = false;
+ MenuItemView* item = state_.item;
+ while (item && !found) {
+ found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() &&
+ item->GetSubmenu() == scroll_task_->submenu());
+ item = item->GetParentMenuItem();
+ }
+ if (!found)
+ StopScrolling();
+ }
+}
+
+void MenuController::CloseMenu(MenuItemView* item) {
+ DCHECK(item);
+ if (!item->HasSubmenu())
+ return;
+ item->GetSubmenu()->Hide();
+}
+
+void MenuController::OpenMenu(MenuItemView* item) {
+ DCHECK(item);
+ if (item->GetSubmenu()->IsShowing()) {
+ return;
+ }
+
+ bool prefer_leading =
+ state_.open_leading.empty() ? true : state_.open_leading.back();
+ bool resulting_direction;
+ gfx::Rect bounds =
+ CalculateMenuBounds(item, prefer_leading, &resulting_direction);
+ state_.open_leading.push_back(resulting_direction);
+ bool do_capture = (!did_capture_ && blocking_run_);
+ showing_submenu_ = true;
+ item->GetSubmenu()->ShowAt(owner_, bounds, do_capture);
+ showing_submenu_ = false;
+ did_capture_ = true;
+}
+
+void MenuController::BuildPathsAndCalculateDiff(
+ MenuItemView* old_item,
+ MenuItemView* new_item,
+ std::vector<MenuItemView*>* old_path,
+ std::vector<MenuItemView*>* new_path,
+ size_t* first_diff_at) {
+ DCHECK(old_path && new_path && first_diff_at);
+ BuildMenuItemPath(old_item, old_path);
+ BuildMenuItemPath(new_item, new_path);
+
+ size_t common_size = std::min(old_path->size(), new_path->size());
+
+ // Find the first difference between the two paths, when the loop
+ // returns, diff_i is the first index where the two paths differ.
+ for (size_t i = 0; i < common_size; ++i) {
+ if ((*old_path)[i] != (*new_path)[i]) {
+ *first_diff_at = i;
+ return;
+ }
+ }
+
+ *first_diff_at = common_size;
+}
+
+void MenuController::BuildMenuItemPath(MenuItemView* item,
+ std::vector<MenuItemView*>* path) {
+ if (!item)
+ return;
+ BuildMenuItemPath(item->GetParentMenuItem(), path);
+ path->push_back(item);
+}
+
+void MenuController::StartShowTimer() {
+ show_timer_.Start(TimeDelta::FromMilliseconds(kShowDelay), this,
+ &MenuController::CommitPendingSelection);
+}
+
+void MenuController::StopShowTimer() {
+ show_timer_.Stop();
+}
+
+void MenuController::StartCancelAllTimer() {
+ cancel_all_timer_.Start(TimeDelta::FromMilliseconds(kCloseOnExitTime),
+ this, &MenuController::CancelAll);
+}
+
+void MenuController::StopCancelAllTimer() {
+ cancel_all_timer_.Stop();
+}
+
+gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item,
+ bool prefer_leading,
+ bool* is_leading) {
+ DCHECK(item);
+
+ SubmenuView* submenu = item->GetSubmenu();
+ DCHECK(submenu);
+
+ gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize();
+
+ // Don't let the menu go to wide. This is some where between what IE and FF
+ // do.
+ pref.set_width(std::min(pref.width(), kMaxMenuWidth));
+ if (!state_.monitor_bounds.IsEmpty())
+ pref.set_width(std::min(pref.width(), state_.monitor_bounds.width()));
+
+ // Assume we can honor prefer_leading.
+ *is_leading = prefer_leading;
+
+ int x, y;
+
+ if (!item->GetParentMenuItem()) {
+ // First item, position relative to initial location.
+ x = state_.initial_bounds.x();
+ y = state_.initial_bounds.bottom();
+ if (state_.anchor == MenuItemView::TOPRIGHT)
+ x = x + state_.initial_bounds.width() - pref.width();
+ if (!state_.monitor_bounds.IsEmpty() &&
+ y + pref.height() > state_.monitor_bounds.bottom()) {
+ // The menu doesn't fit on screen. If the first location is above the
+ // half way point, show from the mouse location to bottom of screen.
+ // Otherwise show from the top of the screen to the location of the mouse.
+ // While odd, this behavior matches IE.
+ if (y < (state_.monitor_bounds.y() +
+ state_.monitor_bounds.height() / 2)) {
+ pref.set_height(std::min(pref.height(),
+ state_.monitor_bounds.bottom() - y));
+ } else {
+ pref.set_height(std::min(pref.height(),
+ state_.initial_bounds.y() - state_.monitor_bounds.y()));
+ y = state_.initial_bounds.y() - pref.height();
+ }
+ }
+ } else {
+ // Not the first menu; position it relative to the bounds of the menu
+ // item.
+ gfx::Point item_loc;
+ View::ConvertPointToScreen(item, &item_loc);
+
+ // We must make sure we take into account the UI layout. If the layout is
+ // RTL, then a 'leading' menu is positioned to the left of the parent menu
+ // item and not to the right.
+ bool layout_is_rtl = item->UILayoutIsRightToLeft();
+ bool create_on_the_right = (prefer_leading && !layout_is_rtl) ||
+ (!prefer_leading && layout_is_rtl);
+
+ if (create_on_the_right) {
+ x = item_loc.x() + item->width() - kSubmenuHorizontalInset;
+ if (state_.monitor_bounds.width() != 0 &&
+ x + pref.width() > state_.monitor_bounds.right()) {
+ if (layout_is_rtl)
+ *is_leading = true;
+ else
+ *is_leading = false;
+ x = item_loc.x() - pref.width() + kSubmenuHorizontalInset;
+ }
+ } else {
+ x = item_loc.x() - pref.width() + kSubmenuHorizontalInset;
+ if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) {
+ if (layout_is_rtl)
+ *is_leading = false;
+ else
+ *is_leading = true;
+ x = item_loc.x() + item->width() - kSubmenuHorizontalInset;
+ }
+ }
+ y = item_loc.y() - SubmenuView::kSubmenuBorderSize;
+ if (state_.monitor_bounds.width() != 0) {
+ pref.set_height(std::min(pref.height(), state_.monitor_bounds.height()));
+ if (y + pref.height() > state_.monitor_bounds.bottom())
+ y = state_.monitor_bounds.bottom() - pref.height();
+ if (y < state_.monitor_bounds.y())
+ y = state_.monitor_bounds.y();
+ }
+ }
+
+ if (state_.monitor_bounds.width() != 0) {
+ if (x + pref.width() > state_.monitor_bounds.right())
+ x = state_.monitor_bounds.right() - pref.width();
+ if (x < state_.monitor_bounds.x())
+ x = state_.monitor_bounds.x();
+ }
+ return gfx::Rect(x, y, pref.width(), pref.height());
+}
+
+// static
+int MenuController::MenuDepth(MenuItemView* item) {
+ if (!item)
+ return 0;
+ return MenuDepth(item->GetParentMenuItem()) + 1;
+}
+
+void MenuController::IncrementSelection(int delta) {
+ MenuItemView* item = pending_state_.item;
+ DCHECK(item);
+ if (pending_state_.submenu_open && item->HasSubmenu() &&
+ item->GetSubmenu()->IsShowing()) {
+ // A menu is selected and open, but none of its children are selected,
+ // select the first menu item.
+ if (item->GetSubmenu()->GetMenuItemCount()) {
+ SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, false);
+ ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0));
+ return; // return so else case can fall through.
+ }
+ }
+ if (item->GetParentMenuItem()) {
+ MenuItemView* parent = item->GetParentMenuItem();
+ int parent_count = parent->GetSubmenu()->GetMenuItemCount();
+ if (parent_count > 1) {
+ for (int i = 0; i < parent_count; ++i) {
+ if (parent->GetSubmenu()->GetMenuItemAt(i) == item) {
+ int next_index = (i + delta + parent_count) % parent_count;
+ ScrollToVisible(parent->GetSubmenu()->GetMenuItemAt(next_index));
+ SetSelection(parent->GetSubmenu()->GetMenuItemAt(next_index), false,
+ false);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void MenuController::OpenSubmenuChangeSelectionIfCan() {
+ MenuItemView* item = pending_state_.item;
+ if (item->HasSubmenu()) {
+ if (item->GetSubmenu()->GetMenuItemCount() > 0) {
+ SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, true);
+ } else {
+ // No menu items, just show the sub-menu.
+ SetSelection(item, true, true);
+ }
+ }
+}
+
+void MenuController::CloseSubmenu() {
+ MenuItemView* item = state_.item;
+ DCHECK(item);
+ if (!item->GetParentMenuItem())
+ return;
+ if (item->HasSubmenu() && item->GetSubmenu()->IsShowing()) {
+ SetSelection(item, false, true);
+ } else if (item->GetParentMenuItem()->GetParentMenuItem()) {
+ SetSelection(item->GetParentMenuItem(), false, true);
+ }
+}
+
+bool MenuController::IsMenuWindow(MenuItemView* item, HWND window) {
+ if (!item)
+ return false;
+ return ((item->HasSubmenu() && item->GetSubmenu()->IsShowing() &&
+ item->GetSubmenu()->GetWidget()->GetNativeView() == window) ||
+ IsMenuWindow(item->GetParentMenuItem(), window));
+}
+
+bool MenuController::SelectByChar(wchar_t character) {
+ wchar_t char_array[1] = { character };
+ wchar_t key = l10n_util::ToLower(char_array)[0];
+ MenuItemView* item = pending_state_.item;
+ if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing())
+ item = item->GetParentMenuItem();
+ DCHECK(item);
+ DCHECK(item->HasSubmenu());
+ SubmenuView* submenu = item->GetSubmenu();
+ DCHECK(submenu);
+ int menu_item_count = submenu->GetMenuItemCount();
+ if (!menu_item_count)
+ return false;
+ for (int i = 0; i < menu_item_count; ++i) {
+ MenuItemView* child = submenu->GetMenuItemAt(i);
+ if (child->GetMnemonic() == key && child->IsEnabled()) {
+ Accept(child, 0);
+ return true;
+ }
+ }
+
+ // No matching mnemonic, search through items that don't have mnemonic
+ // based on first character of the title.
+ int first_match = -1;
+ bool has_multiple = false;
+ int next_match = -1;
+ int index_of_item = -1;
+ for (int i = 0; i < menu_item_count; ++i) {
+ MenuItemView* child = submenu->GetMenuItemAt(i);
+ if (!child->GetMnemonic() && child->IsEnabled()) {
+ std::wstring lower_title = l10n_util::ToLower(child->GetTitle());
+ if (child == pending_state_.item)
+ index_of_item = i;
+ if (lower_title.length() && lower_title[0] == key) {
+ if (first_match == -1)
+ first_match = i;
+ else
+ has_multiple = true;
+ if (next_match == -1 && index_of_item != -1 && i > index_of_item)
+ next_match = i;
+ }
+ }
+ }
+ if (first_match != -1) {
+ if (!has_multiple) {
+ if (submenu->GetMenuItemAt(first_match)->HasSubmenu()) {
+ SetSelection(submenu->GetMenuItemAt(first_match), true, false);
+ } else {
+ Accept(submenu->GetMenuItemAt(first_match), 0);
+ return true;
+ }
+ } else if (index_of_item == -1 || next_match == -1) {
+ SetSelection(submenu->GetMenuItemAt(first_match), false, false);
+ } else {
+ SetSelection(submenu->GetMenuItemAt(next_match), false, false);
+ }
+ }
+ return false;
+}
+
+void MenuController::RepostEvent(SubmenuView* source,
+ const MouseEvent& event) {
+ gfx::Point screen_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+ HWND window = WindowFromPoint(screen_loc.ToPOINT());
+ if (window) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "RepostEvent on press";
+#endif
+
+ // Release the capture.
+ SubmenuView* submenu = state_.item->GetRootMenuItem()->GetSubmenu();
+ submenu->ReleaseCapture();
+
+ if (submenu->native_view() && submenu->native_view() &&
+ GetWindowThreadProcessId(submenu->native_view(), NULL) !=
+ GetWindowThreadProcessId(window, NULL)) {
+ // Even though we have mouse capture, windows generates a mouse event
+ // if the other window is in a separate thread. Don't generate an event in
+ // this case else the target window can get double events leading to bad
+ // behavior.
+ return;
+ }
+
+ // Convert the coordinates to the target window.
+ RECT window_bounds;
+ GetWindowRect(window, &window_bounds);
+ int window_x = screen_loc.x() - window_bounds.left;
+ int window_y = screen_loc.y() - window_bounds.top;
+
+ // Determine whether the click was in the client area or not.
+ // NOTE: WM_NCHITTEST coordinates are relative to the screen.
+ LRESULT nc_hit_result = SendMessage(window, WM_NCHITTEST, 0,
+ MAKELPARAM(screen_loc.x(),
+ screen_loc.y()));
+ const bool in_client_area = (nc_hit_result == HTCLIENT);
+
+ // TODO(sky): this isn't right. The event to generate should correspond
+ // with the event we just got. MouseEvent only tells us what is down,
+ // which may differ. Need to add ability to get changed button from
+ // MouseEvent.
+ int event_type;
+ if (event.IsLeftMouseButton())
+ event_type = in_client_area ? WM_LBUTTONDOWN : WM_NCLBUTTONDOWN;
+ else if (event.IsMiddleMouseButton())
+ event_type = in_client_area ? WM_MBUTTONDOWN : WM_NCMBUTTONDOWN;
+ else if (event.IsRightMouseButton())
+ event_type = in_client_area ? WM_RBUTTONDOWN : WM_NCRBUTTONDOWN;
+ else
+ event_type = 0; // Unknown mouse press.
+
+ if (event_type) {
+ if (in_client_area) {
+ PostMessage(window, event_type, event.GetWindowsFlags(),
+ MAKELPARAM(window_x, window_y));
+ } else {
+ PostMessage(window, event_type, nc_hit_result,
+ MAKELPARAM(screen_loc.x(), screen_loc.y()));
+ }
+ }
+ }
+}
+
+void MenuController::SetDropMenuItem(
+ MenuItemView* new_target,
+ MenuDelegate::DropPosition new_position) {
+ if (new_target == drop_target_ && new_position == drop_position_)
+ return;
+
+ if (drop_target_) {
+ drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem(
+ NULL, MenuDelegate::DROP_NONE);
+ }
+ drop_target_ = new_target;
+ drop_position_ = new_position;
+ if (drop_target_) {
+ drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem(
+ drop_target_, drop_position_);
+ }
+}
+
+void MenuController::UpdateScrolling(const MenuPart& part) {
+ if (!part.is_scroll() && !scroll_task_.get())
+ return;
+
+ if (!scroll_task_.get())
+ scroll_task_.reset(new MenuScrollTask());
+ scroll_task_->Update(part);
+}
+
+void MenuController::StopScrolling() {
+ scroll_task_.reset(NULL);
+}
+
+} // namespace views