summaryrefslogtreecommitdiffstats
path: root/views/controls/menu
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
commit2362e4fe2905ab75d3230ebc3e307ae53e2b8362 (patch)
treee6d88357a2021811e0e354f618247217be8bb3da /views/controls/menu
parentdb23ac3e713dc17509b2b15d3ee634968da45715 (diff)
downloadchromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.zip
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.gz
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.bz2
Move src/chrome/views to src/views. RS=darin http://crbug.com/11387
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15604 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/controls/menu')
-rw-r--r--views/controls/menu/chrome_menu.cc2816
-rw-r--r--views/controls/menu/chrome_menu.h948
-rw-r--r--views/controls/menu/controller.h33
-rw-r--r--views/controls/menu/menu.cc626
-rw-r--r--views/controls/menu/menu.h355
-rw-r--r--views/controls/menu/view_menu_delegate.h34
6 files changed, 4812 insertions, 0 deletions
diff --git a/views/controls/menu/chrome_menu.cc b/views/controls/menu/chrome_menu.cc
new file mode 100644
index 0000000..a887fd5
--- /dev/null
+++ b/views/controls/menu/chrome_menu.cc
@@ -0,0 +1,2816 @@
+// 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/chrome_menu.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "app/os_exchange_data.h"
+#include "base/base_drag_source.h"
+#include "base/gfx/native_theme.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "base/timer.h"
+#include "base/win_util.h"
+// TODO(beng): (Cleanup) remove this browser dep.
+#include "chrome/browser/drag_utils.h"
+#include "chrome/common/gfx/color_utils.h"
+#include "grit/generated_resources.h"
+#include "skia/ext/skia_utils_win.h"
+#include "views/border.h"
+#include "views/view_constants.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+
+#undef min
+#undef max
+
+using base::Time;
+using base::TimeDelta;
+
+// Margins between the top of the item and the label.
+static const int kItemTopMargin = 3;
+
+// Margins between the bottom of the item and the label.
+static const int kItemBottomMargin = 4;
+
+// Margins used if the menu doesn't have icons.
+static const int kItemNoIconTopMargin = 1;
+static const int kItemNoIconBottomMargin = 3;
+
+// Margins between the left of the item and the icon.
+static const int kItemLeftMargin = 4;
+
+// Padding between the label and submenu arrow.
+static const int kLabelToArrowPadding = 10;
+
+// Padding between the arrow and the edge.
+static const int kArrowToEdgePadding = 5;
+
+// Padding between the icon and label.
+static const int kIconToLabelPadding = 8;
+
+// Padding between the gutter and label.
+static const int kGutterToLabel = 5;
+
+// Height of the scroll arrow.
+// This goes up to 4 with large fonts, but this is close enough for now.
+static const int kScrollArrowHeight = 3;
+
+// Size of the check. This comes from the OS.
+static int check_width;
+static int check_height;
+
+// Size of the submenu arrow. This comes from the OS.
+static int arrow_width;
+static int arrow_height;
+
+// Width of the gutter. Only used if render_gutter is true.
+static int gutter_width;
+
+// Margins between the right of the item and the label.
+static int item_right_margin;
+
+// X-coordinate of where the label starts.
+static int label_start;
+
+// Height of the separator.
+static int separator_height;
+
+// Padding around the edges of the submenu.
+static const int kSubmenuBorderSize = 3;
+
+// Amount to inset submenus.
+static const int kSubmenuHorizontalInset = 3;
+
+// 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;
+
+// Height of the drop indicator. This should be an event number.
+static const int kDropIndicatorHeight = 2;
+
+// Color of the drop indicator.
+static const SkColor kDropIndicatorColor = SK_ColorBLACK;
+
+// Whether or not the gutter should be rendered. The gutter is specific to
+// Vista.
+static bool render_gutter = false;
+
+// 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;
+
+// Period of the scroll timer (in milliseconds).
+static const int kScrollTimerMS = 30;
+
+// Preferred height of menu items. Reset every time a menu is run.
+static int pref_menu_height;
+
+// Are mnemonics shown? This is updated before the menus are shown.
+static bool show_mnemonics;
+
+using gfx::NativeTheme;
+
+namespace views {
+
+namespace {
+
+// Returns the font menus are to use.
+ChromeFont GetMenuFont() {
+ NONCLIENTMETRICS metrics;
+ win_util::GetNonClientMetrics(&metrics);
+
+ l10n_util::AdjustUIFont(&(metrics.lfMenuFont));
+ HFONT font = CreateFontIndirect(&metrics.lfMenuFont);
+ DLOG_ASSERT(font);
+ return ChromeFont::CreateFont(font);
+}
+
+// Calculates all sizes that we can from the OS.
+//
+// This is invoked prior to Running a menu.
+void UpdateMenuPartSizes(bool has_icons) {
+ HDC dc = GetDC(NULL);
+ RECT bounds = { 0, 0, 200, 200 };
+ SIZE check_size;
+ if (NativeTheme::instance()->GetThemePartSize(
+ NativeTheme::MENU, dc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, &bounds,
+ TS_TRUE, &check_size) == S_OK) {
+ check_width = check_size.cx;
+ check_height = check_size.cy;
+ } else {
+ check_width = GetSystemMetrics(SM_CXMENUCHECK);
+ check_height = GetSystemMetrics(SM_CYMENUCHECK);
+ }
+
+ SIZE arrow_size;
+ if (NativeTheme::instance()->GetThemePartSize(
+ NativeTheme::MENU, dc, MENU_POPUPSUBMENU, MSM_NORMAL, &bounds,
+ TS_TRUE, &arrow_size) == S_OK) {
+ arrow_width = arrow_size.cx;
+ arrow_height = arrow_size.cy;
+ } else {
+ // Sadly I didn't see a specify metrics for this.
+ arrow_width = GetSystemMetrics(SM_CXMENUCHECK);
+ arrow_height = GetSystemMetrics(SM_CYMENUCHECK);
+ }
+
+ SIZE gutter_size;
+ if (NativeTheme::instance()->GetThemePartSize(
+ NativeTheme::MENU, dc, MENU_POPUPGUTTER, MSM_NORMAL, &bounds,
+ TS_TRUE, &gutter_size) == S_OK) {
+ gutter_width = gutter_size.cx;
+ render_gutter = true;
+ } else {
+ gutter_width = 0;
+ render_gutter = false;
+ }
+
+ SIZE separator_size;
+ if (NativeTheme::instance()->GetThemePartSize(
+ NativeTheme::MENU, dc, MENU_POPUPSEPARATOR, MSM_NORMAL, &bounds,
+ TS_TRUE, &separator_size) == S_OK) {
+ separator_height = separator_size.cy;
+ } else {
+ separator_height = GetSystemMetrics(SM_CYMENU) / 2;
+ }
+
+ item_right_margin = kLabelToArrowPadding + arrow_width + kArrowToEdgePadding;
+
+ if (has_icons) {
+ label_start = kItemLeftMargin + check_width + kIconToLabelPadding;
+ } else {
+ // If there are no icons don't pad by the icon to label padding. This
+ // makes us look close to system menus.
+ label_start = kItemLeftMargin + check_width;
+ }
+ if (render_gutter)
+ label_start += gutter_width + kGutterToLabel;
+
+ ReleaseDC(NULL, dc);
+
+ MenuItemView menu_item(NULL);
+ menu_item.SetTitle(L"blah"); // Text doesn't matter here.
+ pref_menu_height = menu_item.GetPreferredSize().height();
+}
+
+// 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 MenuScrollTask {
+ public:
+ MenuScrollTask() : submenu_(NULL) {
+ pixels_per_second_ = 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);
+};
+
+// MenuScrollButton ------------------------------------------------------------
+
+// MenuScrollButton is used for the scroll buttons when not all menu items fit
+// on screen. MenuScrollButton forwards appropriate events to the
+// MenuController.
+
+class MenuScrollButton : public View {
+ public:
+ explicit MenuScrollButton(SubmenuView* host, bool is_up)
+ : host_(host),
+ is_up_(is_up),
+ // Make our height the same as that of other MenuItemViews.
+ pref_height_(pref_menu_height) {
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(kScrollArrowHeight * 2 - 1, pref_height_);
+ }
+
+ virtual bool CanDrop(const OSExchangeData& data) {
+ DCHECK(host_->GetMenuItem()->GetMenuController());
+ return true; // Always return true so that drop events are targeted to us.
+ }
+
+ virtual void OnDragEntered(const DropTargetEvent& event) {
+ DCHECK(host_->GetMenuItem()->GetMenuController());
+ host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton(
+ host_, is_up_);
+ }
+
+ virtual int OnDragUpdated(const DropTargetEvent& event) {
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ virtual void OnDragExited() {
+ DCHECK(host_->GetMenuItem()->GetMenuController());
+ host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
+ }
+
+ virtual int OnPerformDrop(const DropTargetEvent& event) {
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ virtual void Paint(ChromeCanvas* canvas) {
+ HDC dc = canvas->beginPlatformPaint();
+
+ // The background.
+ RECT item_bounds = { 0, 0, width(), height() };
+ NativeTheme::instance()->PaintMenuItemBackground(
+ NativeTheme::MENU, dc, MENU_POPUPITEM, MPI_NORMAL, false,
+ &item_bounds);
+
+ // Then the arrow.
+ int x = width() / 2;
+ int y = (height() - kScrollArrowHeight) / 2;
+ int delta_y = 1;
+ if (!is_up_) {
+ delta_y = -1;
+ y += kScrollArrowHeight;
+ }
+ SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT);
+ for (int i = 0; i < kScrollArrowHeight; ++i, --x, y += delta_y)
+ canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1);
+
+ canvas->endPlatformPaint();
+ }
+
+ private:
+ // SubmenuView we were created for.
+ SubmenuView* host_;
+
+ // Direction of the button.
+ bool is_up_;
+
+ // Preferred height.
+ int pref_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollButton);
+};
+
+// MenuScrollView --------------------------------------------------------------
+
+// MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so
+// that ScrollRectToVisible works.
+//
+// NOTE: It is possible to use ScrollView directly (after making it deal with
+// null scrollbars), but clicking on a child of ScrollView forces the window to
+// become active, which we don't want. As we really only need a fraction of
+// what ScrollView does, we use a one off variant.
+
+class MenuScrollView : public View {
+ public:
+ explicit MenuScrollView(View* child) {
+ AddChildView(child);
+ }
+
+ virtual void ScrollRectToVisible(int x, int y, int width, int height) {
+ // NOTE: this assumes we only want to scroll in the y direction.
+
+ View* child = GetContents();
+ // Convert y to view's coordinates.
+ y -= child->y();
+ gfx::Size pref = child->GetPreferredSize();
+ // Constrain y to make sure we don't show past the bottom of the view.
+ y = std::max(0, std::min(pref.height() - this->height(), y));
+ child->SetY(-y);
+ }
+
+ // Returns the contents, which is the SubmenuView.
+ View* GetContents() {
+ return GetChildViewAt(0);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollView);
+};
+
+// MenuScrollViewContainer -----------------------------------------------------
+
+// MenuScrollViewContainer contains the SubmenuView (through a MenuScrollView)
+// and two scroll buttons. The scroll buttons are only visible and enabled if
+// the preferred height of the SubmenuView is bigger than our bounds.
+class MenuScrollViewContainer : public View {
+ public:
+ explicit MenuScrollViewContainer(SubmenuView* content_view) {
+ scroll_up_button_ = new MenuScrollButton(content_view, true);
+ scroll_down_button_ = new MenuScrollButton(content_view, false);
+ AddChildView(scroll_up_button_);
+ AddChildView(scroll_down_button_);
+
+ scroll_view_ = new MenuScrollView(content_view);
+ AddChildView(scroll_view_);
+
+ set_border(Border::CreateEmptyBorder(
+ kSubmenuBorderSize, kSubmenuBorderSize,
+ kSubmenuBorderSize, kSubmenuBorderSize));
+ }
+
+ virtual void Paint(ChromeCanvas* canvas) {
+ HDC dc = canvas->beginPlatformPaint();
+ CRect bounds(0, 0, width(), height());
+ NativeTheme::instance()->PaintMenuBackground(
+ NativeTheme::MENU, dc, MENU_POPUPBACKGROUND, 0, &bounds);
+ canvas->endPlatformPaint();
+ }
+
+ View* scroll_down_button() { return scroll_down_button_; }
+
+ View* scroll_up_button() { return scroll_up_button_; }
+
+ virtual void Layout() {
+ gfx::Insets insets = GetInsets();
+ int x = insets.left();
+ int y = insets.top();
+ int width = View::width() - insets.width();
+ int content_height = height() - insets.height();
+ if (!scroll_up_button_->IsVisible()) {
+ scroll_view_->SetBounds(x, y, width, content_height);
+ scroll_view_->Layout();
+ return;
+ }
+
+ gfx::Size pref = scroll_up_button_->GetPreferredSize();
+ scroll_up_button_->SetBounds(x, y, width, pref.height());
+ content_height -= pref.height();
+
+ const int scroll_view_y = y + pref.height();
+
+ pref = scroll_down_button_->GetPreferredSize();
+ scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(),
+ width, pref.height());
+ content_height -= pref.height();
+
+ scroll_view_->SetBounds(x, scroll_view_y, width, content_height);
+ scroll_view_->Layout();
+ }
+
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize();
+ scroll_up_button_->SetVisible(content_pref.height() > height());
+ scroll_down_button_->SetVisible(content_pref.height() > height());
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize();
+ gfx::Insets insets = GetInsets();
+ prefsize.Enlarge(insets.width(), insets.height());
+ return prefsize;
+ }
+
+ private:
+ // The scroll buttons.
+ View* scroll_up_button_;
+ View* scroll_down_button_;
+
+ // The scroll view.
+ MenuScrollView* scroll_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollViewContainer);
+};
+
+// MenuSeparator ---------------------------------------------------------------
+
+// Renders a separator.
+
+class MenuSeparator : public View {
+ public:
+ MenuSeparator() {
+ }
+
+ void Paint(ChromeCanvas* canvas) {
+ // The gutter is rendered before the background.
+ int start_x = 0;
+ int start_y = height() / 3;
+ HDC dc = canvas->beginPlatformPaint();
+ if (render_gutter) {
+ // If render_gutter is true, we're on Vista and need to render the
+ // gutter, then indent the separator from the gutter.
+ RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0,
+ height() };
+ gutter_bounds.right = gutter_bounds.left + gutter_width;
+ NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL,
+ &gutter_bounds);
+ start_x = gutter_bounds.left + gutter_width;
+ start_y = 0;
+ }
+ RECT separator_bounds = { start_x, start_y, width(), height() };
+ NativeTheme::instance()->PaintMenuSeparator(dc, MENU_POPUPSEPARATOR,
+ MPI_NORMAL, &separator_bounds);
+ canvas->endPlatformPaint();
+ }
+
+ gfx::Size GetPreferredSize() {
+ return gfx::Size(10, // Just in case we're the only item in a menu.
+ separator_height);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuSeparator);
+};
+
+// MenuHostRootView ----------------------------------------------------------
+
+// MenuHostRootView is the RootView of the window showing the menu.
+// SubmenuView's scroll view is added as a child of MenuHostRootView.
+// MenuHostRootView forwards relevant events to the MenuController.
+//
+// As all the menu items are owned by the root menu item, care must be taken
+// such that when MenuHostRootView is deleted it doesn't delete the menu items.
+
+class MenuHostRootView : public RootView {
+ public:
+ explicit MenuHostRootView(Widget* widget,
+ SubmenuView* submenu)
+ : RootView(widget),
+ submenu_(submenu),
+ forward_drag_to_menu_controller_(true),
+ suspend_events_(false) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << " new MenuHostRootView " << this;
+#endif
+ }
+
+ virtual bool OnMousePressed(const MouseEvent& event) {
+ if (suspend_events_)
+ return true;
+
+ forward_drag_to_menu_controller_ =
+ ((event.x() < 0 || event.y() < 0 || event.x() >= width() ||
+ event.y() >= height()) ||
+ !RootView::OnMousePressed(event));
+ if (forward_drag_to_menu_controller_)
+ GetMenuController()->OnMousePressed(submenu_, event);
+ return true;
+ }
+
+ virtual bool OnMouseDragged(const MouseEvent& event) {
+ if (suspend_events_)
+ return true;
+
+ if (forward_drag_to_menu_controller_) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << " MenuHostRootView::OnMouseDragged source=" << submenu_;
+#endif
+ GetMenuController()->OnMouseDragged(submenu_, event);
+ return true;
+ }
+ return RootView::OnMouseDragged(event);
+ }
+
+ virtual void OnMouseReleased(const MouseEvent& event, bool canceled) {
+ if (suspend_events_)
+ return;
+
+ RootView::OnMouseReleased(event, canceled);
+ if (forward_drag_to_menu_controller_) {
+ forward_drag_to_menu_controller_ = false;
+ if (canceled) {
+ GetMenuController()->Cancel(true);
+ } else {
+ GetMenuController()->OnMouseReleased(submenu_, event);
+ }
+ }
+ }
+
+ virtual void OnMouseMoved(const MouseEvent& event) {
+ if (suspend_events_)
+ return;
+
+ RootView::OnMouseMoved(event);
+ GetMenuController()->OnMouseMoved(submenu_, event);
+ }
+
+ virtual void ProcessOnMouseExited() {
+ if (suspend_events_)
+ return;
+
+ RootView::ProcessOnMouseExited();
+ }
+
+ virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e) {
+ // RootView::ProcessMouseWheelEvent forwards to the focused view. We don't
+ // have a focused view, so we need to override this then forward to
+ // the menu.
+ return submenu_->OnMouseWheel(e);
+ }
+
+ void SuspendEvents() {
+ suspend_events_ = true;
+ }
+
+ private:
+ MenuController* GetMenuController() {
+ return submenu_->GetMenuItem()->GetMenuController();
+ }
+
+ // The SubmenuView we contain.
+ SubmenuView* submenu_;
+
+ // Whether mouse dragged/released should be forwarded to the MenuController.
+ bool forward_drag_to_menu_controller_;
+
+ // Whether events are suspended. If true, no events are forwarded to the
+ // MenuController.
+ bool suspend_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHostRootView);
+};
+
+// MenuHost ------------------------------------------------------------------
+
+// MenuHost is the window responsible for showing a single menu.
+//
+// Similar to MenuHostRootView, care must be taken such that when MenuHost is
+// deleted, it doesn't delete the menu items. MenuHost is closed via a
+// DelayedClosed, which avoids timing issues with deleting the window while
+// capture or events are directed at it.
+
+class MenuHost : public WidgetWin {
+ public:
+ explicit MenuHost(SubmenuView* submenu)
+ : closed_(false),
+ submenu_(submenu),
+ owns_capture_(false) {
+ set_window_style(WS_POPUP);
+ set_initial_class_style(
+ (win_util::GetWinVersion() < win_util::WINVERSION_XP) ?
+ 0 : CS_DROPSHADOW);
+ is_mouse_down_ =
+ ((GetKeyState(VK_LBUTTON) & 0x80) ||
+ (GetKeyState(VK_RBUTTON) & 0x80) ||
+ (GetKeyState(VK_MBUTTON) & 0x80) ||
+ (GetKeyState(VK_XBUTTON1) & 0x80) ||
+ (GetKeyState(VK_XBUTTON2) & 0x80));
+ // Mouse clicks shouldn't give us focus.
+ set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE);
+ }
+
+ void Init(HWND parent,
+ const gfx::Rect& bounds,
+ View* contents_view,
+ bool do_capture) {
+ WidgetWin::Init(parent, bounds, true);
+ SetContentsView(contents_view);
+ // We don't want to take focus away from the hosting window.
+ ShowWindow(SW_SHOWNA);
+ owns_capture_ = do_capture;
+ if (do_capture) {
+ SetCapture();
+ has_capture_ = true;
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "Doing capture";
+#endif
+ }
+ }
+
+ virtual void Hide() {
+ if (closed_) {
+ // We're already closed, nothing to do.
+ // This is invoked twice if the first time just hid us, and the second
+ // time deleted Closed (deleted) us.
+ return;
+ }
+ // The menus are freed separately, and possibly before the window is closed,
+ // remove them so that View doesn't try to access deleted objects.
+ static_cast<MenuHostRootView*>(GetRootView())->SuspendEvents();
+ GetRootView()->RemoveAllChildViews(false);
+ closed_ = true;
+ ReleaseCapture();
+ WidgetWin::Hide();
+ }
+
+ virtual void HideWindow() {
+ // Make sure we release capture before hiding.
+ ReleaseCapture();
+ WidgetWin::Hide();
+ }
+
+ virtual void OnCaptureChanged(HWND hwnd) {
+ WidgetWin::OnCaptureChanged(hwnd);
+ owns_capture_ = false;
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "Capture changed";
+#endif
+ }
+
+ void ReleaseCapture() {
+ if (owns_capture_) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "released capture";
+#endif
+ owns_capture_ = false;
+ ::ReleaseCapture();
+ }
+ }
+
+ protected:
+ // Overriden to create MenuHostRootView.
+ virtual RootView* CreateRootView() {
+ return new MenuHostRootView(this, submenu_);
+ }
+
+ virtual void OnCancelMode() {
+ if (!closed_) {
+#ifdef DEBUG_MENU
+ DLOG(INFO) << "OnCanceMode, closing menu";
+#endif
+ submenu_->GetMenuItem()->GetMenuController()->Cancel(true);
+ }
+ }
+
+ // Overriden to return false, we do NOT want to release capture on mouse
+ // release.
+ virtual bool ReleaseCaptureOnMouseReleased() {
+ return false;
+ }
+
+ private:
+ // If true, we've been closed.
+ bool closed_;
+
+ // If true, we own the capture and need to release it.
+ bool owns_capture_;
+
+ // The view we contain.
+ SubmenuView* submenu_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHost);
+};
+
+// EmptyMenuMenuItem ---------------------------------------------------------
+
+// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
+// is itself a MenuItemView, but it uses a different ID so that it isn't
+// identified as a MenuItemView.
+
+class EmptyMenuMenuItem : public MenuItemView {
+ public:
+ // ID used for EmptyMenuMenuItem.
+ static const int kEmptyMenuItemViewID;
+
+ explicit EmptyMenuMenuItem(MenuItemView* parent) :
+ MenuItemView(parent, 0, NORMAL) {
+ SetTitle(l10n_util::GetString(IDS_MENU_EMPTY_SUBMENU));
+ // Set this so that we're not identified as a normal menu item.
+ SetID(kEmptyMenuItemViewID);
+ SetEnabled(false);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem);
+};
+
+// static
+const int EmptyMenuMenuItem::kEmptyMenuItemViewID =
+ MenuItemView::kMenuItemViewID + 1;
+
+} // namespace
+
+// SubmenuView ---------------------------------------------------------------
+
+SubmenuView::SubmenuView(MenuItemView* parent)
+ : parent_menu_item_(parent),
+ host_(NULL),
+ drop_item_(NULL),
+ drop_position_(MenuDelegate::DROP_NONE),
+ scroll_view_container_(NULL) {
+ DCHECK(parent);
+ // We'll delete ourselves, otherwise the ScrollView would delete us on close.
+ SetParentOwned(false);
+}
+
+SubmenuView::~SubmenuView() {
+ // The menu may not have been closed yet (it will be hidden, but not
+ // necessarily closed).
+ Close();
+
+ delete scroll_view_container_;
+}
+
+int SubmenuView::GetMenuItemCount() {
+ int count = 0;
+ for (int i = 0; i < GetChildViewCount(); ++i) {
+ if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID)
+ count++;
+ }
+ return count;
+}
+
+MenuItemView* SubmenuView::GetMenuItemAt(int index) {
+ for (int i = 0, count = 0; i < GetChildViewCount(); ++i) {
+ if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID &&
+ count++ == index) {
+ return static_cast<MenuItemView*>(GetChildViewAt(i));
+ }
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+void SubmenuView::Layout() {
+ // We're in a ScrollView, and need to set our width/height ourselves.
+ View* parent = GetParent();
+ if (!parent)
+ return;
+ SetBounds(x(), y(), parent->width(), GetPreferredSize().height());
+
+ gfx::Insets insets = GetInsets();
+ int x = insets.left();
+ int y = insets.top();
+ int menu_item_width = width() - insets.width();
+ for (int i = 0; i < GetChildViewCount(); ++i) {
+ View* child = GetChildViewAt(i);
+ gfx::Size child_pref_size = child->GetPreferredSize();
+ child->SetBounds(x, y, menu_item_width, child_pref_size.height());
+ y += child_pref_size.height();
+ }
+}
+
+gfx::Size SubmenuView::GetPreferredSize() {
+ if (GetChildViewCount() == 0)
+ return gfx::Size();
+
+ int max_width = 0;
+ int height = 0;
+ for (int i = 0; i < GetChildViewCount(); ++i) {
+ View* child = GetChildViewAt(i);
+ gfx::Size child_pref_size = child->GetPreferredSize();
+ max_width = std::max(max_width, child_pref_size.width());
+ height += child_pref_size.height();
+ }
+ gfx::Insets insets = GetInsets();
+ return gfx::Size(max_width + insets.width(), height + insets.height());
+}
+
+void SubmenuView::DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current) {
+ SchedulePaint();
+}
+
+void SubmenuView::PaintChildren(ChromeCanvas* canvas) {
+ View::PaintChildren(canvas);
+
+ if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON)
+ PaintDropIndicator(canvas, drop_item_, drop_position_);
+}
+
+bool SubmenuView::CanDrop(const OSExchangeData& data) {
+ DCHECK(GetMenuItem()->GetMenuController());
+ return GetMenuItem()->GetMenuController()->CanDrop(this, data);
+}
+
+void SubmenuView::OnDragEntered(const DropTargetEvent& event) {
+ DCHECK(GetMenuItem()->GetMenuController());
+ GetMenuItem()->GetMenuController()->OnDragEntered(this, event);
+}
+
+int SubmenuView::OnDragUpdated(const DropTargetEvent& event) {
+ DCHECK(GetMenuItem()->GetMenuController());
+ return GetMenuItem()->GetMenuController()->OnDragUpdated(this, event);
+}
+
+void SubmenuView::OnDragExited() {
+ DCHECK(GetMenuItem()->GetMenuController());
+ GetMenuItem()->GetMenuController()->OnDragExited(this);
+}
+
+int SubmenuView::OnPerformDrop(const DropTargetEvent& event) {
+ DCHECK(GetMenuItem()->GetMenuController());
+ return GetMenuItem()->GetMenuController()->OnPerformDrop(this, event);
+}
+
+bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) {
+ gfx::Rect vis_bounds = GetVisibleBounds();
+ int menu_item_count = GetMenuItemCount();
+ if (vis_bounds.height() == height() || !menu_item_count) {
+ // All menu items are visible, nothing to scroll.
+ return true;
+ }
+
+ // Find the index of the first menu item whose y-coordinate is >= visible
+ // y-coordinate.
+ int first_vis_index = -1;
+ for (int i = 0; i < menu_item_count; ++i) {
+ MenuItemView* menu_item = GetMenuItemAt(i);
+ if (menu_item->y() == vis_bounds.y()) {
+ first_vis_index = i;
+ break;
+ } else if (menu_item->y() > vis_bounds.y()) {
+ first_vis_index = std::max(0, i - 1);
+ break;
+ }
+ }
+ if (first_vis_index == -1)
+ return true;
+
+ // If the first item isn't entirely visible, make it visible, otherwise make
+ // the next/previous one entirely visible.
+ int delta = abs(e.GetOffset() / WHEEL_DELTA);
+ bool scroll_up = (e.GetOffset() > 0);
+ while (delta-- > 0) {
+ int scroll_amount = 0;
+ if (scroll_up) {
+ if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) {
+ if (first_vis_index != 0) {
+ scroll_amount = GetMenuItemAt(first_vis_index - 1)->y() -
+ vis_bounds.y();
+ first_vis_index--;
+ } else {
+ break;
+ }
+ } else {
+ scroll_amount = GetMenuItemAt(first_vis_index)->y() - vis_bounds.y();
+ }
+ } else {
+ if (first_vis_index + 1 == GetMenuItemCount())
+ break;
+ scroll_amount = GetMenuItemAt(first_vis_index + 1)->y() -
+ vis_bounds.y();
+ if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y())
+ first_vis_index++;
+ }
+ ScrollRectToVisible(0, vis_bounds.y() + scroll_amount, vis_bounds.width(),
+ vis_bounds.height());
+ vis_bounds = GetVisibleBounds();
+ }
+
+ return true;
+}
+
+bool SubmenuView::IsShowing() {
+ return host_ && host_->IsVisible();
+}
+
+void SubmenuView::ShowAt(HWND parent,
+ const gfx::Rect& bounds,
+ bool do_capture) {
+ if (host_) {
+ host_->ShowWindow(SW_SHOWNA);
+ return;
+ }
+
+ host_ = new MenuHost(this);
+ // Force construction of the scroll view container.
+ GetScrollViewContainer();
+ // Make sure the first row is visible.
+ ScrollRectToVisible(0, 0, 1, 1);
+ host_->Init(parent, bounds, scroll_view_container_, do_capture);
+}
+
+void SubmenuView::Close() {
+ if (host_) {
+ host_->Close();
+ host_ = NULL;
+ }
+}
+
+void SubmenuView::Hide() {
+ if (host_)
+ host_->HideWindow();
+}
+
+void SubmenuView::ReleaseCapture() {
+ host_->ReleaseCapture();
+}
+
+void SubmenuView::SetDropMenuItem(MenuItemView* item,
+ MenuDelegate::DropPosition position) {
+ if (drop_item_ == item && drop_position_ == position)
+ return;
+ SchedulePaintForDropIndicator(drop_item_, drop_position_);
+ drop_item_ = item;
+ drop_position_ = position;
+ SchedulePaintForDropIndicator(drop_item_, drop_position_);
+}
+
+bool SubmenuView::GetShowSelection(MenuItemView* item) {
+ if (drop_item_ == NULL)
+ return true;
+ // Something is being dropped on one of this menus items. Show the
+ // selection if the drop is on the passed in item and the drop position is
+ // ON.
+ return (drop_item_ == item && drop_position_ == MenuDelegate::DROP_ON);
+}
+
+MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() {
+ if (!scroll_view_container_) {
+ scroll_view_container_ = new MenuScrollViewContainer(this);
+ // Otherwise MenuHost would delete us.
+ scroll_view_container_->SetParentOwned(false);
+ }
+ return scroll_view_container_;
+}
+
+void SubmenuView::PaintDropIndicator(ChromeCanvas* canvas,
+ MenuItemView* item,
+ MenuDelegate::DropPosition position) {
+ if (position == MenuDelegate::DROP_NONE)
+ return;
+
+ gfx::Rect bounds = CalculateDropIndicatorBounds(item, position);
+ canvas->FillRectInt(kDropIndicatorColor, bounds.x(), bounds.y(),
+ bounds.width(), bounds.height());
+}
+
+void SubmenuView::SchedulePaintForDropIndicator(
+ MenuItemView* item,
+ MenuDelegate::DropPosition position) {
+ if (item == NULL)
+ return;
+
+ if (position == MenuDelegate::DROP_ON) {
+ item->SchedulePaint();
+ } else if (position != MenuDelegate::DROP_NONE) {
+ gfx::Rect bounds = CalculateDropIndicatorBounds(item, position);
+ SchedulePaint(bounds.x(), bounds.y(), bounds.width(), bounds.height());
+ }
+}
+
+gfx::Rect SubmenuView::CalculateDropIndicatorBounds(
+ MenuItemView* item,
+ MenuDelegate::DropPosition position) {
+ DCHECK(position != MenuDelegate::DROP_NONE);
+ gfx::Rect item_bounds = item->bounds();
+ switch (position) {
+ case MenuDelegate::DROP_BEFORE:
+ item_bounds.Offset(0, -kDropIndicatorHeight / 2);
+ item_bounds.set_height(kDropIndicatorHeight);
+ return item_bounds;
+
+ case MenuDelegate::DROP_AFTER:
+ item_bounds.Offset(0, item_bounds.height() - kDropIndicatorHeight / 2);
+ item_bounds.set_height(kDropIndicatorHeight);
+ return item_bounds;
+
+ default:
+ // Don't render anything for on.
+ return gfx::Rect();
+ }
+}
+
+// MenuItemView ---------------------------------------------------------------
+
+// static
+const int MenuItemView::kMenuItemViewID = 1001;
+
+// static
+bool MenuItemView::allow_task_nesting_during_run_ = false;
+
+MenuItemView::MenuItemView(MenuDelegate* delegate) {
+ // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes supplies a
+ // NULL delegate.
+ Init(NULL, 0, SUBMENU, delegate);
+}
+
+MenuItemView::~MenuItemView() {
+ if (controller_) {
+ // We're currently showing.
+
+ // We can't delete ourselves while we're blocking.
+ DCHECK(!controller_->IsBlockingRun());
+
+ // Invoking Cancel is going to call us back and notify the delegate.
+ // Notifying the delegate from the destructor can be problematic. To avoid
+ // this the delegate is set to NULL.
+ delegate_ = NULL;
+
+ controller_->Cancel(true);
+ }
+ delete submenu_;
+}
+
+void MenuItemView::RunMenuAt(HWND parent,
+ const gfx::Rect& bounds,
+ AnchorPosition anchor,
+ bool has_mnemonics) {
+ PrepareForRun(has_mnemonics);
+
+ int mouse_event_flags;
+
+ MenuController* controller = MenuController::GetActiveInstance();
+ if (controller && !controller->IsBlockingRun()) {
+ // A menu is already showing, but it isn't a blocking menu. Cancel it.
+ // We can get here during drag and drop if the user right clicks on the
+ // menu quickly after the drop.
+ controller->Cancel(true);
+ controller = NULL;
+ }
+ bool owns_controller = false;
+ if (!controller) {
+ // No menus are showing, show one.
+ controller = new MenuController(true);
+ MenuController::SetActiveInstance(controller);
+ owns_controller = true;
+ } else {
+ // A menu is already showing, use the same controller.
+
+ // Don't support blocking from within non-blocking.
+ DCHECK(controller->IsBlockingRun());
+ }
+
+ controller_ = controller;
+
+ // Run the loop.
+ MenuItemView* result =
+ controller->Run(parent, this, bounds, anchor, &mouse_event_flags);
+
+ RemoveEmptyMenus();
+
+ controller_ = NULL;
+
+ if (owns_controller) {
+ // We created the controller and need to delete it.
+ if (MenuController::GetActiveInstance() == controller)
+ MenuController::SetActiveInstance(NULL);
+ delete controller;
+ }
+ // Make sure all the windows we created to show the menus have been
+ // destroyed.
+ DestroyAllMenuHosts();
+ if (result && delegate_)
+ delegate_->ExecuteCommand(result->GetCommand(), mouse_event_flags);
+}
+
+void MenuItemView::RunMenuForDropAt(HWND parent,
+ const gfx::Rect& bounds,
+ AnchorPosition anchor) {
+ PrepareForRun(false);
+
+ // If there is a menu, hide it so that only one menu is shown during dnd.
+ MenuController* current_controller = MenuController::GetActiveInstance();
+ if (current_controller) {
+ current_controller->Cancel(true);
+ }
+
+ // Always create a new controller for non-blocking.
+ controller_ = new MenuController(false);
+
+ // Set the instance, that way it can be canceled by another menu.
+ MenuController::SetActiveInstance(controller_);
+
+ controller_->Run(parent, this, bounds, anchor, NULL);
+}
+
+void MenuItemView::Cancel() {
+ if (controller_ && !canceled_) {
+ canceled_ = true;
+ controller_->Cancel(true);
+ }
+}
+
+SubmenuView* MenuItemView::CreateSubmenu() {
+ if (!submenu_)
+ submenu_ = new SubmenuView(this);
+ return submenu_;
+}
+
+void MenuItemView::SetSelected(bool selected) {
+ selected_ = selected;
+ SchedulePaint();
+}
+
+void MenuItemView::SetIcon(const SkBitmap& icon, int item_id) {
+ MenuItemView* item = GetDescendantByID(item_id);
+ DCHECK(item);
+ item->SetIcon(icon);
+}
+
+void MenuItemView::SetIcon(const SkBitmap& icon) {
+ icon_ = icon;
+ SchedulePaint();
+}
+
+void MenuItemView::Paint(ChromeCanvas* canvas) {
+ Paint(canvas, false);
+}
+
+gfx::Size MenuItemView::GetPreferredSize() {
+ ChromeFont& font = GetRootMenuItem()->font_;
+ return gfx::Size(
+ font.GetStringWidth(title_) + label_start + item_right_margin,
+ font.height() + GetBottomMargin() + GetTopMargin());
+}
+
+MenuController* MenuItemView::GetMenuController() {
+ return GetRootMenuItem()->controller_;
+}
+
+MenuDelegate* MenuItemView::GetDelegate() {
+ return GetRootMenuItem()->delegate_;
+}
+
+MenuItemView* MenuItemView::GetRootMenuItem() {
+ MenuItemView* item = this;
+ while (item) {
+ MenuItemView* parent = item->GetParentMenuItem();
+ if (!parent)
+ return item;
+ item = parent;
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+wchar_t MenuItemView::GetMnemonic() {
+ if (!has_mnemonics_)
+ return 0;
+
+ const std::wstring& title = GetTitle();
+ size_t index = 0;
+ do {
+ index = title.find('&', index);
+ if (index != std::wstring::npos) {
+ if (index + 1 != title.size() && title[index + 1] != '&')
+ return title[index + 1];
+ index++;
+ }
+ } while (index != std::wstring::npos);
+ return 0;
+}
+
+MenuItemView::MenuItemView(MenuItemView* parent,
+ int command,
+ MenuItemView::Type type) {
+ Init(parent, command, type, NULL);
+}
+
+void MenuItemView::Init(MenuItemView* parent,
+ int command,
+ MenuItemView::Type type,
+ MenuDelegate* delegate) {
+ delegate_ = delegate;
+ controller_ = NULL;
+ canceled_ = false;
+ parent_menu_item_ = parent;
+ type_ = type;
+ selected_ = false;
+ command_ = command;
+ submenu_ = NULL;
+ // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
+ SetID(kMenuItemViewID);
+ has_icons_ = false;
+
+ MenuDelegate* root_delegate = GetDelegate();
+ if (root_delegate)
+ SetEnabled(root_delegate->IsCommandEnabled(command));
+}
+
+MenuItemView* MenuItemView::AppendMenuItemInternal(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon,
+ Type type) {
+ if (!submenu_)
+ CreateSubmenu();
+ if (type == SEPARATOR) {
+ submenu_->AddChildView(new MenuSeparator());
+ return NULL;
+ }
+ MenuItemView* item = new MenuItemView(this, item_id, type);
+ if (label.empty() && GetDelegate())
+ item->SetTitle(GetDelegate()->GetLabel(item_id));
+ else
+ item->SetTitle(label);
+ item->SetIcon(icon);
+ if (type == SUBMENU)
+ item->CreateSubmenu();
+ submenu_->AddChildView(item);
+ return item;
+}
+
+MenuItemView* MenuItemView::GetDescendantByID(int id) {
+ if (GetCommand() == id)
+ return this;
+ if (!HasSubmenu())
+ return NULL;
+ for (int i = 0; i < GetSubmenu()->GetChildViewCount(); ++i) {
+ View* child = GetSubmenu()->GetChildViewAt(i);
+ if (child->GetID() == MenuItemView::kMenuItemViewID) {
+ MenuItemView* result = static_cast<MenuItemView*>(child)->
+ GetDescendantByID(id);
+ if (result)
+ return result;
+ }
+ }
+ return NULL;
+}
+
+void MenuItemView::DropMenuClosed(bool notify_delegate) {
+ DCHECK(controller_);
+ DCHECK(!controller_->IsBlockingRun());
+ if (MenuController::GetActiveInstance() == controller_)
+ MenuController::SetActiveInstance(NULL);
+ delete controller_;
+ controller_ = NULL;
+
+ RemoveEmptyMenus();
+
+ if (notify_delegate && delegate_) {
+ // Our delegate is null when invoked from the destructor.
+ delegate_->DropMenuClosed(this);
+ }
+ // WARNING: its possible the delegate deleted us at this point.
+}
+
+void MenuItemView::PrepareForRun(bool has_mnemonics) {
+ // Currently we only support showing the root.
+ DCHECK(!parent_menu_item_);
+
+ // Don't invoke run from within run on the same menu.
+ DCHECK(!controller_);
+
+ // Force us to have a submenu.
+ CreateSubmenu();
+
+ canceled_ = false;
+
+ has_mnemonics_ = has_mnemonics;
+
+ AddEmptyMenus();
+
+ if (!MenuController::GetActiveInstance()) {
+ // Only update the menu size if there are no menus showing, otherwise
+ // things may shift around.
+ UpdateMenuPartSizes(has_icons_);
+ }
+
+ font_ = GetMenuFont();
+
+ BOOL show_cues;
+ show_mnemonics =
+ (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) &&
+ show_cues == TRUE);
+}
+
+int MenuItemView::GetDrawStringFlags() {
+ int flags = 0;
+ if (UILayoutIsRightToLeft())
+ flags |= ChromeCanvas::TEXT_ALIGN_RIGHT;
+ else
+ flags |= ChromeCanvas::TEXT_ALIGN_LEFT;
+
+ if (has_mnemonics_) {
+ if (show_mnemonics)
+ flags |= ChromeCanvas::SHOW_PREFIX;
+ else
+ flags |= ChromeCanvas::HIDE_PREFIX;
+ }
+ return flags;
+}
+
+void MenuItemView::AddEmptyMenus() {
+ DCHECK(HasSubmenu());
+ if (submenu_->GetChildViewCount() == 0) {
+ submenu_->AddChildView(0, new EmptyMenuMenuItem(this));
+ } else {
+ for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
+ ++i) {
+ MenuItemView* child = submenu_->GetMenuItemAt(i);
+ if (child->HasSubmenu())
+ child->AddEmptyMenus();
+ }
+ }
+}
+
+void MenuItemView::RemoveEmptyMenus() {
+ DCHECK(HasSubmenu());
+ // Iterate backwards as we may end up removing views, which alters the child
+ // view count.
+ for (int i = submenu_->GetChildViewCount() - 1; i >= 0; --i) {
+ View* child = submenu_->GetChildViewAt(i);
+ if (child->GetID() == MenuItemView::kMenuItemViewID) {
+ MenuItemView* menu_item = static_cast<MenuItemView*>(child);
+ if (menu_item->HasSubmenu())
+ menu_item->RemoveEmptyMenus();
+ } else if (child->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) {
+ submenu_->RemoveChildView(child);
+ }
+ }
+}
+
+void MenuItemView::AdjustBoundsForRTLUI(RECT* rect) const {
+ gfx::Rect mirrored_rect(*rect);
+ mirrored_rect.set_x(MirroredLeftPointForRect(mirrored_rect));
+ *rect = mirrored_rect.ToRECT();
+}
+
+void MenuItemView::Paint(ChromeCanvas* canvas, bool for_drag) {
+ bool render_selection =
+ (!for_drag && IsSelected() &&
+ parent_menu_item_->GetSubmenu()->GetShowSelection(this));
+ int state = render_selection ? MPI_HOT :
+ (IsEnabled() ? MPI_NORMAL : MPI_DISABLED);
+ HDC dc = canvas->beginPlatformPaint();
+
+ // The gutter is rendered before the background.
+ if (render_gutter && !for_drag) {
+ RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0,
+ height() };
+ gutter_bounds.right = gutter_bounds.left + gutter_width;
+ AdjustBoundsForRTLUI(&gutter_bounds);
+ NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL,
+ &gutter_bounds);
+ }
+
+ // Render the background.
+ if (!for_drag) {
+ RECT item_bounds = { 0, 0, width(), height() };
+ AdjustBoundsForRTLUI(&item_bounds);
+ NativeTheme::instance()->PaintMenuItemBackground(
+ NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection,
+ &item_bounds);
+ }
+
+ int icon_x = kItemLeftMargin;
+ int top_margin = GetTopMargin();
+ int bottom_margin = GetBottomMargin();
+ int icon_y = top_margin + (height() - kItemTopMargin -
+ bottom_margin - check_height) / 2;
+ int icon_height = check_height;
+ int icon_width = check_width;
+
+ if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) {
+ // Draw the check background.
+ RECT check_bg_bounds = { 0, 0, icon_x + icon_width, height() };
+ const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED;
+ AdjustBoundsForRTLUI(&check_bg_bounds);
+ NativeTheme::instance()->PaintMenuCheckBackground(
+ NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state,
+ &check_bg_bounds);
+
+ // And the check.
+ RECT check_bounds = { icon_x, icon_y, icon_x + icon_width,
+ icon_y + icon_height };
+ const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL :
+ MC_CHECKMARKDISABLED;
+ AdjustBoundsForRTLUI(&check_bounds);
+ NativeTheme::instance()->PaintMenuCheck(
+ NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_bounds,
+ render_selection);
+ }
+
+ // Render the foreground.
+ // Menu color is specific to Vista, fallback to classic colors if can't
+ // get color.
+ int default_sys_color = render_selection ? COLOR_HIGHLIGHTTEXT :
+ (IsEnabled() ? COLOR_MENUTEXT : COLOR_GRAYTEXT);
+ SkColor fg_color = NativeTheme::instance()->GetThemeColorWithDefault(
+ NativeTheme::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR,
+ default_sys_color);
+ int width = this->width() - item_right_margin - label_start;
+ ChromeFont& font = GetRootMenuItem()->font_;
+ gfx::Rect text_bounds(label_start, top_margin, width, font.height());
+ text_bounds.set_x(MirroredLeftPointForRect(text_bounds));
+ if (for_drag) {
+ // With different themes, it's difficult to tell what the correct foreground
+ // and background colors are for the text to draw the correct halo. Instead,
+ // just draw black on white, which will look good in most cases.
+ canvas->DrawStringWithHalo(GetTitle(), font, 0x00000000, 0xFFFFFFFF,
+ text_bounds.x(), text_bounds.y(),
+ text_bounds.width(), text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+ } else {
+ canvas->DrawStringInt(GetTitle(), font, fg_color,
+ text_bounds.x(), text_bounds.y(), text_bounds.width(),
+ text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+ }
+
+ if (icon_.width() > 0) {
+ gfx::Rect icon_bounds(kItemLeftMargin,
+ top_margin + (height() - top_margin -
+ bottom_margin - icon_.height()) / 2,
+ icon_.width(),
+ icon_.height());
+ icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds));
+ canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y());
+ }
+
+ if (HasSubmenu()) {
+ int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED;
+ RECT arrow_bounds = {
+ this->width() - item_right_margin + kLabelToArrowPadding,
+ 0,
+ 0,
+ height()
+ };
+ arrow_bounds.right = arrow_bounds.left + arrow_width;
+ AdjustBoundsForRTLUI(&arrow_bounds);
+
+ // If our sub menus open from right to left (which is the case when the
+ // locale is RTL) then we should make sure the menu arrow points to the
+ // right direction.
+ NativeTheme::MenuArrowDirection arrow_direction;
+ if (UILayoutIsRightToLeft())
+ arrow_direction = NativeTheme::LEFT_POINTING_ARROW;
+ else
+ arrow_direction = NativeTheme::RIGHT_POINTING_ARROW;
+
+ NativeTheme::instance()->PaintMenuArrow(
+ NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_bounds,
+ arrow_direction, render_selection);
+ }
+ canvas->endPlatformPaint();
+}
+
+void MenuItemView::DestroyAllMenuHosts() {
+ if (!HasSubmenu())
+ return;
+
+ submenu_->Close();
+ for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
+ ++i) {
+ submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts();
+ }
+}
+
+int MenuItemView::GetTopMargin() {
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_ ? kItemTopMargin : kItemNoIconTopMargin;
+}
+
+int MenuItemView::GetBottomMargin() {
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_ ?
+ kItemBottomMargin : kItemNoIconBottomMargin;
+}
+
+// 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 = source->GetMenuItem();
+ 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);
+ ChromeCanvas canvas(item->width(), item->height(), false);
+ item->Paint(&canvas, true);
+
+ scoped_refptr<OSExchangeData> data(new OSExchangeData);
+ item->GetDelegate()->WriteDragData(item, data.get());
+ 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(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() == EmptyMenuMenuItem::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() == EmptyMenuMenuItem::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;
+ 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() - 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->host() && submenu->host()->GetNativeView() &&
+ GetWindowThreadProcessId(submenu->host()->GetNativeView(), 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
diff --git a/views/controls/menu/chrome_menu.h b/views/controls/menu/chrome_menu.h
new file mode 100644
index 0000000..02caaed
--- /dev/null
+++ b/views/controls/menu/chrome_menu.h
@@ -0,0 +1,948 @@
+// 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.
+
+#ifndef VIEWS_CONTROLS_MENU_CHROME_MENU_H_
+#define VIEWS_CONTROLS_MENU_CHROME_MENU_H_
+
+#include <list>
+#include <vector>
+
+#include "app/drag_drop_types.h"
+#include "app/gfx/chrome_font.h"
+#include "base/gfx/point.h"
+#include "base/gfx/rect.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "skia/include/SkBitmap.h"
+#include "views/controls/menu/controller.h"
+#include "views/view.h"
+
+namespace views {
+
+class WidgetWin;
+class MenuController;
+class MenuItemView;
+class SubmenuView;
+
+namespace {
+class MenuHost;
+class MenuHostRootView;
+class MenuScrollTask;
+class MenuScrollViewContainer;
+}
+
+// MenuDelegate --------------------------------------------------------------
+
+// Delegate for the menu.
+
+class MenuDelegate : Controller {
+ public:
+ // Used during drag and drop to indicate where the drop indicator should
+ // be rendered.
+ enum DropPosition {
+ // Indicates a drop is not allowed here.
+ DROP_NONE,
+
+ // Indicates the drop should occur before the item.
+ DROP_BEFORE,
+
+ // Indicates the drop should occur after the item.
+ DROP_AFTER,
+
+ // Indicates the drop should occur on the item.
+ DROP_ON
+ };
+
+ // Whether or not an item should be shown as checked.
+ // TODO(sky): need checked support.
+ virtual bool IsItemChecked(int id) const {
+ return false;
+ }
+
+ // The string shown for the menu item. This is only invoked when an item is
+ // added with an empty label.
+ virtual std::wstring GetLabel(int id) const {
+ return std::wstring();
+ }
+
+ // Shows the context menu with the specified id. This is invoked when the
+ // user does the appropriate gesture to show a context menu. The id
+ // identifies the id of the menu to show the context menu for.
+ // is_mouse_gesture is true if this is the result of a mouse gesture.
+ // If this is not the result of a mouse gesture x/y is the recommended
+ // location to display the content menu at. In either case, x/y is in
+ // screen coordinates.
+ // Returns true if a context menu was displayed, otherwise false
+ virtual bool ShowContextMenu(MenuItemView* source,
+ int id,
+ int x,
+ int y,
+ bool is_mouse_gesture) {
+ return false;
+ }
+
+ // Controller
+ virtual bool SupportsCommand(int id) const {
+ return true;
+ }
+ virtual bool IsCommandEnabled(int id) const {
+ return true;
+ }
+ virtual bool GetContextualLabel(int id, std::wstring* out) const {
+ return false;
+ }
+ virtual void ExecuteCommand(int id) {
+ }
+
+ // Executes the specified command. mouse_event_flags give the flags of the
+ // mouse event that triggered this to be invoked (views::MouseEvent
+ // flags). mouse_event_flags is 0 if this is triggered by a user gesture
+ // other than a mouse event.
+ virtual void ExecuteCommand(int id, int mouse_event_flags) {
+ ExecuteCommand(id);
+ }
+
+ // Returns true if the specified mouse event is one the user can use
+ // to trigger, or accept, the mouse. Defaults to left or right mouse buttons.
+ virtual bool IsTriggerableEvent(const MouseEvent& e) {
+ return e.IsLeftMouseButton() || e.IsRightMouseButton();
+ }
+
+ // Invoked to determine if drops can be accepted for a submenu. This is
+ // ONLY invoked for menus that have submenus and indicates whether or not
+ // a drop can occur on any of the child items of the item. For example,
+ // consider the following menu structure:
+ //
+ // A
+ // B
+ // C
+ //
+ // Where A has a submenu with children B and C. This is ONLY invoked for
+ // A, not B and C.
+ //
+ // To restrict which children can be dropped on override GetDropOperation.
+ virtual bool CanDrop(MenuItemView* menu, const OSExchangeData& data) {
+ return false;
+ }
+
+ // Returns the drop operation for the specified target menu item. This is
+ // only invoked if CanDrop returned true for the parent menu. position
+ // is set based on the location of the mouse, reset to specify a different
+ // position.
+ //
+ // If a drop should not be allowed, returned DragDropTypes::DRAG_NONE.
+ virtual int GetDropOperation(MenuItemView* item,
+ const DropTargetEvent& event,
+ DropPosition* position) {
+ NOTREACHED() << "If you override CanDrop, you need to override this too";
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ // Invoked to perform the drop operation. This is ONLY invoked if
+ // canDrop returned true for the parent menu item, and GetDropOperation
+ // returned an operation other than DragDropTypes::DRAG_NONE.
+ //
+ // menu indicates the menu the drop occurred on.
+ virtual int OnPerformDrop(MenuItemView* menu,
+ DropPosition position,
+ const DropTargetEvent& event) {
+ NOTREACHED() << "If you override CanDrop, you need to override this too";
+ return DragDropTypes::DRAG_NONE;
+ }
+
+ // Invoked to determine if it is possible for the user to drag the specified
+ // menu item.
+ virtual bool CanDrag(MenuItemView* menu) {
+ return false;
+ }
+
+ // Invoked to write the data for a drag operation to data. sender is the
+ // MenuItemView being dragged.
+ virtual void WriteDragData(MenuItemView* sender, OSExchangeData* data) {
+ NOTREACHED() << "If you override CanDrag, you must override this too.";
+ }
+
+ // Invoked to determine the drag operations for a drag session of sender.
+ // See DragDropTypes for possible values.
+ virtual int GetDragOperations(MenuItemView* sender) {
+ NOTREACHED() << "If you override CanDrag, you must override this too.";
+ return 0;
+ }
+
+ // Notification the menu has closed. This is only sent when running the
+ // menu for a drop.
+ virtual void DropMenuClosed(MenuItemView* menu) {
+ }
+
+ // Notification that the user has highlighted the specified item.
+ virtual void SelectionChanged(MenuItemView* menu) {
+ }
+};
+
+// MenuItemView --------------------------------------------------------------
+
+// MenuItemView represents a single menu item with a label and optional icon.
+// Each MenuItemView may also contain a submenu, which in turn may contain
+// any number of child MenuItemViews.
+//
+// To use a menu create an initial MenuItemView using the constructor that
+// takes a MenuDelegate, then create any number of child menu items by way
+// of the various AddXXX methods.
+//
+// MenuItemView is itself a View, which means you can add Views to each
+// MenuItemView. This normally NOT want you want, rather add other child Views
+// to the submenu of the MenuItemView.
+//
+// There are two ways to show a MenuItemView:
+// 1. Use RunMenuAt. This blocks the caller, executing the selected command
+// on success.
+// 2. Use RunMenuForDropAt. This is intended for use during a drop session
+// and does NOT block the caller. Instead the delegate is notified when the
+// menu closes via the DropMenuClosed method.
+
+class MenuItemView : public View {
+ friend class MenuController;
+
+ public:
+ // ID used to identify menu items.
+ static const int kMenuItemViewID;
+
+ // If true SetNestableTasksAllowed(true) is invoked before MessageLoop::Run
+ // is invoked. This is only useful for testing and defaults to false.
+ static bool allow_task_nesting_during_run_;
+
+ // Different types of menu items.
+ enum Type {
+ NORMAL,
+ SUBMENU,
+ CHECKBOX,
+ RADIO,
+ SEPARATOR
+ };
+
+ // Where the menu should be anchored to.
+ enum AnchorPosition {
+ TOPLEFT,
+ TOPRIGHT
+ };
+
+ // Constructor for use with the top level menu item. This menu is never
+ // shown to the user, rather its use as the parent for all menu items.
+ explicit MenuItemView(MenuDelegate* delegate);
+
+ virtual ~MenuItemView();
+
+ // Run methods. See description above class for details. Both Run methods take
+ // a rectangle, which is used to position the menu. |has_mnemonics| indicates
+ // whether the items have mnemonics. Mnemonics are identified by way of the
+ // character following the '&'.
+ void RunMenuAt(HWND parent,
+ const gfx::Rect& bounds,
+ AnchorPosition anchor,
+ bool has_mnemonics);
+ void RunMenuForDropAt(HWND parent,
+ const gfx::Rect& bounds,
+ AnchorPosition anchor);
+
+ // Hides and cancels the menu. This does nothing if the menu is not open.
+ void Cancel();
+
+ // Adds an item to this menu.
+ // item_id The id of the item, used to identify it in delegate callbacks
+ // or (if delegate is NULL) to identify the command associated
+ // with this item with the controller specified in the ctor. Note
+ // that this value should not be 0 as this has a special meaning
+ // ("NULL command, no item selected")
+ // label The text label shown.
+ // type The type of item.
+ void AppendMenuItem(int item_id,
+ const std::wstring& label,
+ Type type) {
+ AppendMenuItemInternal(item_id, label, SkBitmap(), type);
+ }
+
+ // Append a submenu to this menu.
+ // The returned pointer is owned by this menu.
+ MenuItemView* AppendSubMenu(int item_id,
+ const std::wstring& label) {
+ return AppendMenuItemInternal(item_id, label, SkBitmap(), SUBMENU);
+ }
+
+ // Append a submenu with an icon to this menu.
+ // The returned pointer is owned by this menu.
+ MenuItemView* AppendSubMenuWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ return AppendMenuItemInternal(item_id, label, icon, SUBMENU);
+ }
+
+ // This is a convenience for standard text label menu items where the label
+ // is provided with this call.
+ void AppendMenuItemWithLabel(int item_id,
+ const std::wstring& label) {
+ AppendMenuItem(item_id, label, NORMAL);
+ }
+
+ // This is a convenience for text label menu items where the label is
+ // provided by the delegate.
+ void AppendDelegateMenuItem(int item_id) {
+ AppendMenuItem(item_id, std::wstring(), NORMAL);
+ }
+
+ // Adds a separator to this menu
+ void AppendSeparator() {
+ AppendMenuItemInternal(0, std::wstring(), SkBitmap(), SEPARATOR);
+ }
+
+ // Appends a menu item with an icon. This is for the menu item which
+ // needs an icon. Calling this function forces the Menu class to draw
+ // the menu, instead of relying on Windows.
+ void AppendMenuItemWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ AppendMenuItemInternal(item_id, label, icon, NORMAL);
+ }
+
+ // Returns the view that contains child menu items. If the submenu has
+ // not been creates, this creates it.
+ virtual SubmenuView* CreateSubmenu();
+
+ // Returns true if this menu item has a submenu.
+ virtual bool HasSubmenu() const { return (submenu_ != NULL); }
+
+ // Returns the view containing child menu items.
+ virtual SubmenuView* GetSubmenu() const { return submenu_; }
+
+ // Returns the parent menu item.
+ MenuItemView* GetParentMenuItem() const { return parent_menu_item_; }
+
+ // Sets the font.
+ void SetFont(const ChromeFont& font) { font_ = font; }
+
+ // Sets the title
+ void SetTitle(const std::wstring& title) {
+ title_ = title;
+ }
+
+ // Returns the title.
+ const std::wstring& GetTitle() const { return title_; }
+
+ // Sets whether this item is selected. This is invoked as the user moves
+ // the mouse around the menu while open.
+ void SetSelected(bool selected);
+
+ // Returns true if the item is selected.
+ bool IsSelected() const { return selected_; }
+
+ // Sets the icon for the descendant identified by item_id.
+ void SetIcon(const SkBitmap& icon, int item_id);
+
+ // Sets the icon of this menu item.
+ void SetIcon(const SkBitmap& icon);
+
+ // Returns the icon.
+ const SkBitmap& GetIcon() const { return icon_; }
+
+ // Sets the command id of this menu item.
+ void SetCommand(int command) { command_ = command; }
+
+ // Returns the command id of this item.
+ int GetCommand() const { return command_; }
+
+ // Paints the menu item.
+ virtual void Paint(ChromeCanvas* canvas);
+
+ // Returns the preferred size of this item.
+ virtual gfx::Size GetPreferredSize();
+
+ // Returns the object responsible for controlling showing the menu.
+ MenuController* GetMenuController();
+
+ // Returns the delegate. This returns the delegate of the root menu item.
+ MenuDelegate* GetDelegate();
+
+ // Returns the root parent, or this if this has no parent.
+ MenuItemView* GetRootMenuItem();
+
+ // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView
+ // doesn't have a mnemonic.
+ wchar_t GetMnemonic();
+
+ // Do we have icons? This only has effect on the top menu. Turning this on
+ // makes the menus slightly wider and taller.
+ void set_has_icons(bool has_icons) {
+ has_icons_ = has_icons;
+ }
+
+ protected:
+ // Creates a MenuItemView. This is used by the various AddXXX methods.
+ MenuItemView(MenuItemView* parent, int command, Type type);
+
+ private:
+ // Called by the two constructors to initialize this menu item.
+ void Init(MenuItemView* parent,
+ int command,
+ MenuItemView::Type type,
+ MenuDelegate* delegate);
+
+ // All the AddXXX methods funnel into this.
+ MenuItemView* AppendMenuItemInternal(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon,
+ Type type);
+
+ // Returns the descendant with the specified command.
+ MenuItemView* GetDescendantByID(int id);
+
+ // Invoked by the MenuController when the menu closes as the result of
+ // drag and drop run.
+ void DropMenuClosed(bool notify_delegate);
+
+ // The RunXXX methods call into this to set up the necessary state before
+ // running.
+ void PrepareForRun(bool has_mnemonics);
+
+ // Returns the flags passed to DrawStringInt.
+ int GetDrawStringFlags();
+
+ // If this menu item has no children a child is added showing it has no
+ // children. Otherwise AddEmtpyMenuIfNecessary is recursively invoked on
+ // child menu items that have children.
+ void AddEmptyMenus();
+
+ // Undoes the work of AddEmptyMenus.
+ void RemoveEmptyMenus();
+
+ // Given bounds within our View, this helper routine mirrors the bounds if
+ // necessary.
+ void AdjustBoundsForRTLUI(RECT* rect) const;
+
+ // Actual paint implementation. If for_drag is true, portions of the menu
+ // are not rendered.
+ void Paint(ChromeCanvas* canvas, bool for_drag);
+
+ // Destroys the window used to display this menu and recursively destroys
+ // the windows used to display all descendants.
+ void DestroyAllMenuHosts();
+
+ // Returns the various margins.
+ int GetTopMargin();
+ int GetBottomMargin();
+
+ // The delegate. This is only valid for the root menu item. You shouldn't
+ // use this directly, instead use GetDelegate() which walks the tree as
+ // as necessary.
+ MenuDelegate* delegate_;
+
+ // Returns the controller for the run operation, or NULL if the menu isn't
+ // showing.
+ MenuController* controller_;
+
+ // Used to detect when Cancel was invoked.
+ bool canceled_;
+
+ // Our parent.
+ MenuItemView* parent_menu_item_;
+
+ // Type of menu. NOTE: MenuItemView doesn't itself represent SEPARATOR,
+ // that is handled by an entirely different view class.
+ Type type_;
+
+ // Whether we're selected.
+ bool selected_;
+
+ // Command id.
+ int command_;
+
+ // Submenu, created via CreateSubmenu.
+ SubmenuView* submenu_;
+
+ // Font.
+ ChromeFont font_;
+
+ // Title.
+ std::wstring title_;
+
+ // Icon.
+ SkBitmap icon_;
+
+ // Does the title have a mnemonic?
+ bool has_mnemonics_;
+
+ bool has_icons_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MenuItemView);
+};
+
+// SubmenuView ----------------------------------------------------------------
+
+// SubmenuView is the parent of all menu items.
+//
+// SubmenuView has the following responsibilities:
+// . It positions and sizes all child views (any type of View may be added,
+// not just MenuItemViews).
+// . Forwards the appropriate events to the MenuController. This allows the
+// MenuController to update the selection as the user moves the mouse around.
+// . Renders the drop indicator during a drop operation.
+// . Shows and hides the window (a WidgetWin) when the menu is shown on
+// screen.
+//
+// SubmenuView is itself contained in a MenuScrollViewContainer.
+// MenuScrollViewContainer handles showing as much of the SubmenuView as the
+// screen allows. If the SubmenuView is taller than the screen, scroll buttons
+// are provided that allow the user to see all the menu items.
+class SubmenuView : public View {
+ public:
+ // Creates a SubmenuView for the specified menu item.
+ explicit SubmenuView(MenuItemView* parent);
+ ~SubmenuView();
+
+ // Returns the number of child views that are MenuItemViews.
+ // MenuItemViews are identified by ID.
+ int GetMenuItemCount();
+
+ // Returns the MenuItemView at the specified index.
+ MenuItemView* GetMenuItemAt(int index);
+
+ // Positions and sizes the child views. This tiles the views vertically,
+ // giving each child the available width.
+ virtual void Layout();
+ virtual gfx::Size GetPreferredSize();
+
+ // View method. Overriden to schedule a paint. We do this so that when
+ // scrolling occurs, everything is repainted correctly.
+ virtual void DidChangeBounds(const gfx::Rect& previous,
+ const gfx::Rect& current);
+
+ // Painting.
+ void PaintChildren(ChromeCanvas* canvas);
+
+ // Drag and drop methods. These are forwarded to the MenuController.
+ virtual bool CanDrop(const OSExchangeData& data);
+ virtual void OnDragEntered(const DropTargetEvent& event);
+ virtual int OnDragUpdated(const DropTargetEvent& event);
+ virtual void OnDragExited();
+ virtual int OnPerformDrop(const DropTargetEvent& event);
+
+ // Scrolls on menu item boundaries.
+ virtual bool OnMouseWheel(const MouseWheelEvent& e);
+
+ // Returns true if the menu is showing.
+ bool IsShowing();
+
+ // Shows the menu at the specified location. Coordinates are in screen
+ // coordinates. max_width gives the max width the view should be.
+ void ShowAt(HWND parent, const gfx::Rect& bounds, bool do_capture);
+
+ // Closes the menu, destroying the host.
+ void Close();
+
+ // Hides the hosting window.
+ //
+ // The hosting window is hidden first, then deleted (Close) when the menu is
+ // done running. This is done to avoid deletion ordering dependencies. In
+ // particular, during drag and drop (and when a modal dialog is shown as
+ // a result of choosing a context menu) it is possible that an event is
+ // being processed by the host, so that host is on the stack when we need to
+ // close the window. If we closed the window immediately (and deleted it),
+ // when control returned back to host we would crash as host was deleted.
+ void Hide();
+
+ // If mouse capture was grabbed, it is released. Does nothing if mouse was
+ // not captured.
+ void ReleaseCapture();
+
+ // Returns the parent menu item we're showing children for.
+ MenuItemView* GetMenuItem() const { return parent_menu_item_; }
+
+ // Overriden to return true. This prevents tab from doing anything.
+ virtual bool CanProcessTabKeyEvents() { return true; }
+
+ // Set the drop item and position.
+ void SetDropMenuItem(MenuItemView* item,
+ MenuDelegate::DropPosition position);
+
+ // Returns whether the selection should be shown for the specified item.
+ // The selection is NOT shown during drag and drop when the drop is over
+ // the menu.
+ bool GetShowSelection(MenuItemView* item);
+
+ // Returns the container for the SubmenuView.
+ MenuScrollViewContainer* GetScrollViewContainer();
+
+ // Returns the host of the menu. Returns NULL if not showing.
+ MenuHost* host() const { return host_; }
+
+ private:
+ // Paints the drop indicator. This is only invoked if item is non-NULL and
+ // position is not DROP_NONE.
+ void PaintDropIndicator(ChromeCanvas* canvas,
+ MenuItemView* item,
+ MenuDelegate::DropPosition position);
+
+ void SchedulePaintForDropIndicator(MenuItemView* item,
+ MenuDelegate::DropPosition position);
+
+ // Calculates the location of th edrop indicator.
+ gfx::Rect CalculateDropIndicatorBounds(MenuItemView* item,
+ MenuDelegate::DropPosition position);
+
+ // Parent menu item.
+ MenuItemView* parent_menu_item_;
+
+ // WidgetWin subclass used to show the children.
+ MenuHost* host_;
+
+ // If non-null, indicates a drop is in progress and drop_item is the item
+ // the drop is over.
+ MenuItemView* drop_item_;
+
+ // Position of the drop.
+ MenuDelegate::DropPosition drop_position_;
+
+ // Ancestor of the SubmenuView, lazily created.
+ MenuScrollViewContainer* scroll_view_container_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SubmenuView);
+};
+
+// MenuController -------------------------------------------------------------
+
+// MenuController manages showing, selecting and drag/drop for menus.
+// All relevant events are forwarded to the MenuController from SubmenuView
+// and MenuHost.
+
+class MenuController : public MessageLoopForUI::Dispatcher {
+ public:
+ friend class MenuHostRootView;
+ friend class MenuItemView;
+ friend class MenuScrollTask;
+
+ // If a menu is currently active, this returns the controller for it.
+ static MenuController* GetActiveInstance();
+
+ // Runs the menu at the specified location. If the menu was configured to
+ // block, the selected item is returned. If the menu does not block this
+ // returns NULL immediately.
+ MenuItemView* Run(HWND parent,
+ MenuItemView* root,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position,
+ int* mouse_event_flags);
+
+ // Whether or not Run blocks.
+ bool IsBlockingRun() const { return blocking_run_; }
+
+ // Sets the selection to menu_item, a value of NULL unselects everything.
+ // If open_submenu is true and menu_item has a submenu, the submenu is shown.
+ // If update_immediately is true, submenus are opened immediately, otherwise
+ // submenus are only opened after a timer fires.
+ //
+ // Internally this updates pending_state_ immediatley, and if
+ // update_immediately is true, CommitPendingSelection is invoked to
+ // show/hide submenus and update state_.
+ void SetSelection(MenuItemView* menu_item,
+ bool open_submenu,
+ bool update_immediately);
+
+ // Cancels the current Run. If all is true, any nested loops are canceled
+ // as well. This immediately hides all menus.
+ void Cancel(bool all);
+
+ // An alternative to Cancel(true) that can be used with a OneShotTimer.
+ void CancelAll() { return Cancel(true); }
+
+ // Various events, forwarded from the submenu.
+ //
+ // NOTE: the coordinates of the events are in that of the
+ // MenuScrollViewContainer.
+ void OnMousePressed(SubmenuView* source, const MouseEvent& event);
+ void OnMouseDragged(SubmenuView* source, const MouseEvent& event);
+ void OnMouseReleased(SubmenuView* source, const MouseEvent& event);
+ void OnMouseMoved(SubmenuView* source, const MouseEvent& event);
+ void OnMouseEntered(SubmenuView* source, const MouseEvent& event);
+ bool CanDrop(SubmenuView* source, const OSExchangeData& data);
+ void OnDragEntered(SubmenuView* source, const DropTargetEvent& event);
+ int OnDragUpdated(SubmenuView* source, const DropTargetEvent& event);
+ void OnDragExited(SubmenuView* source);
+ int OnPerformDrop(SubmenuView* source, const DropTargetEvent& event);
+
+ // Invoked from the scroll buttons of the MenuScrollViewContainer.
+ void OnDragEnteredScrollButton(SubmenuView* source, bool is_up);
+ void OnDragExitedScrollButton(SubmenuView* source);
+
+ private:
+ // Tracks selection information.
+ struct State {
+ State() : item(NULL), submenu_open(false) {}
+
+ // The selected menu item.
+ MenuItemView* item;
+
+ // If item has a submenu this indicates if the submenu is showing.
+ bool submenu_open;
+
+ // Bounds passed to the run menu. Used for positioning the first menu.
+ gfx::Rect initial_bounds;
+
+ // Position of the initial menu.
+ MenuItemView::AnchorPosition anchor;
+
+ // The direction child menus have opened in.
+ std::list<bool> open_leading;
+
+ // Bounds for the monitor we're showing on.
+ gfx::Rect monitor_bounds;
+ };
+
+ // Used by GetMenuPartByScreenCoordinate to indicate the menu part at a
+ // particular location.
+ struct MenuPart {
+ // Type of part.
+ enum Type {
+ NONE,
+ MENU_ITEM,
+ SCROLL_UP,
+ SCROLL_DOWN
+ };
+
+ MenuPart() : type(NONE), menu(NULL), submenu(NULL) {}
+
+ // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP.
+ bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; }
+
+ // Type of part.
+ Type type;
+
+ // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise
+ // this is NULL.
+ // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item
+ // but is over a menu (for example, the mouse is over a separator or
+ // empty menu), this is NULL.
+ MenuItemView* menu;
+
+ // If type is SCROLL_*, this is the submenu the mouse is over.
+ SubmenuView* submenu;
+ };
+
+ // Sets the active MenuController.
+ static void SetActiveInstance(MenuController* controller);
+
+ // Dispatcher method. This returns true if the menu was canceled, or
+ // if the message is such that the menu should be closed.
+ virtual bool Dispatch(const MSG& msg);
+
+ // Key processing. The return value of these is returned from Dispatch.
+ // In other words, if these return false (which they do if escape was
+ // pressed, or a matching mnemonic was found) the message loop returns.
+ bool OnKeyDown(const MSG& msg);
+ bool OnChar(const MSG& msg);
+
+ // Creates a MenuController. If blocking is true, Run blocks the caller
+ explicit MenuController(bool blocking);
+
+ ~MenuController();
+
+ // Invoked when the user accepts the selected item. This is only used
+ // when blocking. This schedules the loop to quit.
+ void Accept(MenuItemView* item, int mouse_event_flags);
+
+ // Closes all menus, including any menus of nested invocations of Run.
+ void CloseAllNestedMenus();
+
+ // Gets the enabled menu item at the specified location.
+ // If over_any_menu is non-null it is set to indicate whether the location
+ // is over any menu. It is possible for this to return NULL, but
+ // over_any_menu to be true. For example, the user clicked on a separator.
+ MenuItemView* GetMenuItemAt(View* menu, int x, int y);
+
+ // If there is an empty menu item at the specified location, it is returned.
+ MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y);
+
+ // Returns true if the coordinate is over the scroll buttons of the
+ // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to
+ // indicate which scroll button the coordinate is.
+ bool IsScrollButtonAt(SubmenuView* source,
+ int x,
+ int y,
+ MenuPart::Type* part);
+
+ // Returns the target for the mouse event.
+ MenuPart GetMenuPartByScreenCoordinate(SubmenuView* source,
+ int source_x,
+ int source_y);
+
+ // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns
+ // true if the supplied SubmenuView contains the location in terms of the
+ // screen. If it does, part is set appropriately and true is returned.
+ bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu,
+ const gfx::Point& screen_loc,
+ MenuPart* part);
+
+ // Returns true if the SubmenuView contains the specified location. This does
+ // NOT included the scroll buttons, only the submenu view.
+ bool DoesSubmenuContainLocation(SubmenuView* submenu,
+ const gfx::Point& screen_loc);
+
+ // Opens/Closes the necessary menus such that state_ matches that of
+ // pending_state_. This is invoked if submenus are not opened immediately,
+ // but after a delay.
+ void CommitPendingSelection();
+
+ // If item has a submenu, it is closed. This does NOT update the selection
+ // in anyway.
+ void CloseMenu(MenuItemView* item);
+
+ // If item has a submenu, it is opened. This does NOT update the selection
+ // in anyway.
+ void OpenMenu(MenuItemView* item);
+
+ // Builds the paths of the two menu items into the two paths, and
+ // sets first_diff_at to the location of the first difference between the
+ // two paths.
+ void BuildPathsAndCalculateDiff(MenuItemView* old_item,
+ MenuItemView* new_item,
+ std::vector<MenuItemView*>* old_path,
+ std::vector<MenuItemView*>* new_path,
+ size_t* first_diff_at);
+
+ // Builds the path for the specified item.
+ void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path);
+
+ // Starts/stops the timer that commits the pending state to state
+ // (opens/closes submenus).
+ void StartShowTimer();
+ void StopShowTimer();
+
+ // Starts/stops the timer cancel the menu. This is used during drag and
+ // drop when the drop enters/exits the menu.
+ void StartCancelAllTimer();
+ void StopCancelAllTimer();
+
+ // Calculates the bounds of the menu to show. is_leading is set to match the
+ // direction the menu opened in.
+ gfx::Rect CalculateMenuBounds(MenuItemView* item,
+ bool prefer_leading,
+ bool* is_leading);
+
+ // Returns the depth of the menu.
+ static int MenuDepth(MenuItemView* item);
+
+ // Selects the next/previous menu item.
+ void IncrementSelection(int delta);
+
+ // If the selected item has a submenu and it isn't currently open, the
+ // the selection is changed such that the menu opens immediately.
+ void OpenSubmenuChangeSelectionIfCan();
+
+ // If possible, closes the submenu.
+ void CloseSubmenu();
+
+ // Returns true if window is the window used to show item, or any of
+ // items ancestors.
+ bool IsMenuWindow(MenuItemView* item, HWND window);
+
+ // Selects by mnemonic, and if that doesn't work tries the first character of
+ // the title. Returns true if a match was selected and the menu should exit.
+ bool SelectByChar(wchar_t key);
+
+ // If there is a window at the location of the event, a new mouse event is
+ // generated and posted to it.
+ void RepostEvent(SubmenuView* source, const MouseEvent& event);
+
+ // Sets the drop target to new_item.
+ void SetDropMenuItem(MenuItemView* new_item,
+ MenuDelegate::DropPosition position);
+
+ // Starts/stops scrolling as appropriate. part gives the part the mouse is
+ // over.
+ void UpdateScrolling(const MenuPart& part);
+
+ // Stops scrolling.
+ void StopScrolling();
+
+ // The active instance.
+ static MenuController* active_instance_;
+
+ // If true, Run blocks. If false, Run doesn't block and this is used for
+ // drag and drop. Note that the semantics for drag and drop are slightly
+ // different: cancel timer is kicked off any time the drag moves outside the
+ // menu, mouse events do nothing...
+ bool blocking_run_;
+
+ // If true, we're showing.
+ bool showing_;
+
+ // If true, all nested run loops should be exited.
+ bool exit_all_;
+
+ // Whether we did a capture. We do a capture only if we're blocking and
+ // the mouse was down when Run.
+ bool did_capture_;
+
+ // As the user drags the mouse around pending_state_ changes immediately.
+ // When the user stops moving/dragging the mouse (or clicks the mouse)
+ // pending_state_ is committed to state_, potentially resulting in
+ // opening or closing submenus. This gives a slight delayed effect to
+ // submenus as the user moves the mouse around. This is done so that as the
+ // user moves the mouse all submenus don't immediately pop.
+ State pending_state_;
+ State state_;
+
+ // If the user accepted the selection, this is the result.
+ MenuItemView* result_;
+
+ // The mouse event flags when the user clicked on a menu. Is 0 if the
+ // user did not use the mousee to select the menu.
+ int result_mouse_event_flags_;
+
+ // If not empty, it means we're nested. When Run is invoked from within
+ // Run, the current state (state_) is pushed onto menu_stack_. This allows
+ // MenuController to restore the state when the nested run returns.
+ std::list<State> menu_stack_;
+
+ // As the mouse moves around submenus are not opened immediately. Instead
+ // they open after this timer fires.
+ base::OneShotTimer<MenuController> show_timer_;
+
+ // Used to invoke CancelAll(). This is used during drag and drop to hide the
+ // menu after the mouse moves out of the of the menu. This is necessitated by
+ // the lack of an ability to detect when the drag has completed from the drop
+ // side.
+ base::OneShotTimer<MenuController> cancel_all_timer_;
+
+ // Drop target.
+ MenuItemView* drop_target_;
+ MenuDelegate::DropPosition drop_position_;
+
+ // Owner of child windows.
+ HWND owner_;
+
+ // Indicates a possible drag operation.
+ bool possible_drag_;
+
+ // Location the mouse was pressed at. Used to detect d&d.
+ int press_x_;
+ int press_y_;
+
+ // We get a slew of drag updated messages as the mouse is over us. To avoid
+ // continually processing whether we can drop, we cache the coordinates.
+ bool valid_drop_coordinates_;
+ int drop_x_;
+ int drop_y_;
+ int last_drop_operation_;
+
+ // If true, we're in the middle of invoking ShowAt on a submenu.
+ bool showing_submenu_;
+
+ // Task for scrolling the menu. If non-null indicates a scroll is currently
+ // underway.
+ scoped_ptr<MenuScrollTask> scroll_task_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MenuController);
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_MENU_CHROME_MENU_H_
diff --git a/views/controls/menu/controller.h b/views/controls/menu/controller.h
new file mode 100644
index 0000000..6a693cc
--- /dev/null
+++ b/views/controls/menu/controller.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef VIEWS_CONTROLS_MENU_CONTROLLER_H_
+#define VIEWS_CONTROLS_MENU_CONTROLLER_H_
+
+#include <string>
+
+// TODO(beng): remove this interface and fold it into MenuDelegate.
+
+class Controller {
+ public:
+ virtual ~Controller() { }
+
+ // Whether or not a command is supported by this controller.
+ virtual bool SupportsCommand(int id) const = 0;
+
+ // Whether or not a command is enabled.
+ virtual bool IsCommandEnabled(int id) const = 0;
+
+ // Assign the provided string with a contextual label. Returns true if a
+ // contextual label exists and false otherwise. This method can be used when
+ // implementing a menu or button that needs to have a different label
+ // depending on the context. If this method returns false, the default
+ // label used when creating the button or menu is used.
+ virtual bool GetContextualLabel(int id, std::wstring* out) const = 0;
+
+ // Executes a command.
+ virtual void ExecuteCommand(int id) = 0;
+};
+
+#endif // VIEWS_CONTROLS_MENU_CONTROLLER_H_
diff --git a/views/controls/menu/menu.cc b/views/controls/menu/menu.cc
new file mode 100644
index 0000000..1597da5
--- /dev/null
+++ b/views/controls/menu/menu.cc
@@ -0,0 +1,626 @@
+// 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.h"
+
+#include <atlbase.h>
+#include <atlapp.h>
+#include <atlwin.h>
+#include <atlcrack.h>
+#include <atlframe.h>
+#include <atlmisc.h>
+#include <string>
+
+#include "app/gfx/chrome_canvas.h"
+#include "app/gfx/chrome_font.h"
+#include "app/l10n_util.h"
+#include "app/l10n_util_win.h"
+#include "base/gfx/rect.h"
+#include "base/logging.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "views/accelerator.h"
+
+const SkBitmap* Menu::Delegate::kEmptyIcon = 0;
+
+// The width of an icon, including the pixels between the icon and
+// the item label.
+static const int kIconWidth = 23;
+// Margins between the top of the item and the label.
+static const int kItemTopMargin = 3;
+// Margins between the bottom of the item and the label.
+static const int kItemBottomMargin = 4;
+// Margins between the left of the item and the icon.
+static const int kItemLeftMargin = 4;
+// Margins between the right of the item and the label.
+static const int kItemRightMargin = 10;
+// The width for displaying the sub-menu arrow.
+static const int kArrowWidth = 10;
+
+// Current active MenuHostWindow. If NULL, no menu is active.
+static MenuHostWindow* active_host_window = NULL;
+
+// The data of menu items needed to display.
+struct Menu::ItemData {
+ std::wstring label;
+ SkBitmap icon;
+ bool submenu;
+};
+
+namespace {
+
+static int ChromeGetMenuItemID(HMENU hMenu, int pos) {
+ // The built-in Windows ::GetMenuItemID doesn't work for submenus,
+ // so here's our own implementation.
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_ID;
+ GetMenuItemInfo(hMenu, pos, TRUE, &mii);
+ return mii.wID;
+}
+
+// MenuHostWindow -------------------------------------------------------------
+
+// MenuHostWindow is the HWND the HMENU is parented to. MenuHostWindow is used
+// to intercept right clicks on the HMENU and notify the delegate as well as
+// for drawing icons.
+//
+class MenuHostWindow : public CWindowImpl<MenuHostWindow, CWindow,
+ CWinTraits<WS_CHILD>> {
+ public:
+ MenuHostWindow(Menu* menu, HWND parent_window) : menu_(menu) {
+ int extended_style = 0;
+ // If the menu needs to be created with a right-to-left UI layout, we must
+ // set the appropriate RTL flags (such as WS_EX_LAYOUTRTL) property for the
+ // underlying HWND.
+ if (menu_->delegate_->IsRightToLeftUILayout())
+ extended_style |= l10n_util::GetExtendedStyles();
+ Create(parent_window, gfx::Rect().ToRECT(), 0, 0, extended_style);
+ }
+
+ ~MenuHostWindow() {
+ DestroyWindow();
+ }
+
+ DECLARE_FRAME_WND_CLASS(L"MenuHostWindow", NULL);
+ BEGIN_MSG_MAP(MenuHostWindow);
+ MSG_WM_RBUTTONUP(OnRButtonUp)
+ MSG_WM_MEASUREITEM(OnMeasureItem)
+ MSG_WM_DRAWITEM(OnDrawItem)
+ END_MSG_MAP();
+
+ private:
+ // NOTE: I really REALLY tried to use WM_MENURBUTTONUP, but I ran into
+ // two problems in using it:
+ // 1. It doesn't contain the coordinates of the mouse.
+ // 2. It isn't invoked for menuitems representing a submenu that have children
+ // menu items (not empty).
+
+ void OnRButtonUp(UINT w_param, const CPoint& loc) {
+ int id;
+ if (menu_->delegate_ && FindMenuIDByLocation(menu_, loc, &id))
+ menu_->delegate_->ShowContextMenu(menu_, id, loc.x, loc.y, true);
+ }
+
+ void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* lpmis) {
+ Menu::ItemData* data = reinterpret_cast<Menu::ItemData*>(lpmis->itemData);
+ if (data != NULL) {
+ ChromeFont font;
+ lpmis->itemWidth = font.GetStringWidth(data->label) + kIconWidth +
+ kItemLeftMargin + kItemRightMargin -
+ GetSystemMetrics(SM_CXMENUCHECK);
+ if (data->submenu)
+ lpmis->itemWidth += kArrowWidth;
+ // If the label contains an accelerator, make room for tab.
+ if (data->label.find(L'\t') != std::wstring::npos)
+ lpmis->itemWidth += font.GetStringWidth(L" ");
+ lpmis->itemHeight = font.height() + kItemBottomMargin + kItemTopMargin;
+ } else {
+ // Measure separator size.
+ lpmis->itemHeight = GetSystemMetrics(SM_CYMENU) / 2;
+ lpmis->itemWidth = 0;
+ }
+ }
+
+ void OnDrawItem(UINT wParam, DRAWITEMSTRUCT* lpdis) {
+ HDC hDC = lpdis->hDC;
+ COLORREF prev_bg_color, prev_text_color;
+
+ // Set background color and text color
+ if (lpdis->itemState & ODS_SELECTED) {
+ prev_bg_color = SetBkColor(hDC, GetSysColor(COLOR_HIGHLIGHT));
+ prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ } else {
+ prev_bg_color = SetBkColor(hDC, GetSysColor(COLOR_MENU));
+ if (lpdis->itemState & ODS_DISABLED)
+ prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
+ else
+ prev_text_color = SetTextColor(hDC, GetSysColor(COLOR_MENUTEXT));
+ }
+
+ if (lpdis->itemData) {
+ Menu::ItemData* data =
+ reinterpret_cast<Menu::ItemData*>(lpdis->itemData);
+
+ // Draw the background.
+ HBRUSH hbr = CreateSolidBrush(GetBkColor(hDC));
+ FillRect(hDC, &lpdis->rcItem, hbr);
+ DeleteObject(hbr);
+
+ // Draw the label.
+ RECT rect = lpdis->rcItem;
+ rect.top += kItemTopMargin;
+ // Should we add kIconWidth only when icon.width() != 0 ?
+ rect.left += kItemLeftMargin + kIconWidth;
+ rect.right -= kItemRightMargin;
+ UINT format = DT_TOP | DT_SINGLELINE;
+ // Check whether the mnemonics should be underlined.
+ BOOL underline_mnemonics;
+ SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &underline_mnemonics, 0);
+ if (!underline_mnemonics)
+ format |= DT_HIDEPREFIX;
+ ChromeFont font;
+ HGDIOBJ old_font = static_cast<HFONT>(SelectObject(hDC, font.hfont()));
+ int fontsize = font.FontSize();
+
+ // If an accelerator is specified (with a tab delimiting the rest of the
+ // label from the accelerator), we have to justify the fist part on the
+ // left and the accelerator on the right.
+ // TODO(jungshik): This will break in RTL UI. Currently, he/ar use the
+ // window system UI font and will not hit here.
+ std::wstring label = data->label;
+ std::wstring accel;
+ std::wstring::size_type tab_pos = label.find(L'\t');
+ if (tab_pos != std::wstring::npos) {
+ accel = label.substr(tab_pos);
+ label = label.substr(0, tab_pos);
+ }
+ DrawTextEx(hDC, const_cast<wchar_t*>(label.data()),
+ static_cast<int>(label.size()), &rect, format | DT_LEFT, NULL);
+ if (!accel.empty())
+ DrawTextEx(hDC, const_cast<wchar_t*>(accel.data()),
+ static_cast<int>(accel.size()), &rect,
+ format | DT_RIGHT, NULL);
+ SelectObject(hDC, old_font);
+
+ // Draw the icon after the label, otherwise it would be covered
+ // by the label.
+ if (data->icon.width() != 0 && data->icon.height() != 0) {
+ ChromeCanvas canvas(data->icon.width(), data->icon.height(), false);
+ canvas.drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode);
+ canvas.DrawBitmapInt(data->icon, 0, 0);
+ canvas.getTopPlatformDevice().drawToHDC(hDC, lpdis->rcItem.left +
+ kItemLeftMargin, lpdis->rcItem.top + (lpdis->rcItem.bottom -
+ lpdis->rcItem.top - data->icon.height()) / 2, NULL);
+ }
+
+ } else {
+ // Draw the separator
+ lpdis->rcItem.top += (lpdis->rcItem.bottom - lpdis->rcItem.top) / 3;
+ DrawEdge(hDC, &lpdis->rcItem, EDGE_ETCHED, BF_TOP);
+ }
+
+ SetBkColor(hDC, prev_bg_color);
+ SetTextColor(hDC, prev_text_color);
+ }
+
+ bool FindMenuIDByLocation(Menu* menu, const CPoint& loc, int* id) {
+ int index = MenuItemFromPoint(NULL, menu->menu_, loc);
+ if (index != -1) {
+ *id = ChromeGetMenuItemID(menu->menu_, index);
+ return true;
+ } else {
+ for (std::vector<Menu*>::iterator i = menu->submenus_.begin();
+ i != menu->submenus_.end(); ++i) {
+ if (FindMenuIDByLocation(*i, loc, id))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // The menu that created us.
+ Menu* menu_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MenuHostWindow);
+};
+
+} // namespace
+
+bool Menu::Delegate::IsRightToLeftUILayout() const {
+ return l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT;
+}
+
+const SkBitmap& Menu::Delegate::GetEmptyIcon() const {
+ if (kEmptyIcon == NULL)
+ kEmptyIcon = new SkBitmap();
+ return *kEmptyIcon;
+}
+
+Menu::Menu(Delegate* delegate, AnchorPoint anchor, HWND owner)
+ : delegate_(delegate),
+ menu_(CreatePopupMenu()),
+ anchor_(anchor),
+ owner_(owner),
+ is_menu_visible_(false),
+ owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL)) {
+ DCHECK(delegate_);
+}
+
+Menu::Menu(Menu* parent)
+ : delegate_(parent->delegate_),
+ menu_(CreatePopupMenu()),
+ anchor_(parent->anchor_),
+ owner_(parent->owner_),
+ is_menu_visible_(false),
+ owner_draw_(parent->owner_draw_) {
+}
+
+Menu::Menu(HMENU hmenu)
+ : delegate_(NULL),
+ menu_(hmenu),
+ anchor_(TOPLEFT),
+ owner_(NULL),
+ is_menu_visible_(false),
+ owner_draw_(false) {
+ DCHECK(menu_);
+}
+
+Menu::~Menu() {
+ STLDeleteContainerPointers(submenus_.begin(), submenus_.end());
+ STLDeleteContainerPointers(item_data_.begin(), item_data_.end());
+ DestroyMenu(menu_);
+}
+
+UINT Menu::GetStateFlagsForItemID(int item_id) const {
+ // Use the delegate to get enabled and checked state.
+ UINT flags =
+ delegate_->IsCommandEnabled(item_id) ? MFS_ENABLED : MFS_DISABLED;
+
+ if (delegate_->IsItemChecked(item_id))
+ flags |= MFS_CHECKED;
+
+ if (delegate_->IsItemDefault(item_id))
+ flags |= MFS_DEFAULT;
+
+ return flags;
+}
+
+void Menu::AddMenuItemInternal(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon,
+ HMENU submenu,
+ MenuItemType type) {
+ DCHECK(type != SEPARATOR) << "Call AddSeparator instead!";
+
+ if (label.empty() && !delegate_) {
+ // No label and no delegate; don't add an empty menu.
+ // It appears under some circumstance we're getting an empty label
+ // (l10n_util::GetString(IDS_TASK_MANAGER) returns ""). This shouldn't
+ // happen, but I'm working over the crash here.
+ NOTREACHED();
+ return;
+ }
+
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE | MIIM_ID;
+ if (submenu) {
+ mii.fMask |= MIIM_SUBMENU;
+ mii.hSubMenu = submenu;
+ }
+
+ // Set the type and ID.
+ if (!owner_draw_) {
+ mii.fType = MFT_STRING;
+ mii.fMask |= MIIM_STRING;
+ } else {
+ mii.fType = MFT_OWNERDRAW;
+ }
+
+ if (type == RADIO)
+ mii.fType |= MFT_RADIOCHECK;
+
+ mii.wID = item_id;
+
+ // Set the item data.
+ Menu::ItemData* data = new ItemData;
+ item_data_.push_back(data);
+ data->submenu = submenu != NULL;
+
+ std::wstring actual_label(label.empty() ?
+ delegate_->GetLabel(item_id) : label);
+
+ // Find out if there is a shortcut we need to append to the label.
+ views::Accelerator accelerator(0, false, false, false);
+ if (delegate_ && delegate_->GetAcceleratorInfo(item_id, &accelerator)) {
+ actual_label += L'\t';
+ actual_label += accelerator.GetShortcutText();
+ }
+ labels_.push_back(actual_label);
+
+ if (owner_draw_) {
+ if (icon.width() != 0 && icon.height() != 0)
+ data->icon = icon;
+ else
+ data->icon = delegate_->GetIcon(item_id);
+ } else {
+ mii.dwTypeData = const_cast<wchar_t*>(labels_.back().c_str());
+ }
+
+ InsertMenuItem(menu_, index, TRUE, &mii);
+}
+
+void Menu::AppendMenuItem(int item_id,
+ const std::wstring& label,
+ MenuItemType type) {
+ AddMenuItem(-1, item_id, label, type);
+}
+
+void Menu::AddMenuItem(int index,
+ int item_id,
+ const std::wstring& label,
+ MenuItemType type) {
+ if (type == SEPARATOR)
+ AddSeparator(index);
+ else
+ AddMenuItemInternal(index, item_id, label, SkBitmap(), NULL, type);
+}
+
+Menu* Menu::AppendSubMenu(int item_id, const std::wstring& label) {
+ return AddSubMenu(-1, item_id, label);
+}
+
+Menu* Menu::AddSubMenu(int index, int item_id, const std::wstring& label) {
+ return AddSubMenuWithIcon(index, item_id, label, SkBitmap());
+}
+
+Menu* Menu::AppendSubMenuWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ return AddSubMenuWithIcon(-1, item_id, label, icon);
+}
+
+Menu* Menu::AddSubMenuWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ if (!owner_draw_ && icon.width() != 0 && icon.height() != 0)
+ owner_draw_ = true;
+
+ Menu* submenu = new Menu(this);
+ submenus_.push_back(submenu);
+ AddMenuItemInternal(index, item_id, label, icon, submenu->menu_, NORMAL);
+ return submenu;
+}
+
+void Menu::AppendMenuItemWithLabel(int item_id, const std::wstring& label) {
+ AddMenuItemWithLabel(-1, item_id, label);
+}
+
+void Menu::AddMenuItemWithLabel(int index, int item_id,
+ const std::wstring& label) {
+ AddMenuItem(index, item_id, label, Menu::NORMAL);
+}
+
+void Menu::AppendDelegateMenuItem(int item_id) {
+ AddDelegateMenuItem(-1, item_id);
+}
+
+void Menu::AddDelegateMenuItem(int index, int item_id) {
+ AddMenuItem(index, item_id, std::wstring(), Menu::NORMAL);
+}
+
+void Menu::AppendSeparator() {
+ AddSeparator(-1);
+}
+
+void Menu::AddSeparator(int index) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(menu_, index, TRUE, &mii);
+}
+
+void Menu::AppendMenuItemWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ AddMenuItemWithIcon(-1, item_id, label, icon);
+}
+
+void Menu::AddMenuItemWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon) {
+ if (!owner_draw_)
+ owner_draw_ = true;
+ AddMenuItemInternal(index, item_id, label, icon, NULL, Menu::NORMAL);
+}
+
+void Menu::EnableMenuItemByID(int item_id, bool enabled) {
+ UINT enable_flags = enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
+ EnableMenuItem(menu_, item_id, MF_BYCOMMAND | enable_flags);
+}
+
+void Menu::EnableMenuItemAt(int index, bool enabled) {
+ UINT enable_flags = enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
+ EnableMenuItem(menu_, index, MF_BYPOSITION | enable_flags);
+}
+
+void Menu::SetMenuLabel(int item_id, const std::wstring& label) {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_STRING;
+ mii.dwTypeData = const_cast<wchar_t*>(label.c_str());
+ mii.cch = static_cast<UINT>(label.size());
+ SetMenuItemInfo(menu_, item_id, false, &mii);
+}
+
+DWORD Menu::GetTPMAlignFlags() const {
+ // The manner in which we handle the menu alignment depends on whether or not
+ // the menu is displayed within a mirrored view. If the UI is mirrored, the
+ // alignment needs to be fliped so that instead of aligning the menu to the
+ // right of the point, we align it to the left and vice versa.
+ DWORD align_flags = TPM_TOPALIGN;
+ switch (anchor_) {
+ case TOPLEFT:
+ if (delegate_->IsRightToLeftUILayout()) {
+ align_flags |= TPM_RIGHTALIGN;
+ } else {
+ align_flags |= TPM_LEFTALIGN;
+ }
+ break;
+
+ case TOPRIGHT:
+ if (delegate_->IsRightToLeftUILayout()) {
+ align_flags |= TPM_LEFTALIGN;
+ } else {
+ align_flags |= TPM_RIGHTALIGN;
+ }
+ break;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+ return align_flags;
+}
+
+bool Menu::SetIcon(const SkBitmap& icon, int item_id) {
+ if (!owner_draw_)
+ owner_draw_ = true;
+
+ const int num_items = GetMenuItemCount(menu_);
+ int sep_count = 0;
+ for (int i = 0; i < num_items; ++i) {
+ if (!(GetMenuState(menu_, i, MF_BYPOSITION) & MF_SEPARATOR)) {
+ if (ChromeGetMenuItemID(menu_, i) == item_id) {
+ item_data_[i - sep_count]->icon = icon;
+ // When the menu is running, we use SetMenuItemInfo to let Windows
+ // update the item information so that the icon being displayed
+ // could change immediately.
+ if (active_host_window) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE | MIIM_DATA;
+ mii.fType = MFT_OWNERDRAW;
+ mii.dwItemData =
+ reinterpret_cast<ULONG_PTR>(item_data_[i - sep_count]);
+ SetMenuItemInfo(menu_, item_id, false, &mii);
+ }
+ return true;
+ }
+ } else {
+ ++sep_count;
+ }
+ }
+
+ // Continue searching for the item in submenus.
+ for (size_t i = 0; i < submenus_.size(); ++i) {
+ if (submenus_[i]->SetIcon(icon, item_id))
+ return true;
+ }
+
+ return false;
+}
+
+void Menu::SetMenuInfo() {
+ const int num_items = GetMenuItemCount(menu_);
+ int sep_count = 0;
+ for (int i = 0; i < num_items; ++i) {
+ MENUITEMINFO mii_info;
+ mii_info.cbSize = sizeof(mii_info);
+ // Get the menu's original type.
+ mii_info.fMask = MIIM_FTYPE;
+ GetMenuItemInfo(menu_, i, MF_BYPOSITION, &mii_info);
+ // Set item states.
+ if (!(mii_info.fType & MF_SEPARATOR)) {
+ const int id = ChromeGetMenuItemID(menu_, i);
+
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_STATE | MIIM_FTYPE | MIIM_DATA | MIIM_STRING;
+ // We also need MFT_STRING for owner drawn items in order to let Windows
+ // handle the accelerators for us.
+ mii.fType = MFT_STRING;
+ if (owner_draw_)
+ mii.fType |= MFT_OWNERDRAW;
+ // If the menu originally has radiocheck type, we should follow it.
+ if (mii_info.fType & MFT_RADIOCHECK)
+ mii.fType |= MFT_RADIOCHECK;
+ mii.fState = GetStateFlagsForItemID(id);
+
+ // Validate the label. If there is a contextual label, use it, otherwise
+ // default to the static label
+ std::wstring label;
+ if (!delegate_->GetContextualLabel(id, &label))
+ label = labels_[i - sep_count];
+
+ if (owner_draw_) {
+ item_data_[i - sep_count]->label = label;
+ mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data_[i - sep_count]);
+ }
+ mii.dwTypeData = const_cast<wchar_t*>(label.c_str());
+ mii.cch = static_cast<UINT>(label.size());
+ SetMenuItemInfo(menu_, i, true, &mii);
+ } else {
+ // Set data for owner drawn separators. Set dwItemData NULL to indicate
+ // a separator.
+ if (owner_draw_) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR | MFT_OWNERDRAW;
+ mii.dwItemData = NULL;
+ SetMenuItemInfo(menu_, i, true, &mii);
+ }
+ ++sep_count;
+ }
+ }
+
+ for (size_t i = 0; i < submenus_.size(); ++i)
+ submenus_[i]->SetMenuInfo();
+}
+
+void Menu::RunMenuAt(int x, int y) {
+ SetMenuInfo();
+
+ delegate_->MenuWillShow();
+
+ // NOTE: we don't use TPM_RIGHTBUTTON here as it breaks selecting by way of
+ // press, drag, release. See bugs 718 and 8560.
+ UINT flags =
+ GetTPMAlignFlags() | TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE;
+ is_menu_visible_ = true;
+ DCHECK(owner_);
+ // In order for context menus on menus to work, the context menu needs to
+ // share the same window as the first menu is parented to.
+ bool created_host = false;
+ if (!active_host_window) {
+ created_host = true;
+ active_host_window = new MenuHostWindow(this, owner_);
+ }
+ UINT selected_id =
+ TrackPopupMenuEx(menu_, flags, x, y, active_host_window->m_hWnd, NULL);
+ if (created_host) {
+ delete active_host_window;
+ active_host_window = NULL;
+ }
+ is_menu_visible_ = false;
+
+ // Execute the chosen command
+ if (selected_id != 0)
+ delegate_->ExecuteCommand(selected_id);
+}
+
+void Menu::Cancel() {
+ DCHECK(is_menu_visible_);
+ EndMenu();
+}
+
+int Menu::ItemCount() {
+ return GetMenuItemCount(menu_);
+}
diff --git a/views/controls/menu/menu.h b/views/controls/menu/menu.h
new file mode 100644
index 0000000..0be9126
--- /dev/null
+++ b/views/controls/menu/menu.h
@@ -0,0 +1,355 @@
+// 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.
+
+#ifndef CONTROLS_MENU_VIEWS_MENU_H_
+#define CONTROLS_MENU_VIEWS_MENU_H_
+
+#include <windows.h>
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "views/controls/menu/controller.h"
+
+class SkBitmap;
+
+namespace {
+class MenuHostWindow;
+}
+
+namespace views {
+class Accelerator;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Menu class
+//
+// A wrapper around a Win32 HMENU handle that provides convenient APIs for
+// menu construction, display and subsequent command execution.
+//
+///////////////////////////////////////////////////////////////////////////////
+class Menu {
+ friend class MenuHostWindow;
+
+ public:
+ /////////////////////////////////////////////////////////////////////////////
+ //
+ // Delegate Interface
+ //
+ // Classes implement this interface to tell the menu system more about each
+ // item as it is created.
+ //
+ /////////////////////////////////////////////////////////////////////////////
+ class Delegate : public Controller {
+ public:
+ virtual ~Delegate() { }
+
+ // Whether or not an item should be shown as checked.
+ virtual bool IsItemChecked(int id) const {
+ return false;
+ }
+
+ // Whether or not an item should be shown as the default (using bold).
+ // There can only be one default menu item.
+ virtual bool IsItemDefault(int id) const {
+ return false;
+ }
+
+ // The string shown for the menu item.
+ virtual std::wstring GetLabel(int id) const {
+ return std::wstring();
+ }
+
+ // The delegate needs to implement this function if it wants to display
+ // the shortcut text next to each menu item. If there is an accelerator
+ // for a given item id, the implementor must return it.
+ virtual bool GetAcceleratorInfo(int id, views::Accelerator* accel) {
+ return false;
+ }
+
+ // The icon shown for the menu item.
+ virtual const SkBitmap& GetIcon(int id) const {
+ return GetEmptyIcon();
+ }
+
+ // The number of items to show in the menu
+ virtual int GetItemCount() const {
+ return 0;
+ }
+
+ // Whether or not an item is a separator.
+ virtual bool IsItemSeparator(int id) const {
+ return false;
+ }
+
+ // Shows the context menu with the specified id. This is invoked when the
+ // user does the appropriate gesture to show a context menu. The id
+ // identifies the id of the menu to show the context menu for.
+ // is_mouse_gesture is true if this is the result of a mouse gesture.
+ // If this is not the result of a mouse gesture x/y is the recommended
+ // location to display the content menu at. In either case, x/y is in
+ // screen coordinates.
+ virtual void ShowContextMenu(Menu* source,
+ int id,
+ int x,
+ int y,
+ bool is_mouse_gesture) {
+ }
+
+ // Whether an item has an icon.
+ virtual bool HasIcon(int id) const {
+ return false;
+ }
+
+ // Notification that the menu is about to be popped up.
+ virtual void MenuWillShow() {
+ }
+
+ // Whether to create a right-to-left menu. The default implementation
+ // returns true if the locale's language is a right-to-left language (such
+ // as Hebrew) and false otherwise. This is generally the right behavior
+ // since there is no reason to show left-to-right menus for right-to-left
+ // locales. However, subclasses can override this behavior so that the menu
+ // is a right-to-left menu only if the view's layout is right-to-left
+ // (since the view can use a different layout than the locale's language
+ // layout).
+ virtual bool IsRightToLeftUILayout() const;
+
+ // Controller
+ virtual bool SupportsCommand(int id) const {
+ return true;
+ }
+ virtual bool IsCommandEnabled(int id) const {
+ return true;
+ }
+ virtual bool GetContextualLabel(int id, std::wstring* out) const {
+ return false;
+ }
+ virtual void ExecuteCommand(int id) {
+ }
+
+ protected:
+ // Returns an empty icon. Will initialize kEmptyIcon if it hasn't been
+ // initialized.
+ const SkBitmap& GetEmptyIcon() const;
+
+ private:
+ // Will be initialized to an icon of 0 width and 0 height when first using.
+ // An empty icon means we don't need to draw it.
+ static const SkBitmap* kEmptyIcon;
+ };
+
+ // This class is a helper that simply wraps a controller and forwards all
+ // state and execution actions to it. Use this when you're not defining your
+ // own custom delegate, but just hooking a context menu to some existing
+ // controller elsewhere.
+ class BaseControllerDelegate : public Delegate {
+ public:
+ explicit BaseControllerDelegate(Controller* wrapped)
+ : controller_(wrapped) {
+ }
+
+ // Overridden from Menu::Delegate
+ virtual bool SupportsCommand(int id) const {
+ return controller_->SupportsCommand(id);
+ }
+ virtual bool IsCommandEnabled(int id) const {
+ return controller_->IsCommandEnabled(id);
+ }
+ virtual void ExecuteCommand(int id) {
+ controller_->ExecuteCommand(id);
+ }
+ virtual bool GetContextualLabel(int id, std::wstring* out) const {
+ return controller_->GetContextualLabel(id, out);
+ }
+
+ private:
+ // The internal controller that we wrap to forward state and execution
+ // actions to.
+ Controller* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseControllerDelegate);
+ };
+
+ // How this popup should align itself relative to the point it is run at.
+ enum AnchorPoint {
+ TOPLEFT,
+ TOPRIGHT
+ };
+
+ // Different types of menu items
+ enum MenuItemType {
+ NORMAL,
+ CHECKBOX,
+ RADIO,
+ SEPARATOR
+ };
+
+ // Construct a Menu using the specified controller to determine command
+ // state.
+ // delegate A Menu::Delegate implementation that provides more
+ // information about the Menu presentation.
+ // anchor An alignment hint for the popup menu.
+ // owner The window that the menu is being brought up relative
+ // to. Not actually used for anything but must not be
+ // NULL.
+ Menu(Delegate* delegate, AnchorPoint anchor, HWND owner);
+ // Alternatively, a Menu object can be constructed wrapping an existing
+ // HMENU. This can be used to use the convenience methods to insert
+ // menu items and manage label string ownership. However this kind of
+ // Menu object cannot use the delegate.
+ explicit Menu(HMENU hmenu);
+ virtual ~Menu();
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Adds an item to this menu.
+ // item_id The id of the item, used to identify it in delegate callbacks
+ // or (if delegate is NULL) to identify the command associated
+ // with this item with the controller specified in the ctor. Note
+ // that this value should not be 0 as this has a special meaning
+ // ("NULL command, no item selected")
+ // label The text label shown.
+ // type The type of item.
+ void AppendMenuItem(int item_id,
+ const std::wstring& label,
+ MenuItemType type);
+ void AddMenuItem(int index,
+ int item_id,
+ const std::wstring& label,
+ MenuItemType type);
+
+ // Append a submenu to this menu.
+ // The returned pointer is owned by this menu.
+ Menu* AppendSubMenu(int item_id,
+ const std::wstring& label);
+ Menu* AddSubMenu(int index, int item_id, const std::wstring& label);
+
+ // Append a submenu with an icon to this menu
+ // The returned pointer is owned by this menu.
+ // Unless the icon is empty, calling this function forces the Menu class
+ // to draw the menu, instead of relying on Windows.
+ Menu* AppendSubMenuWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon);
+ Menu* AddSubMenuWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon);
+
+ // This is a convenience for standard text label menu items where the label
+ // is provided with this call.
+ void AppendMenuItemWithLabel(int item_id, const std::wstring& label);
+ void AddMenuItemWithLabel(int index, int item_id, const std::wstring& label);
+
+ // This is a convenience for text label menu items where the label is
+ // provided by the delegate.
+ void AppendDelegateMenuItem(int item_id);
+ void AddDelegateMenuItem(int index, int item_id);
+
+ // Adds a separator to this menu
+ void AppendSeparator();
+ void AddSeparator(int index);
+
+ // Appends a menu item with an icon. This is for the menu item which
+ // needs an icon. Calling this function forces the Menu class to draw
+ // the menu, instead of relying on Windows.
+ void AppendMenuItemWithIcon(int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon);
+ void AddMenuItemWithIcon(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon);
+
+ // Enables or disables the item with the specified id.
+ void EnableMenuItemByID(int item_id, bool enabled);
+ void EnableMenuItemAt(int index, bool enabled);
+
+ // Sets menu label at specified index.
+ void SetMenuLabel(int item_id, const std::wstring& label);
+
+ // Sets an icon for an item with a given item_id. Calling this function
+ // also forces the Menu class to draw the menu, instead of relying on Windows.
+ // Returns false if the item with |item_id| is not found.
+ bool SetIcon(const SkBitmap& icon, int item_id);
+
+ // Shows the menu, blocks until the user dismisses the menu or selects an
+ // item, and executes the command for the selected item (if any).
+ // Warning: Blocking call. Will implicitly run a message loop.
+ void RunMenuAt(int x, int y);
+
+ // Cancels the menu.
+ virtual void Cancel();
+
+ // Returns the number of menu items.
+ int ItemCount();
+
+ protected:
+ // The delegate that is being used to get information about the presentation.
+ Delegate* delegate_;
+
+ private:
+ // The data of menu items needed to display.
+ struct ItemData;
+
+ explicit Menu(Menu* parent);
+
+ void AddMenuItemInternal(int index,
+ int item_id,
+ const std::wstring& label,
+ const SkBitmap& icon,
+ HMENU submenu,
+ MenuItemType type);
+
+ // Sets menu information before displaying, including sub-menus.
+ void SetMenuInfo();
+
+ // Get all the state flags for the |fState| field of MENUITEMINFO for the
+ // item with the specified id. |delegate| is consulted if non-NULL about
+ // the state of the item in preference to |controller_|.
+ UINT GetStateFlagsForItemID(int item_id) const;
+
+ // Gets the Win32 TPM alignment flags for the specified AnchorPoint.
+ DWORD GetTPMAlignFlags() const;
+
+ // The Win32 Menu Handle we wrap
+ HMENU menu_;
+
+ // The window that would receive WM_COMMAND messages when the user selects
+ // an item from the menu.
+ HWND owner_;
+
+ // This list is used to store the default labels for the menu items.
+ // We may use contextual labels when RunMenu is called, so we must save
+ // a copy of default ones here.
+ std::vector<std::wstring> labels_;
+
+ // A flag to indicate whether this menu will be drawn by the Menu class.
+ // If it's true, all the menu items will be owner drawn. Otherwise,
+ // all the drawing will be done by Windows.
+ bool owner_draw_;
+
+ // How this popup menu should be aligned relative to the point it is run at.
+ AnchorPoint anchor_;
+
+ // This list is to store the string labels and icons to display. It's used
+ // when owner_draw_ is true. We give MENUITEMINFO pointers to these
+ // structures to specify what we'd like to draw. If owner_draw_ is false,
+ // we only give MENUITEMINFO pointers to the labels_.
+ // The label member of the ItemData structure comes from either labels_ or
+ // the GetContextualLabel.
+ std::vector<ItemData*> item_data_;
+
+ // Our sub-menus, if any.
+ std::vector<Menu*> submenus_;
+
+ // Whether the menu is visible.
+ bool is_menu_visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(Menu);
+};
+
+#endif // CONTROLS_MENU_VIEWS_MENU_H_
diff --git a/views/controls/menu/view_menu_delegate.h b/views/controls/menu/view_menu_delegate.h
new file mode 100644
index 0000000..bb72ed3
--- /dev/null
+++ b/views/controls/menu/view_menu_delegate.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+#define VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+
+#include "base/gfx/native_widget_types.h"
+
+namespace views {
+
+class View;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ViewMenuDelegate
+//
+// An interface that allows a component to tell a View about a menu that it
+// has constructed that the view can show (e.g. for MenuButton views, or as a
+// context menu.)
+//
+////////////////////////////////////////////////////////////////////////////////
+class ViewMenuDelegate {
+ public:
+ // Create and show a menu at the specified position. Source is the view the
+ // ViewMenuDelegate was set on.
+ virtual void RunMenu(View* source,
+ const CPoint& pt,
+ gfx::NativeView hwnd) = 0;
+};
+
+} // namespace views
+
+#endif // VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_