summaryrefslogtreecommitdiffstats
path: root/ui/views
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-28 15:33:33 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-28 15:33:33 +0000
commit5e554e8fbf652534b50c89e2ffafaeb96161cc80 (patch)
tree26750feec0b3e886ee55f5b8b3eee37414aa1860 /ui/views
parent23b8a3ca4a6f95b5f9e8fd9ae0553ad141b8e991 (diff)
downloadchromium_src-5e554e8fbf652534b50c89e2ffafaeb96161cc80.zip
chromium_src-5e554e8fbf652534b50c89e2ffafaeb96161cc80.tar.gz
chromium_src-5e554e8fbf652534b50c89e2ffafaeb96161cc80.tar.bz2
views: Move menu directory to ui/views/controls/.
BUG=104039 R=maruel@chromium.org TBR=ben@chromium.org Review URL: http://codereview.chromium.org/8718004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111701 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/views')
-rw-r--r--ui/views/controls/button/button_dropdown.cc6
-rw-r--r--ui/views/controls/button/menu_button.cc2
-rw-r--r--ui/views/controls/combobox/native_combobox_views.cc4
-rw-r--r--ui/views/controls/combobox/native_combobox_views.h2
-rw-r--r--ui/views/controls/menu/menu.cc102
-rw-r--r--ui/views/controls/menu/menu.h277
-rw-r--r--ui/views/controls/menu/menu_2.cc61
-rw-r--r--ui/views/controls/menu/menu_2.h100
-rw-r--r--ui/views/controls/menu/menu_config.cc53
-rw-r--r--ui/views/controls/menu/menu_config.h102
-rw-r--r--ui/views/controls/menu/menu_config_aura.cc25
-rw-r--r--ui/views/controls/menu/menu_config_linux.cc25
-rw-r--r--ui/views/controls/menu/menu_config_win.cc104
-rw-r--r--ui/views/controls/menu/menu_controller.cc1955
-rw-r--r--ui/views/controls/menu/menu_controller.h513
-rw-r--r--ui/views/controls/menu/menu_controller_delegate.h41
-rw-r--r--ui/views/controls/menu/menu_delegate.cc127
-rw-r--r--ui/views/controls/menu/menu_delegate.h209
-rw-r--r--ui/views/controls/menu/menu_gtk.cc82
-rw-r--r--ui/views/controls/menu/menu_gtk.h54
-rw-r--r--ui/views/controls/menu/menu_host.cc110
-rw-r--r--ui/views/controls/menu/menu_host.h79
-rw-r--r--ui/views/controls/menu/menu_host_root_view.cc69
-rw-r--r--ui/views/controls/menu/menu_host_root_view.h50
-rw-r--r--ui/views/controls/menu/menu_image_util.cc116
-rw-r--r--ui/views/controls/menu/menu_image_util.h24
-rw-r--r--ui/views/controls/menu/menu_item_view.cc779
-rw-r--r--ui/views/controls/menu/menu_item_view.h473
-rw-r--r--ui/views/controls/menu/menu_item_view_aura.cc107
-rw-r--r--ui/views/controls/menu/menu_item_view_linux.cc116
-rw-r--r--ui/views/controls/menu/menu_item_view_win.cc171
-rw-r--r--ui/views/controls/menu/menu_listener.h25
-rw-r--r--ui/views/controls/menu/menu_model_adapter.cc200
-rw-r--r--ui/views/controls/menu/menu_model_adapter.h80
-rw-r--r--ui/views/controls/menu/menu_model_adapter_unittest.cc307
-rw-r--r--ui/views/controls/menu/menu_runner.cc275
-rw-r--r--ui/views/controls/menu/menu_runner.h99
-rw-r--r--ui/views/controls/menu/menu_scroll_view_container.cc247
-rw-r--r--ui/views/controls/menu/menu_scroll_view_container.h54
-rw-r--r--ui/views/controls/menu/menu_separator.h28
-rw-r--r--ui/views/controls/menu/menu_separator_aura.cc24
-rw-r--r--ui/views/controls/menu/menu_separator_linux.cc24
-rw-r--r--ui/views/controls/menu/menu_separator_win.cc48
-rw-r--r--ui/views/controls/menu/menu_win.cc574
-rw-r--r--ui/views/controls/menu/menu_win.h140
-rw-r--r--ui/views/controls/menu/menu_wrapper.h71
-rw-r--r--ui/views/controls/menu/native_menu_gtk.cc628
-rw-r--r--ui/views/controls/menu/native_menu_gtk.h162
-rw-r--r--ui/views/controls/menu/native_menu_host.h34
-rw-r--r--ui/views/controls/menu/native_menu_host_delegate.h32
-rw-r--r--ui/views/controls/menu/native_menu_views.cc171
-rw-r--r--ui/views/controls/menu/native_menu_views.h62
-rw-r--r--ui/views/controls/menu/native_menu_win.cc765
-rw-r--r--ui/views/controls/menu/native_menu_win.h184
-rw-r--r--ui/views/controls/menu/nested_dispatcher_gtk.cc35
-rw-r--r--ui/views/controls/menu/nested_dispatcher_gtk.h51
-rw-r--r--ui/views/controls/menu/submenu_view.cc398
-rw-r--r--ui/views/controls/menu/submenu_view.h204
-rw-r--r--ui/views/controls/menu/view_menu_delegate.h40
-rw-r--r--ui/views/controls/scrollbar/base_scroll_bar.cc4
-rw-r--r--ui/views/controls/scrollbar/base_scroll_bar.h2
-rw-r--r--ui/views/controls/scrollbar/bitmap_scroll_bar.cc2
-rw-r--r--ui/views/controls/textfield/native_textfield_views.cc6
-rw-r--r--ui/views/controls/textfield/native_textfield_win.cc4
-rw-r--r--ui/views/examples/menu_example.cc4
-rw-r--r--ui/views/touchui/touch_selection_controller_impl.cc2
-rw-r--r--ui/views/widget/widget.cc2
67 files changed, 10906 insertions, 20 deletions
diff --git a/ui/views/controls/button/button_dropdown.cc b/ui/views/controls/button/button_dropdown.cc
index e344271..a9cbb94 100644
--- a/ui/views/controls/button/button_dropdown.cc
+++ b/ui/views/controls/button/button_dropdown.cc
@@ -12,10 +12,10 @@
#include "ui/base/accessibility/accessible_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/menu_model.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/widget/widget.h"
-#include "views/controls/menu/menu_item_view.h"
-#include "views/controls/menu/menu_model_adapter.h"
-#include "views/controls/menu/menu_runner.h"
namespace views {
diff --git a/ui/views/controls/button/menu_button.cc b/ui/views/controls/button/menu_button.cc
index 8a61cbb..56ebbea 100644
--- a/ui/views/controls/button/menu_button.cc
+++ b/ui/views/controls/button/menu_button.cc
@@ -14,10 +14,10 @@
#include "ui/gfx/canvas.h"
#include "ui/gfx/screen.h"
#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/menu/view_menu_delegate.h"
#include "ui/views/events/event.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
-#include "views/controls/menu/view_menu_delegate.h"
using base::Time;
using base::TimeDelta;
diff --git a/ui/views/controls/combobox/native_combobox_views.cc b/ui/views/controls/combobox/native_combobox_views.cc
index 1e8e535..4c3bc63 100644
--- a/ui/views/controls/combobox/native_combobox_views.cc
+++ b/ui/views/controls/combobox/native_combobox_views.cc
@@ -16,13 +16,13 @@
#include "ui/gfx/font.h"
#include "ui/gfx/path.h"
#include "ui/views/controls/combobox/combobox.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#include "views/background.h"
#include "views/border.h"
#include "views/controls/focusable_border.h"
-#include "views/controls/menu/menu_runner.h"
-#include "views/controls/menu/submenu_view.h"
#if defined(OS_LINUX)
#include "ui/gfx/gtk_util.h"
diff --git a/ui/views/controls/combobox/native_combobox_views.h b/ui/views/controls/combobox/native_combobox_views.h
index 571b70b..784539e 100644
--- a/ui/views/controls/combobox/native_combobox_views.h
+++ b/ui/views/controls/combobox/native_combobox_views.h
@@ -7,7 +7,7 @@
#pragma once
#include "ui/views/controls/combobox/native_combobox_wrapper.h"
-#include "views/controls/menu/menu_delegate.h"
+#include "ui/views/controls/menu/menu_delegate.h"
#include "views/view.h"
namespace gfx {
diff --git a/ui/views/controls/menu/menu.cc b/ui/views/controls/menu/menu.cc
new file mode 100644
index 0000000..2607cde
--- /dev/null
+++ b/ui/views/controls/menu/menu.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu.h"
+
+#include "base/i18n/rtl.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace views {
+
+bool Menu::Delegate::IsRightToLeftUILayout() const {
+ return base::i18n::IsRTL();
+}
+
+const SkBitmap& Menu::Delegate::GetEmptyIcon() const {
+ static const SkBitmap* empty_icon = new SkBitmap();
+ return *empty_icon;
+}
+
+Menu::Menu(Delegate* delegate, AnchorPoint anchor)
+ : delegate_(delegate),
+ anchor_(anchor) {
+}
+
+Menu::Menu(Menu* parent)
+ : delegate_(parent->delegate_),
+ anchor_(parent->anchor_) {
+}
+
+Menu::~Menu() {
+}
+
+void Menu::AppendMenuItem(int item_id,
+ const string16& label,
+ MenuItemType type) {
+ AddMenuItem(-1, item_id, label, type);
+}
+
+void Menu::AddMenuItem(int index,
+ int item_id,
+ const string16& label,
+ MenuItemType type) {
+ if (type == SEPARATOR)
+ AddSeparator(index);
+ else
+ AddMenuItemInternal(index, item_id, label, SkBitmap(), type);
+}
+
+Menu* Menu::AppendSubMenu(int item_id, const string16& label) {
+ return AddSubMenu(-1, item_id, label);
+}
+
+Menu* Menu::AddSubMenu(int index, int item_id, const string16& label) {
+ return AddSubMenuWithIcon(index, item_id, label, SkBitmap());
+}
+
+Menu* Menu::AppendSubMenuWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ return AddSubMenuWithIcon(-1, item_id, label, icon);
+}
+
+void Menu::AppendMenuItemWithLabel(int item_id, const string16& label) {
+ AddMenuItemWithLabel(-1, item_id, label);
+}
+
+void Menu::AddMenuItemWithLabel(int index,
+ int item_id,
+ const string16& 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, string16(), Menu::NORMAL);
+}
+
+void Menu::AppendSeparator() {
+ AddSeparator(-1);
+}
+
+void Menu::AppendMenuItemWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ AddMenuItemWithIcon(-1, item_id, label, icon);
+}
+
+void Menu::AddMenuItemWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ AddMenuItemInternal(index, item_id, label, icon, Menu::NORMAL);
+}
+
+Menu::Menu() : delegate_(NULL), anchor_(TOPLEFT) {
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu.h b/ui/views/controls/menu/menu.h
new file mode 100644
index 0000000..ed20389
--- /dev/null
+++ b/ui/views/controls/menu/menu.h
@@ -0,0 +1,277 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+
+class SkBitmap;
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+class Accelerator;
+}
+
+namespace views {
+
+class VIEWS_EXPORT Menu {
+ public:
+ /////////////////////////////////////////////////////////////////////////////
+ //
+ // Delegate Interface
+ //
+ // Classes implement this interface to tell the menu system more about each
+ // item as it is created.
+ //
+ /////////////////////////////////////////////////////////////////////////////
+ class VIEWS_EXPORT Delegate {
+ 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 string16 GetLabel(int id) const {
+ return string16();
+ }
+
+ // 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, ui::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 |p| is the recommended
+ // location to display the content menu at. In either case, |p| is in
+ // screen coordinates.
+ virtual void ShowContextMenu(Menu* source,
+ int id,
+ const gfx::Point& p,
+ 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, string16* out) const {
+ return false;
+ }
+ virtual void ExecuteCommand(int id) {
+ }
+
+ protected:
+ // Returns an empty icon.
+ const SkBitmap& GetEmptyIcon() const;
+ };
+
+ // 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);
+ Menu();
+ virtual ~Menu();
+
+ static Menu* Create(Delegate* delegate,
+ AnchorPoint anchor,
+ gfx::NativeView parent);
+
+ // Creates a new menu with the contents of the system menu for the given
+ // parent window. The caller owns the returned pointer.
+ static Menu* GetSystemMenu(gfx::NativeWindow parent);
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+ Delegate* delegate() const { return delegate_; }
+
+ AnchorPoint anchor() const { return anchor_; }
+
+ // 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 string16& label,
+ MenuItemType type);
+ void AddMenuItem(int index,
+ int item_id,
+ const string16& label,
+ MenuItemType type);
+
+ // Append a submenu to this menu.
+ // The returned pointer is owned by this menu.
+ Menu* AppendSubMenu(int item_id,
+ const string16& label);
+ Menu* AddSubMenu(int index, int item_id, const string16& 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 string16& label,
+ const SkBitmap& icon);
+ virtual Menu* AddSubMenuWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) = 0;
+
+ // This is a convenience for standard text label menu items where the label
+ // is provided with this call.
+ void AppendMenuItemWithLabel(int item_id, const string16& label);
+ void AddMenuItemWithLabel(int index, int item_id, const string16& 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();
+ virtual void AddSeparator(int index) = 0;
+
+ // 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 string16& label,
+ const SkBitmap& icon);
+ virtual void AddMenuItemWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon);
+
+ // Enables or disables the item with the specified id.
+ virtual void EnableMenuItemByID(int item_id, bool enabled) = 0;
+ virtual void EnableMenuItemAt(int index, bool enabled) = 0;
+
+ // Sets menu label at specified index.
+ virtual void SetMenuLabel(int item_id, const string16& label) = 0;
+
+ // 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.
+ virtual bool SetIcon(const SkBitmap& icon, int item_id) = 0;
+
+ // 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.
+ virtual void RunMenuAt(int x, int y) = 0;
+
+ // Cancels the menu.
+ virtual void Cancel() = 0;
+
+ // Returns the number of menu items.
+ virtual int ItemCount() = 0;
+
+#if defined(OS_WIN)
+ // Returns the underlying menu handle
+ virtual HMENU GetMenuHandle() const = 0;
+#endif // defined(OS_WIN)
+
+ protected:
+ explicit Menu(Menu* parent);
+
+ virtual void AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ MenuItemType type) = 0;
+
+ private:
+ // The delegate that is being used to get information about the presentation.
+ Delegate* delegate_;
+
+ // How this popup menu should be aligned relative to the point it is run at.
+ AnchorPoint anchor_;
+
+ DISALLOW_COPY_AND_ASSIGN(Menu);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_H_
diff --git a/ui/views/controls/menu/menu_2.cc b/ui/views/controls/menu/menu_2.cc
new file mode 100644
index 0000000..d9aad1e
--- /dev/null
+++ b/ui/views/controls/menu/menu_2.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_2.h"
+
+#include "ui/base/models/menu_model.h"
+#include "ui/views/controls/menu/menu_listener.h"
+
+namespace views {
+
+Menu2::Menu2(ui::MenuModel* model)
+ : model_(model),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ wrapper_(MenuWrapper::CreateWrapper(this))) {
+ Rebuild();
+}
+
+Menu2::~Menu2() {}
+
+gfx::NativeMenu Menu2::GetNativeMenu() const {
+ return wrapper_->GetNativeMenu();
+}
+
+void Menu2::RunMenuAt(const gfx::Point& point, Alignment alignment) {
+ wrapper_->RunMenuAt(point, alignment);
+}
+
+void Menu2::RunContextMenuAt(const gfx::Point& point) {
+ RunMenuAt(point, ALIGN_TOPLEFT);
+}
+
+void Menu2::CancelMenu() {
+ wrapper_->CancelMenu();
+}
+
+void Menu2::Rebuild() {
+ wrapper_->Rebuild();
+}
+
+void Menu2::UpdateStates() {
+ wrapper_->UpdateStates();
+}
+
+MenuWrapper::MenuAction Menu2::GetMenuAction() const {
+ return wrapper_->GetMenuAction();
+}
+
+void Menu2::AddMenuListener(MenuListener* listener) {
+ wrapper_->AddMenuListener(listener);
+}
+
+void Menu2::RemoveMenuListener(MenuListener* listener) {
+ wrapper_->RemoveMenuListener(listener);
+}
+
+void Menu2::SetMinimumWidth(int width) {
+ wrapper_->SetMinimumWidth(width);
+}
+
+} // namespace
diff --git a/ui/views/controls/menu/menu_2.h b/ui/views/controls/menu/menu_2.h
new file mode 100644
index 0000000..898596e3
--- /dev/null
+++ b/ui/views/controls/menu/menu_2.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_2_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_2_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/controls/menu/menu_wrapper.h"
+#include "views/views_export.h"
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+
+class NativeMenuGtk;
+
+// A menu. Populated from a model, and relies on a delegate to execute commands.
+//
+// WARNING: do NOT create and use Menu2 on the stack. Menu2 notifies the model
+// of selection AFTER a delay. This means that if use a Menu2 on the stack
+// ActivatedAt is never invoked.
+class VIEWS_EXPORT Menu2 {
+ public:
+ // How the menu is aligned relative to the point it is shown at.
+ // The alignment is reversed by menu if text direction is right to left.
+ enum Alignment {
+ ALIGN_TOPLEFT,
+ ALIGN_TOPRIGHT
+ };
+
+ // Creates a new menu populated with the contents of |model|.
+ // WARNING: this populates the menu on construction by invoking methods on
+ // the model. As such, it is typically not safe to use this as the model
+ // from the constructor. EG:
+ // MyClass : menu_(this) {}
+ // is likely to have problems.
+ explicit Menu2(ui::MenuModel* model);
+ virtual ~Menu2();
+
+ // Runs the menu at the specified point. This method blocks until done.
+ // RunContextMenuAt is the same, but the alignment is the default for a
+ // context menu.
+ void RunMenuAt(const gfx::Point& point, Alignment alignment);
+ void RunContextMenuAt(const gfx::Point& point);
+
+ // Cancels the active menu.
+ void CancelMenu();
+
+ // Called when the model supplying data to this menu has changed, and the menu
+ // must be rebuilt.
+ void Rebuild();
+
+ // Called when the states of the menu items in the menu should be refreshed
+ // from the model.
+ void UpdateStates();
+
+ // For submenus.
+ gfx::NativeMenu GetNativeMenu() const;
+
+ // Get the result of the last call to RunMenuAt to determine whether an
+ // item was selected, the user navigated to a next or previous menu, or
+ // nothing.
+ MenuWrapper::MenuAction GetMenuAction() const;
+
+ // Add a listener to receive a callback when the menu opens.
+ void AddMenuListener(MenuListener* listener);
+
+ // Remove a menu listener.
+ void RemoveMenuListener(MenuListener* listener);
+
+ // Accessors.
+ ui::MenuModel* model() const { return model_; }
+
+ // Sets the minimum width of the menu.
+ void SetMinimumWidth(int width);
+
+ private:
+ friend class NativeMenuGtk;
+
+ ui::MenuModel* model_;
+
+ // The object that actually implements the menu.
+ scoped_ptr<MenuWrapper> wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(Menu2);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_2_H_
diff --git a/ui/views/controls/menu/menu_config.cc b/ui/views/controls/menu/menu_config.cc
new file mode 100644
index 0000000..8fd9d0b
--- /dev/null
+++ b/ui/views/controls/menu/menu_config.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_config.h"
+
+#include "build/build_config.h"
+
+namespace views {
+
+static MenuConfig* config_instance = NULL;
+
+MenuConfig::MenuConfig()
+ : text_color(SK_ColorBLACK),
+ item_top_margin(3),
+ item_bottom_margin(4),
+ item_no_icon_top_margin(1),
+ item_no_icon_bottom_margin(3),
+ item_left_margin(4),
+ label_to_arrow_padding(10),
+ arrow_to_edge_padding(5),
+ icon_to_label_padding(8),
+ gutter_to_label(5),
+ check_width(16),
+ check_height(16),
+ radio_width(16),
+ radio_height(16),
+ arrow_height(9),
+ arrow_width(9),
+ gutter_width(0),
+ separator_height(6),
+ render_gutter(false),
+ show_mnemonics(false),
+ scroll_arrow_height(3),
+ label_to_accelerator_padding(10),
+ show_accelerators(true) {
+}
+
+MenuConfig::~MenuConfig() {}
+
+void MenuConfig::Reset() {
+ delete config_instance;
+ config_instance = NULL;
+}
+
+// static
+const MenuConfig& MenuConfig::instance() {
+ if (!config_instance)
+ config_instance = Create();
+ return *config_instance;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_config.h b/ui/views/controls/menu/menu_config.h
new file mode 100644
index 0000000..92014f9
--- /dev/null
+++ b/ui/views/controls/menu/menu_config.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_CONFIG_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_CONFIG_H_
+#pragma once
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/font.h"
+#include "views/views_export.h"
+
+namespace views {
+
+// Layout type information for menu items. Use the instance() method to obtain
+// the MenuConfig for the current platform.
+struct VIEWS_EXPORT MenuConfig {
+ MenuConfig();
+ ~MenuConfig();
+
+ // Resets the single shared MenuConfig instance. The next time instance() is
+ // invoked a new MenuConfig is created and configured.
+ static void Reset();
+
+ // Returns the single shared MenuConfig instance, creating if necessary.
+ static const MenuConfig& instance();
+
+ // Font used by menus.
+ gfx::Font font;
+
+ // Normal text color.
+ SkColor text_color;
+
+ // Margins between the top of the item and the label.
+ int item_top_margin;
+
+ // Margins between the bottom of the item and the label.
+ int item_bottom_margin;
+
+ // Margins used if the menu doesn't have icons.
+ int item_no_icon_top_margin;
+ int item_no_icon_bottom_margin;
+
+ // Margins between the left of the item and the icon.
+ int item_left_margin;
+
+ // Padding between the label and submenu arrow.
+ int label_to_arrow_padding;
+
+ // Padding between the arrow and the edge.
+ int arrow_to_edge_padding;
+
+ // Padding between the icon and label.
+ int icon_to_label_padding;
+
+ // Padding between the gutter and label.
+ int gutter_to_label;
+
+ // Size of the check.
+ int check_width;
+ int check_height;
+
+ // Size of the radio bullet.
+ int radio_width;
+ int radio_height;
+
+ // Size of the submenu arrow.
+ int arrow_height;
+ int arrow_width;
+
+ // Width of the gutter. Only used if render_gutter is true.
+ int gutter_width;
+
+ // Height of the separator.
+ int separator_height;
+
+ // Whether or not the gutter should be rendered. The gutter is specific to
+ // Vista.
+ bool render_gutter;
+
+ // Are mnemonics shown?
+ bool show_mnemonics;
+
+ // Height of the scroll arrow.
+ int scroll_arrow_height;
+
+ // Padding between the label and accelerator. Only used if there is an
+ // accelerator.
+ int label_to_accelerator_padding;
+
+ // Whether the keyboard accelerators are visible.
+ bool show_accelerators;
+
+ private:
+ // Creates and configures a new MenuConfig as appropriate for the current
+ // platform.
+ static MenuConfig* Create();
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_CONFIG_H_
diff --git a/ui/views/controls/menu/menu_config_aura.cc b/ui/views/controls/menu/menu_config_aura.cc
new file mode 100644
index 0000000..31c4203
--- /dev/null
+++ b/ui/views/controls/menu/menu_config_aura.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_config.h"
+
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace views {
+
+// static
+MenuConfig* MenuConfig::Create() {
+ MenuConfig* config = new MenuConfig();
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ config->font = rb.GetFont(ResourceBundle::BaseFont);
+ config->arrow_width = rb.GetBitmapNamed(IDR_MENU_ARROW)->width();
+ // Add 4 to force some padding between check and label.
+ config->check_width = rb.GetBitmapNamed(IDR_MENU_CHECK)->width() + 4;
+ config->check_height = rb.GetBitmapNamed(IDR_MENU_CHECK)->height();
+ return config;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_config_linux.cc b/ui/views/controls/menu/menu_config_linux.cc
new file mode 100644
index 0000000..31c4203
--- /dev/null
+++ b/ui/views/controls/menu/menu_config_linux.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_config.h"
+
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace views {
+
+// static
+MenuConfig* MenuConfig::Create() {
+ MenuConfig* config = new MenuConfig();
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ config->font = rb.GetFont(ResourceBundle::BaseFont);
+ config->arrow_width = rb.GetBitmapNamed(IDR_MENU_ARROW)->width();
+ // Add 4 to force some padding between check and label.
+ config->check_width = rb.GetBitmapNamed(IDR_MENU_CHECK)->width() + 4;
+ config->check_height = rb.GetBitmapNamed(IDR_MENU_CHECK)->height();
+ return config;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_config_win.cc b/ui/views/controls/menu/menu_config_win.cc
new file mode 100644
index 0000000..07681fe
--- /dev/null
+++ b/ui/views/controls/menu/menu_config_win.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_config.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+#include "base/logging.h"
+#include "base/win/scoped_gdi_object.h"
+#include "base/win/win_util.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/gfx/native_theme_win.h"
+
+using gfx::NativeTheme;
+using gfx::NativeThemeWin;
+
+namespace views {
+
+// static
+MenuConfig* MenuConfig::Create() {
+ MenuConfig* config = new MenuConfig();
+
+ config->text_color = NativeThemeWin::instance()->GetThemeColorWithDefault(
+ NativeThemeWin::MENU, MENU_POPUPITEM, MPI_NORMAL, TMT_TEXTCOLOR,
+ COLOR_MENUTEXT);
+
+ NONCLIENTMETRICS metrics;
+ base::win::GetNonClientMetrics(&metrics);
+ l10n_util::AdjustUIFont(&(metrics.lfMenuFont));
+ {
+ base::win::ScopedHFONT font(CreateFontIndirect(&metrics.lfMenuFont));
+ DLOG_ASSERT(font.Get());
+ config->font = gfx::Font(font);
+ }
+ NativeTheme::ExtraParams extra;
+ extra.menu_check.is_radio = false;
+ extra.menu_check.is_selected = false;
+ gfx::Size check_size = NativeTheme::instance()->GetPartSize(
+ NativeTheme::kMenuCheck, NativeTheme::kNormal, extra);
+ if (!check_size.IsEmpty()) {
+ config->check_width = check_size.width();
+ config->check_height = check_size.height();
+ } else {
+ config->check_width = GetSystemMetrics(SM_CXMENUCHECK);
+ config->check_height = GetSystemMetrics(SM_CYMENUCHECK);
+ }
+
+ extra.menu_check.is_radio = true;
+ gfx::Size radio_size = NativeTheme::instance()->GetPartSize(
+ NativeTheme::kMenuCheck, NativeTheme::kNormal, extra);
+ if (!radio_size.IsEmpty()) {
+ config->radio_width = radio_size.width();
+ config->radio_height = radio_size.height();
+ } else {
+ config->radio_width = GetSystemMetrics(SM_CXMENUCHECK);
+ config->radio_height = GetSystemMetrics(SM_CYMENUCHECK);
+ }
+
+ gfx::Size arrow_size = NativeTheme::instance()->GetPartSize(
+ NativeTheme::kMenuPopupArrow, NativeTheme::kNormal, extra);
+ if (!arrow_size.IsEmpty()) {
+ config->arrow_width = arrow_size.width();
+ config->arrow_height = arrow_size.height();
+ } else {
+ // Sadly I didn't see a specify metrics for this.
+ config->arrow_width = GetSystemMetrics(SM_CXMENUCHECK);
+ config->arrow_height = GetSystemMetrics(SM_CYMENUCHECK);
+ }
+
+ gfx::Size gutter_size = NativeTheme::instance()->GetPartSize(
+ NativeTheme::kMenuPopupGutter, NativeTheme::kNormal, extra);
+ if (!gutter_size.IsEmpty()) {
+ config->gutter_width = gutter_size.width();
+ config->render_gutter = true;
+ } else {
+ config->gutter_width = 0;
+ config->render_gutter = false;
+ }
+
+ gfx::Size separator_size = NativeTheme::instance()->GetPartSize(
+ NativeTheme::kMenuPopupSeparator, NativeTheme::kNormal, extra);
+ if (!separator_size.IsEmpty()) {
+ config->separator_height = separator_size.height();
+ } else {
+ // -1 makes separator centered.
+ config->separator_height = GetSystemMetrics(SM_CYMENU) / 2 - 1;
+ }
+
+ // On Windows, having some menus use wider spacing than others looks wrong.
+ // See http://crbug.com/88875
+ config->item_no_icon_bottom_margin = config->item_bottom_margin;
+ config->item_no_icon_top_margin = config->item_top_margin;
+
+ BOOL show_cues;
+ config->show_mnemonics =
+ (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) &&
+ show_cues == TRUE);
+ return config;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
new file mode 100644
index 0000000..9678a81
--- /dev/null
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -0,0 +1,1955 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_controller.h"
+
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/rtl.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/events.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/controls/menu/menu_controller_delegate.h"
+#include "ui/views/controls/menu/menu_scroll_view_container.h"
+#include "ui/views/controls/menu/submenu_view.h"
+#include "ui/views/widget/root_view.h"
+#include "ui/views/widget/widget.h"
+#include "views/drag_utils.h"
+#include "views/view_constants.h"
+#include "views/views_delegate.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/desktop.h"
+#elif defined(TOOLKIT_USES_GTK)
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#endif
+
+using base::Time;
+using base::TimeDelta;
+using ui::OSExchangeData;
+
+// Period of the scroll timer (in milliseconds).
+static const int kScrollTimerMS = 30;
+
+// Delay, in ms, between when menus are selected are moused over and the menu
+// appears.
+static const int kShowDelay = 400;
+
+// Amount of time from when the drop exits the menu and the menu is hidden.
+static const int kCloseOnExitTime = 1200;
+
+// Amount to inset submenus.
+static const int kSubmenuHorizontalInset = 3;
+
+namespace views {
+
+namespace {
+
+// Returns true if the mnemonic of |menu| matches key.
+bool MatchesMnemonic(MenuItemView* menu, char16 key) {
+ return menu->GetMnemonic() == key;
+}
+
+// Returns true if |menu| doesn't have a mnemonic and first character of the its
+// title is |key|.
+bool TitleMatchesMnemonic(MenuItemView* menu, char16 key) {
+ if (menu->GetMnemonic())
+ return false;
+
+ string16 lower_title = base::i18n::ToLower(menu->title());
+ return !lower_title.empty() && lower_title[0] == key;
+}
+
+} // namespace
+
+// Convenience for scrolling the view such that the origin is visible.
+static void ScrollToVisible(View* view) {
+ view->ScrollRectToVisible(view->GetLocalBounds());
+}
+
+// Returns the first descendant of |view| that is hot tracked.
+static View* GetFirstHotTrackedView(View* view) {
+ if (!view)
+ return NULL;
+
+ if (view->IsHotTracked())
+ return view;
+
+ for (int i = 0; i < view->child_count(); ++i) {
+ View* hot_view = GetFirstHotTrackedView(view->child_at(i));
+ if (hot_view)
+ return hot_view;
+ }
+ return NULL;
+}
+
+// Recurses through the child views of |view| returning the first view starting
+// at |start| that is focusable. A value of -1 for |start| indicates to start at
+// the first view (if |forward| is false, iterating starts at the last view). If
+// |forward| is true the children are considered first to last, otherwise last
+// to first.
+static View* GetFirstFocusableView(View* view, int start, bool forward) {
+ if (forward) {
+ for (int i = start == -1 ? 0 : start; i < view->child_count(); ++i) {
+ View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward);
+ if (deepest)
+ return deepest;
+ }
+ } else {
+ for (int i = start == -1 ? view->child_count() - 1 : start; i >= 0; --i) {
+ View* deepest = GetFirstFocusableView(view->child_at(i), -1, forward);
+ if (deepest)
+ return deepest;
+ }
+ }
+ return view->IsFocusableInRootView() ? view : NULL;
+}
+
+// Returns the first child of |start| that is focusable.
+static View* GetInitialFocusableView(View* start, bool forward) {
+ return GetFirstFocusableView(start, -1, forward);
+}
+
+// Returns the next view after |start_at| that is focusable. Returns NULL if
+// there are no focusable children of |ancestor| after |start_at|.
+static View* GetNextFocusableView(View* ancestor,
+ View* start_at,
+ bool forward) {
+ DCHECK(ancestor->Contains(start_at));
+ View* parent = start_at;
+ do {
+ View* new_parent = parent->parent();
+ int index = new_parent->GetIndexOf(parent);
+ index += forward ? 1 : -1;
+ if (forward || index != -1) {
+ View* next = GetFirstFocusableView(new_parent, index, forward);
+ if (next)
+ return next;
+ }
+ parent = new_parent;
+ } while (parent != ancestor);
+ return NULL;
+}
+
+// MenuScrollTask --------------------------------------------------------------
+
+// MenuScrollTask is used when the SubmenuView does not all fit on screen and
+// the mouse is over the scroll up/down buttons. MenuScrollTask schedules
+// itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls
+// appropriately.
+
+class MenuController::MenuScrollTask {
+ public:
+ MenuScrollTask() : submenu_(NULL) {
+ pixels_per_second_ = MenuItemView::pref_menu_height() * 20;
+ }
+
+ void Update(const MenuController::MenuPart& part) {
+ if (!part.is_scroll()) {
+ StopScrolling();
+ return;
+ }
+ DCHECK(part.submenu);
+ SubmenuView* new_menu = part.submenu;
+ bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP);
+ if (new_menu == submenu_ && is_scrolling_up_ == new_is_up)
+ return;
+
+ start_scroll_time_ = Time::Now();
+ start_y_ = part.submenu->GetVisibleBounds().y();
+ submenu_ = new_menu;
+ is_scrolling_up_ = new_is_up;
+
+ if (!scrolling_timer_.IsRunning()) {
+ scrolling_timer_.Start(FROM_HERE,
+ 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);
+ vis_rect.set_y(is_scrolling_up_ ?
+ std::max(0, start_y_ - delta_y) :
+ std::min(submenu_->height() - vis_rect.height(), start_y_ + delta_y));
+ submenu_->ScrollRectToVisible(vis_rect);
+ }
+
+ // SubmenuView being scrolled.
+ SubmenuView* submenu_;
+
+ // Direction scrolling.
+ bool is_scrolling_up_;
+
+ // Timer to periodically scroll.
+ base::RepeatingTimer<MenuScrollTask> scrolling_timer_;
+
+ // Time we started scrolling at.
+ Time start_scroll_time_;
+
+ // How many pixels to scroll per second.
+ int pixels_per_second_;
+
+ // Y-coordinate of submenu_view_ when scrolling started.
+ int start_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollTask);
+};
+
+// MenuController:SelectByCharDetails ----------------------------------------
+
+struct MenuController::SelectByCharDetails {
+ SelectByCharDetails()
+ : first_match(-1),
+ has_multiple(false),
+ index_of_item(-1),
+ next_match(-1) {
+ }
+
+ // Index of the first menu with the specified mnemonic.
+ int first_match;
+
+ // If true there are multiple menu items with the same mnemonic.
+ bool has_multiple;
+
+ // Index of the selected item; may remain -1.
+ int index_of_item;
+
+ // If there are multiple matches this is the index of the item after the
+ // currently selected item whose mnemonic matches. This may remain -1 even
+ // though there are matches.
+ int next_match;
+};
+
+// MenuController:State ------------------------------------------------------
+
+MenuController::State::State() : item(NULL), submenu_open(false) {}
+
+MenuController::State::~State() {}
+
+// MenuController ------------------------------------------------------------
+
+// static
+MenuController* MenuController::active_instance_ = NULL;
+
+// static
+MenuController* MenuController::GetActiveInstance() {
+ return active_instance_;
+}
+
+MenuItemView* MenuController::Run(Widget* parent,
+ MenuButton* button,
+ MenuItemView* root,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position,
+ int* result_mouse_event_flags) {
+ exit_type_ = EXIT_NONE;
+ possible_drag_ = false;
+ drag_in_progress_ = 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_EQ(owner_, parent);
+ } else {
+ showing_ = true;
+ }
+
+ // Reset current state.
+ pending_state_ = State();
+ state_ = State();
+ UpdateInitialLocation(bounds, position);
+
+ owner_ = parent;
+
+ // Set the selection, which opens the initial menu.
+ SetSelection(root, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+
+ 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;
+ } else if (button) {
+ menu_button_ = button;
+ }
+
+ // Make sure Chrome doesn't attempt to shut down while the menu is showing.
+ if (ViewsDelegate::views_delegate)
+ ViewsDelegate::views_delegate->AddRef();
+
+ // We need to turn on nestable tasks as in some situations (pressing alt-f for
+ // one) the menus are run from a task. If we don't do this and are invoked
+ // from a task none of the tasks we schedule are processed and the menu
+ // appears totally broken.
+ MessageLoopForUI* loop = MessageLoopForUI::current();
+ bool did_allow_task_nesting = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+ loop->RunWithDispatcher(this);
+ loop->SetNestableTasksAllowed(did_allow_task_nesting);
+
+ if (ViewsDelegate::views_delegate)
+ ViewsDelegate::views_delegate->ReleaseRef();
+
+ // Close any open menus.
+ SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT);
+
+ 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 (exit_type_ == EXIT_OUTERMOST) {
+ SetExitType(EXIT_NONE);
+ } else {
+ 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();
+ SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT);
+
+ // Set exit_all_, which makes sure all nested loops exit immediately.
+ if (exit_type_ != EXIT_DESTROYED)
+ SetExitType(EXIT_ALL);
+ }
+ }
+
+ // If we stopped running because one of the menus was destroyed chances are
+ // the button was also destroyed.
+ if (exit_type_ != EXIT_DESTROYED && menu_button_) {
+ menu_button_->SetState(CustomButton::BS_NORMAL);
+ menu_button_->SchedulePaint();
+ }
+
+ return result;
+}
+
+void MenuController::Cancel(ExitType type) {
+ // If the menu has already been destroyed, no further cancellation is
+ // needed. We especially don't want to set the |exit_type_| to a lesser
+ // value.
+ if (exit_type_ == EXIT_DESTROYED || exit_type_ == type)
+ return;
+
+ 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;
+ SetExitType(type);
+
+ SendMouseCaptureLostToActiveView();
+
+ // Hide windows immediately.
+ SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT);
+
+ if (!blocking_run_) {
+ // If we didn't block the caller we need to notify the menu, which
+ // triggers deleting us.
+ DCHECK(selected);
+ showing_ = false;
+ delegate_->DropMenuClosed(
+ internal::MenuControllerDelegate::NOTIFY_DELEGATE,
+ selected->GetRootMenuItem());
+ // WARNING: the call to MenuClosed deletes us.
+ return;
+ }
+}
+
+void MenuController::OnMousePressed(SubmenuView* source,
+ const MouseEvent& event) {
+ if (!blocking_run_)
+ return;
+
+ DCHECK(!active_mouse_view_);
+
+ MenuPart part = GetMenuPart(source, event.location());
+ 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.
+#if defined(OS_WIN) && !defined(USE_AURA)
+ RepostEvent(source, event);
+ // NOTE: not reposting on linux seems fine.
+#endif
+
+ // And close.
+ ExitType exit_type = EXIT_ALL;
+ if (!menu_stack_.empty()) {
+ // We're running nested menus. Only exit all if the mouse wasn't over one
+ // of the menus from the last run.
+ gfx::Point screen_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+ MenuPart last_part = GetMenuPartByScreenCoordinateUsingMenu(
+ menu_stack_.back().item, screen_loc);
+ if (last_part.type != MenuPart::NONE)
+ exit_type = EXIT_OUTERMOST;
+ }
+ Cancel(exit_type);
+ return;
+ }
+
+ // On a press we immediately commit the selection, that way a submenu
+ // pops up immediately rather than after a delay.
+ int selection_types = SELECTION_UPDATE_IMMEDIATELY;
+ if (!part.menu) {
+ part.menu = part.parent;
+ selection_types |= SELECTION_OPEN_SUBMENU;
+ } else {
+ if (part.menu->GetDelegate()->CanDrag(part.menu)) {
+ possible_drag_ = true;
+ press_pt_ = event.location();
+ }
+ if (part.menu->HasSubmenu())
+ selection_types |= SELECTION_OPEN_SUBMENU;
+ }
+ SetSelection(part.menu, selection_types);
+}
+
+void MenuController::OnMouseDragged(SubmenuView* source,
+ const MouseEvent& event) {
+ MenuPart part = GetMenuPart(source, event.location());
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (possible_drag_) {
+ if (View::ExceededDragThreshold(event.x() - press_pt_.x(),
+ event.y() - press_pt_.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_pt_);
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc);
+ View::ConvertPointToView(NULL, item, &press_loc);
+ // TODO(beng): Convert to CanvasSkia
+ gfx::CanvasSkia canvas(item->width(), item->height(), false);
+ item->PaintButton(&canvas, MenuItemView::PB_FOR_DRAG);
+
+ OSExchangeData data;
+ item->GetDelegate()->WriteDragData(item, &data);
+ drag_utils::SetDragImageOnDataObject(canvas, item->size(), press_loc,
+ &data);
+ StopScrolling();
+ int drag_ops = item->GetDelegate()->GetDragOperations(item);
+ drag_in_progress_ = true;
+ item->GetWidget()->RunShellDrag(NULL, data, drag_ops);
+ drag_in_progress_ = false;
+
+ if (GetActiveInstance() == this) {
+ if (showing_) {
+ // We're still showing, close all menus.
+ CloseAllNestedMenus();
+ Cancel(EXIT_ALL);
+ } // else case, drop was on us.
+ } // else case, someone canceled us, don't do anything
+ }
+ return;
+ }
+ MenuItemView* mouse_menu = NULL;
+ if (part.type == MenuPart::MENU_ITEM) {
+ if (!part.menu)
+ part.menu = source->GetMenuItem();
+ else
+ mouse_menu = part.menu;
+ SetSelection(part.menu ? part.menu : state_.item, SELECTION_OPEN_SUBMENU);
+ } else if (part.type == MenuPart::NONE) {
+ ShowSiblingMenu(source, event);
+ }
+ UpdateActiveMouseView(source, event, mouse_menu);
+}
+
+void MenuController::OnMouseReleased(SubmenuView* source,
+ const MouseEvent& event) {
+ if (!blocking_run_)
+ return;
+
+ DCHECK(state_.item);
+ possible_drag_ = false;
+ DCHECK(blocking_run_);
+ MenuPart part = GetMenuPart(source, event.location());
+ 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.
+ int selection_types = SELECTION_UPDATE_IMMEDIATELY;
+ if (state_.item == pending_state_.item && state_.submenu_open)
+ selection_types |= SELECTION_OPEN_SUBMENU;
+ SetSelection(pending_state_.item, selection_types);
+ 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, true)) {
+ SendMouseCaptureLostToActiveView();
+ return;
+ }
+ }
+
+ // We can use Ctrl+click or the middle mouse button to recursively open urls
+ // for selected folder menu items. If it's only a left click, show the
+ // contents of the folder.
+ if (!part.is_scroll() && part.menu &&
+ !(part.menu->HasSubmenu() &&
+ (event.flags() == ui::EF_LEFT_BUTTON_DOWN))) {
+ if (active_mouse_view_) {
+ SendMouseReleaseToActiveView(source, event);
+ return;
+ }
+ if (part.menu->GetDelegate()->IsTriggerableEvent(part.menu, event)) {
+ Accept(part.menu, event.flags());
+ 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,
+ SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+ }
+ SendMouseCaptureLostToActiveView();
+}
+
+void MenuController::OnMouseMoved(SubmenuView* source,
+ const MouseEvent& event) {
+ if (showing_submenu_)
+ return;
+
+ MenuPart part = GetMenuPart(source, event.location());
+
+ UpdateScrolling(part);
+
+ if (!blocking_run_)
+ return;
+
+ if (part.type == MenuPart::NONE && ShowSiblingMenu(source, event))
+ return;
+
+ if (part.type == MenuPart::MENU_ITEM && part.menu) {
+ SetSelection(part.menu, SELECTION_OPEN_SUBMENU);
+ } else if (!part.is_scroll() && pending_state_.item &&
+ pending_state_.item->GetParentMenuItem() &&
+ (!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(),
+ SELECTION_OPEN_SUBMENU);
+ }
+}
+
+void MenuController::OnMouseEntered(SubmenuView* source,
+ const MouseEvent& event) {
+ // MouseEntered is always followed by a mouse moved, so don't need to
+ // do anything here.
+}
+
+#if defined(OS_LINUX)
+bool MenuController::OnMouseWheel(SubmenuView* source,
+ const MouseWheelEvent& event) {
+ MenuPart part = GetMenuPart(source, event.location());
+ return part.submenu && part.submenu->OnMouseWheel(event);
+}
+#endif
+
+bool MenuController::GetDropFormats(
+ SubmenuView* source,
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) {
+ return source->GetMenuItem()->GetDelegate()->GetDropFormats(
+ source->GetMenuItem(), formats, custom_formats);
+}
+
+bool MenuController::AreDropTypesRequired(SubmenuView* source) {
+ return source->GetMenuItem()->GetDelegate()->AreDropTypesRequired(
+ source->GetMenuItem());
+}
+
+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 == drop_pt_)
+ return last_drop_operation_;
+ drop_pt_ = screen_loc;
+ 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 = ui::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 {
+ drop_position = (menu_item_loc.y() < menu_item_height / 2) ?
+ MenuDelegate::DROP_BEFORE : 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 the menu has a submenu, schedule the submenu to open.
+ SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU :
+ SELECTION_DEFAULT);
+
+ if (drop_position == MenuDelegate::DROP_NONE ||
+ drop_operation == ui::DragDropTypes::DRAG_NONE)
+ menu_item = NULL;
+ } else {
+ SetSelection(source->GetMenuItem(), SELECTION_OPEN_SUBMENU);
+ }
+ 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, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT);
+ CloseAllNestedMenus();
+
+ // Set state such that we exit.
+ showing_ = false;
+ SetExitType(EXIT_ALL);
+
+ // If over an empty menu item, drop occurs on the parent.
+ if (drop_target->id() == MenuItemView::kEmptyMenuItemViewID)
+ drop_target = drop_target->GetParentMenuItem();
+
+ if (!IsBlockingRun()) {
+ delegate_->DropMenuClosed(
+ internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE,
+ item->GetRootMenuItem());
+ }
+
+ // WARNING: the call to MenuClosed deletes us.
+
+ 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();
+}
+
+void MenuController::OnWidgetActivationChanged() {
+ if (!drag_in_progress_)
+ Cancel(EXIT_ALL);
+}
+
+void MenuController::SetSelection(MenuItemView* menu_item,
+ int selection_types) {
+ 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();
+
+ if (pending_state_.item != menu_item && pending_state_.item) {
+ View* current_hot_view = GetFirstHotTrackedView(pending_state_.item);
+ if (current_hot_view)
+ current_hot_view->SetHotTracked(false);
+ }
+
+ // Notify the old path it isn't selected.
+ MenuDelegate* current_delegate =
+ current_path.empty() ? NULL : current_path.front()->GetDelegate();
+ for (size_t i = paths_differ_at; i < current_size; ++i) {
+ if (current_delegate &&
+ current_path[i]->GetType() == MenuItemView::SUBMENU) {
+ current_delegate->WillHideMenu(current_path[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);
+
+ // TODO(sky): convert back to DCHECK when figure out 93471.
+ CHECK(menu_item || (selection_types & SELECTION_EXIT) != 0);
+
+ pending_state_.item = menu_item;
+ pending_state_.submenu_open = (selection_types & SELECTION_OPEN_SUBMENU) != 0;
+
+ // Stop timers.
+ StopShowTimer();
+ StopCancelAllTimer();
+
+ if (selection_types & SELECTION_UPDATE_IMMEDIATELY)
+ CommitPendingSelection();
+ else
+ StartShowTimer();
+
+ // Notify an accessibility focus event on all menu items except for the root.
+ if (menu_item &&
+ (MenuDepth(menu_item) != 1 ||
+ menu_item->GetType() != MenuItemView::SUBMENU)) {
+ menu_item->GetWidget()->NotifyAccessibilityEvent(
+ menu_item, ui::AccessibilityTypes::EVENT_FOCUS, true);
+ }
+}
+
+#if defined(OS_WIN)
+bool MenuController::Dispatch(const MSG& msg) {
+ DCHECK(blocking_run_);
+
+ if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) {
+ // 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, 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: {
+ bool result = OnKeyDown(ui::KeyboardCodeFromNative(msg));
+ TranslateMessage(&msg);
+ return result;
+ }
+ case WM_CHAR:
+ return !SelectByChar(static_cast<char16>(msg.wParam));
+
+ 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(EXIT_ALL);
+ return false;
+
+ default:
+ break;
+ }
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return exit_type_ == EXIT_NONE;
+}
+#elif defined(USE_WAYLAND)
+base::MessagePumpDispatcher::DispatchStatus
+ MenuController::Dispatch(base::wayland::WaylandEvent* ev) {
+ return exit_type_ != EXIT_NONE ?
+ base::MessagePumpDispatcher::EVENT_QUIT :
+ base::MessagePumpDispatcher::EVENT_PROCESSED;
+}
+
+#elif defined(USE_AURA)
+base::MessagePumpDispatcher::DispatchStatus
+ MenuController::Dispatch(XEvent* xev) {
+ if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) {
+ aura::Desktop::GetInstance()->GetDispatcher()->Dispatch(xev);
+ return base::MessagePumpDispatcher::EVENT_QUIT;
+ }
+ switch (ui::EventTypeFromNative(xev)) {
+ case ui::ET_KEY_PRESSED:
+ OnKeyDown(ui::KeyboardCodeFromNative(xev));
+ // OnKeyDown may have set exit_type_.
+ if (exit_type_ != EXIT_NONE)
+ return base::MessagePumpDispatcher::EVENT_QUIT;
+
+ // TODO(oshima): support SelectChar
+ break;
+ case ui::ET_KEY_RELEASED:
+ return base::MessagePumpDispatcher::EVENT_PROCESSED;
+ default:
+ break;
+ }
+
+ // TODO(oshima): Update Windows' Dispatcher to return DispatchStatus
+ // instead of bool.
+ if (aura::Desktop::GetInstance()->GetDispatcher()->Dispatch(xev) ==
+ base::MessagePumpDispatcher::EVENT_IGNORED)
+ return EVENT_IGNORED;
+ return exit_type_ != EXIT_NONE ?
+ base::MessagePumpDispatcher::EVENT_QUIT :
+ base::MessagePumpDispatcher::EVENT_PROCESSED;
+}
+#else
+bool MenuController::Dispatch(GdkEvent* event) {
+ if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) {
+ gtk_main_do_event(event);
+ return false;
+ }
+
+ switch (event->type) {
+ case GDK_KEY_PRESS: {
+ if (!OnKeyDown(ui::WindowsKeyCodeForGdkKeyCode(event->key.keyval)))
+ return false;
+
+ // OnKeyDown may have set exit_type_.
+ if (exit_type_ != EXIT_NONE)
+ return false;
+
+ guint32 keycode = gdk_keyval_to_unicode(event->key.keyval);
+ if (keycode)
+ return !SelectByChar(keycode);
+ return true;
+ }
+
+ case GDK_KEY_RELEASE:
+ return true;
+
+ default:
+ break;
+ }
+
+ // We don't want Gtk to handle keyboard events, otherwise if they get
+ // handled by Gtk, unexpected behavior may occur. For example Tab key
+ // may cause unexpected focus traversing.
+ gtk_main_do_event(event);
+ return exit_type_ == EXIT_NONE;
+}
+#endif
+
+bool MenuController::OnKeyDown(ui::KeyboardCode key_code) {
+ DCHECK(blocking_run_);
+
+ switch (key_code) {
+ case ui::VKEY_UP:
+ IncrementSelection(-1);
+ break;
+
+ case ui::VKEY_DOWN:
+ IncrementSelection(1);
+ break;
+
+ // Handling of VK_RIGHT and VK_LEFT is different depending on the UI
+ // layout.
+ case ui::VKEY_RIGHT:
+ if (base::i18n::IsRTL())
+ CloseSubmenu();
+ else
+ OpenSubmenuChangeSelectionIfCan();
+ break;
+
+ case ui::VKEY_LEFT:
+ if (base::i18n::IsRTL())
+ OpenSubmenuChangeSelectionIfCan();
+ else
+ CloseSubmenu();
+ break;
+
+ case ui::VKEY_SPACE:
+ SendAcceleratorToHotTrackedView();
+ break;
+
+ case ui::VKEY_RETURN:
+ if (pending_state_.item) {
+ if (pending_state_.item->HasSubmenu()) {
+ OpenSubmenuChangeSelectionIfCan();
+ } else if (!SendAcceleratorToHotTrackedView() &&
+ pending_state_.item->IsEnabled()) {
+ Accept(pending_state_.item, 0);
+ return false;
+ }
+ }
+ break;
+
+ case ui::VKEY_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(EXIT_OUTERMOST);
+ return false;
+ }
+ CloseSubmenu();
+ break;
+
+#if defined(OS_WIN)
+ case VK_APPS:
+ break;
+#endif
+
+ default:
+ break;
+ }
+ return true;
+}
+
+MenuController::MenuController(bool blocking,
+ internal::MenuControllerDelegate* delegate)
+ : blocking_run_(blocking),
+ showing_(false),
+ exit_type_(EXIT_NONE),
+ did_capture_(false),
+ result_(NULL),
+ result_mouse_event_flags_(0),
+ drop_target_(NULL),
+ drop_position_(MenuDelegate::DROP_UNKNOWN),
+ owner_(NULL),
+ possible_drag_(false),
+ drag_in_progress_(false),
+ valid_drop_coordinates_(false),
+ last_drop_operation_(MenuDelegate::DROP_UNKNOWN),
+ showing_submenu_(false),
+ menu_button_(NULL),
+ active_mouse_view_(NULL),
+ delegate_(delegate) {
+ active_instance_ = this;
+}
+
+MenuController::~MenuController() {
+ DCHECK(!showing_);
+ if (active_instance_ == this)
+ active_instance_ = NULL;
+ StopShowTimer();
+ StopCancelAllTimer();
+}
+
+bool MenuController::SendAcceleratorToHotTrackedView() {
+ View* hot_view = GetFirstHotTrackedView(pending_state_.item);
+ if (!hot_view)
+ return false;
+
+ ui::Accelerator accelerator(ui::VKEY_RETURN, false, false, false);
+ hot_view->AcceleratorPressed(accelerator);
+ hot_view->SetHotTracked(true);
+ return true;
+}
+
+void MenuController::UpdateInitialLocation(
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position) {
+ 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);
+ }
+
+ // Reverse anchor position for RTL languages.
+ if (base::i18n::IsRTL()) {
+ pending_state_.anchor = position == MenuItemView::TOPRIGHT ?
+ MenuItemView::TOPLEFT : MenuItemView::TOPRIGHT;
+ } else {
+ pending_state_.anchor = position;
+ }
+
+ // Calculate the bounds of the monitor we'll show menus on. Do this once to
+ // avoid repeated system queries for the info.
+ pending_state_.monitor_bounds = gfx::Screen::GetMonitorWorkAreaNearestPoint(
+ bounds.origin());
+}
+
+void MenuController::Accept(MenuItemView* item, int mouse_event_flags) {
+ DCHECK(IsBlockingRun());
+ result_ = item;
+ if (item && !menu_stack_.empty() &&
+ !item->GetDelegate()->ShouldCloseAllMenusOnExecute(item->GetCommand())) {
+ SetExitType(EXIT_OUTERMOST);
+ } else {
+ SetExitType(EXIT_ALL);
+ }
+ result_mouse_event_flags_ = mouse_event_flags;
+}
+
+bool MenuController::ShowSiblingMenu(SubmenuView* source,
+ const MouseEvent& event) {
+ if (!menu_stack_.empty() || !menu_button_)
+ return false;
+
+ View* source_view = source->GetScrollViewContainer();
+ if (event.x() >= 0 && event.x() < source_view->width() && event.y() >= 0 &&
+ event.y() < source_view->height()) {
+ // The mouse is over the menu, no need to continue.
+ return false;
+ }
+
+ gfx::NativeWindow window_under_mouse =
+ gfx::Screen::GetWindowAtCursorScreenPoint();
+ // TODO(oshima): Replace with views only API.
+ if (window_under_mouse != owner_->GetNativeWindow())
+ return false;
+
+ // The user moved the mouse outside the menu and over the owning window. See
+ // if there is a sibling menu we should show.
+ gfx::Point screen_point(event.location());
+ View::ConvertPointToScreen(source_view, &screen_point);
+ MenuItemView::AnchorPosition anchor;
+ bool has_mnemonics;
+ MenuButton* button = NULL;
+ MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()->
+ GetSiblingMenu(source->GetMenuItem()->GetRootMenuItem(),
+ screen_point, &anchor, &has_mnemonics, &button);
+ if (!alt_menu || (state_.item && state_.item->GetRootMenuItem() == alt_menu))
+ return false;
+
+ delegate_->SiblingMenuCreated(alt_menu);
+
+ if (!button) {
+ // If the delegate returns a menu, they must also return a button.
+ NOTREACHED();
+ return false;
+ }
+
+ // There is a sibling menu, update the button state, hide the current menu
+ // and show the new one.
+ menu_button_->SetState(CustomButton::BS_NORMAL);
+ menu_button_->SchedulePaint();
+ menu_button_ = button;
+ menu_button_->SetState(CustomButton::BS_PUSHED);
+ menu_button_->SchedulePaint();
+
+ // Need to reset capture when we show the menu again, otherwise we aren't
+ // going to get any events.
+ did_capture_ = false;
+ gfx::Point screen_menu_loc;
+ View::ConvertPointToScreen(button, &screen_menu_loc);
+ // Subtract 1 from the height to make the popup flush with the button border.
+ UpdateInitialLocation(gfx::Rect(screen_menu_loc.x(), screen_menu_loc.y(),
+ button->width(), button->height() - 1),
+ anchor);
+ alt_menu->PrepareForRun(
+ has_mnemonics,
+ source->GetMenuItem()->GetRootMenuItem()->show_mnemonics_);
+ alt_menu->controller_ = this;
+ SetSelection(alt_menu, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+ return true;
+}
+
+void MenuController::CloseAllNestedMenus() {
+ for (std::list<State>::iterator i = menu_stack_.begin();
+ i != menu_stack_.end(); ++i) {
+ MenuItemView* last_item = i->item;
+ for (MenuItemView* item = last_item; item;
+ item = item->GetParentMenuItem()) {
+ CloseMenu(item);
+ last_item = item;
+ }
+ i->submenu_open = false;
+ i->item = last_item;
+ }
+}
+
+MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) {
+ // Walk the view hierarchy until we find a menu item (or the root).
+ View* child_under_mouse = source->GetEventHandlerForPoint(gfx::Point(x, y));
+ while (child_under_mouse &&
+ child_under_mouse->id() != MenuItemView::kMenuItemViewID) {
+ child_under_mouse = child_under_mouse->parent();
+ }
+ if (child_under_mouse && child_under_mouse->IsEnabled() &&
+ child_under_mouse->id() == 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->GetEventHandlerForPoint(gfx::Point(x, y));
+ if (child_under_mouse &&
+ child_under_mouse->id() == MenuItemView::kEmptyMenuItemViewID) {
+ return static_cast<MenuItemView*>(child_under_mouse);
+ }
+ return NULL;
+}
+
+bool MenuController::IsScrollButtonAt(SubmenuView* source,
+ int x,
+ int y,
+ MenuPart::Type* part) {
+ MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer();
+ View* child_under_mouse =
+ scroll_view->GetEventHandlerForPoint(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::GetMenuPart(
+ SubmenuView* source,
+ const gfx::Point& source_loc) {
+ gfx::Point screen_loc(source_loc);
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+ return GetMenuPartByScreenCoordinateUsingMenu(state_.item, screen_loc);
+}
+
+MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinateUsingMenu(
+ MenuItemView* item,
+ const gfx::Point& screen_loc) {
+ MenuPart part;
+ for (; item; item = item->GetParentMenuItem()) {
+ if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() &&
+ GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc,
+ &part)) {
+ return part;
+ }
+ }
+ 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;
+ part->submenu = menu;
+ if (!part->menu)
+ part->parent = menu->GetMenuItem();
+ return true;
+ }
+
+ // While the mouse isn't over a menu item or the scroll buttons of menu, it
+ // is contained by menu and so we return true. If we didn't return true other
+ // menus would be searched, even though they are likely obscured by us.
+ return true;
+}
+
+bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu,
+ const gfx::Point& screen_loc) {
+ gfx::Point view_loc = screen_loc;
+ View::ConvertPointToView(NULL, submenu, &view_loc);
+ gfx::Rect vis_rect = submenu->GetVisibleBounds();
+ return vis_rect.Contains(view_loc.x(), view_loc.y());
+}
+
+void MenuController::CommitPendingSelection() {
+ StopShowTimer();
+
+ size_t paths_differ_at = 0;
+ std::vector<MenuItemView*> current_path;
+ std::vector<MenuItemView*> new_path;
+ BuildPathsAndCalculateDiff(state_.item, pending_state_.item, &current_path,
+ &new_path, &paths_differ_at);
+
+ // Hide the old menu.
+ for (size_t i = paths_differ_at; i < current_path.size(); ++i) {
+ if (current_path[i]->HasSubmenu()) {
+ current_path[i]->GetSubmenu()->Hide();
+ }
+ }
+
+ // Copy pending to state_, making sure to preserve the direction menus were
+ // opened.
+ std::list<bool> pending_open_direction;
+ state_.open_leading.swap(pending_open_direction);
+ state_ = pending_state_;
+ state_.open_leading.swap(pending_open_direction);
+
+ int menu_depth = MenuDepth(state_.item);
+ if (menu_depth == 0) {
+ state_.open_leading.clear();
+ } else {
+ int cached_size = static_cast<int>(state_.open_leading.size());
+ DCHECK_GE(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;
+ for (MenuItemView* item = state_.item; item && !found;
+ item = item->GetParentMenuItem()) {
+ found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() &&
+ item->GetSubmenu() == scroll_task_->submenu());
+ }
+ 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;
+ }
+
+ OpenMenuImpl(item, true);
+ did_capture_ = true;
+}
+
+void MenuController::OpenMenuImpl(MenuItemView* item, bool show) {
+ if (show) {
+ int old_count = item->GetSubmenu()->child_count();
+ item->GetDelegate()->WillShowMenu(item);
+ if (old_count != item->GetSubmenu()->child_count()) {
+ // If the number of children changed then we may need to add empty items.
+ item->AddEmptyMenus();
+ }
+ }
+ 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;
+ if (show)
+ item->GetSubmenu()->ShowAt(owner_, bounds, do_capture);
+ else
+ item->GetSubmenu()->Reposition(bounds);
+ showing_submenu_ = false;
+}
+
+void MenuController::MenuChildrenChanged(MenuItemView* item) {
+ DCHECK(item);
+
+ // If the current item or pending item is a descendant of the item
+ // that changed, move the selection back to the changed item.
+ const MenuItemView* ancestor = state_.item;
+ while (ancestor && ancestor != item)
+ ancestor = ancestor->GetParentMenuItem();
+ if (!ancestor) {
+ ancestor = pending_state_.item;
+ while (ancestor && ancestor != item)
+ ancestor = ancestor->GetParentMenuItem();
+ if (!ancestor)
+ return;
+ }
+ SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+ if (item->HasSubmenu())
+ OpenMenuImpl(item, false);
+}
+
+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(FROM_HERE, TimeDelta::FromMilliseconds(kShowDelay), this,
+ &MenuController::CommitPendingSelection);
+}
+
+void MenuController::StopShowTimer() {
+ show_timer_.Stop();
+}
+
+void MenuController::StartCancelAllTimer() {
+ cancel_all_timer_.Start(FROM_HERE,
+ 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.
+ pref.set_width(std::min(pref.width(),
+ item->GetDelegate()->GetMaxWidthForMenu(item)));
+ 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. The menu position with
+ // respect to the bounds will be preserved if it has already
+ // been drawn. On the first drawing if the first location is
+ // above the half way point then 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 (item->actual_menu_position() == MenuItemView::POSITION_BELOW_BOUNDS ||
+ (item->actual_menu_position() == MenuItemView::POSITION_BEST_FIT &&
+ y < (state_.monitor_bounds.y() +
+ state_.monitor_bounds.height() / 2))) {
+ pref.set_height(std::min(pref.height(),
+ state_.monitor_bounds.bottom() - y));
+ item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS);
+ } else {
+ pref.set_height(std::min(pref.height(),
+ state_.initial_bounds.y() - state_.monitor_bounds.y()));
+ y = state_.initial_bounds.y() - pref.height();
+ item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS);
+ }
+ } else if (item->actual_menu_position() ==
+ MenuItemView::POSITION_ABOVE_BOUNDS) {
+ // The menu would fit below the bounds, but it has already been
+ // drawn above so keep it there.
+ pref.set_height(std::min(pref.height(),
+ state_.initial_bounds.y() - state_.monitor_bounds.y()));
+ y = state_.initial_bounds.y() - pref.height();
+ item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS);
+ } else {
+ item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS);
+ }
+ } 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 = base::i18n::IsRTL();
+ bool create_on_the_right = (prefer_leading && !layout_is_rtl) ||
+ (!prefer_leading && layout_is_rtl);
+
+ if (create_on_the_right) {
+ x = item_loc.x() + item->width() - kSubmenuHorizontalInset;
+ if (state_.monitor_bounds.width() != 0 &&
+ x + pref.width() > state_.monitor_bounds.right()) {
+ if (layout_is_rtl)
+ *is_leading = true;
+ else
+ *is_leading = false;
+ x = item_loc.x() - pref.width() + kSubmenuHorizontalInset;
+ }
+ } else {
+ x = item_loc.x() - pref.width() + kSubmenuHorizontalInset;
+ if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) {
+ if (layout_is_rtl)
+ *is_leading = false;
+ else
+ *is_leading = true;
+ x = item_loc.x() + item->width() - kSubmenuHorizontalInset;
+ }
+ }
+ y = item_loc.y() - SubmenuView::kSubmenuBorderSize;
+ if (state_.monitor_bounds.width() != 0) {
+ pref.set_height(std::min(pref.height(), state_.monitor_bounds.height()));
+ if (y + pref.height() > state_.monitor_bounds.bottom())
+ y = state_.monitor_bounds.bottom() - pref.height();
+ if (y < state_.monitor_bounds.y())
+ y = state_.monitor_bounds.y();
+ }
+ }
+
+ if (state_.monitor_bounds.width() != 0) {
+ if (x + pref.width() > state_.monitor_bounds.right())
+ x = state_.monitor_bounds.right() - pref.width();
+ if (x < state_.monitor_bounds.x())
+ x = state_.monitor_bounds.x();
+ }
+ return gfx::Rect(x, y, pref.width(), pref.height());
+}
+
+// static
+int MenuController::MenuDepth(MenuItemView* item) {
+ return item ? (MenuDepth(item->GetParentMenuItem()) + 1) : 0;
+}
+
+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), SELECTION_DEFAULT);
+ ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0));
+ return;
+ }
+ }
+
+ if (item->has_children()) {
+ View* hot_view = GetFirstHotTrackedView(item);
+ if (hot_view) {
+ hot_view->SetHotTracked(false);
+ View* to_make_hot = GetNextFocusableView(item, hot_view, delta == 1);
+ if (to_make_hot) {
+ to_make_hot->SetHotTracked(true);
+ return;
+ }
+ } else {
+ View* to_make_hot = GetInitialFocusableView(item, delta == 1);
+ if (to_make_hot) {
+ to_make_hot->SetHotTracked(true);
+ return;
+ }
+ }
+ }
+
+ MenuItemView* parent = item->GetParentMenuItem();
+ if (parent) {
+ int parent_count = parent->GetSubmenu()->GetMenuItemCount();
+ if (parent_count > 1) {
+ for (int i = 0; i < parent_count; ++i) {
+ if (parent->GetSubmenu()->GetMenuItemAt(i) == item) {
+ MenuItemView* to_select =
+ FindNextSelectableMenuItem(parent, i, delta);
+ if (!to_select)
+ break;
+ ScrollToVisible(to_select);
+ SetSelection(to_select, SELECTION_DEFAULT);
+ View* to_make_hot = GetInitialFocusableView(to_select, delta == 1);
+ if (to_make_hot)
+ to_make_hot->SetHotTracked(true);
+ break;
+ }
+ }
+ }
+ }
+}
+
+MenuItemView* MenuController::FindNextSelectableMenuItem(MenuItemView* parent,
+ int index,
+ int delta) {
+ int start_index = index;
+ int parent_count = parent->GetSubmenu()->GetMenuItemCount();
+ // Loop through the menu items skipping any invisible menus. The loop stops
+ // when we wrap or find a visible child.
+ do {
+ index = (index + delta + parent_count) % parent_count;
+ if (index == start_index)
+ return NULL;
+ MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index);
+ if (child->IsVisible())
+ return child;
+ } while (index != start_index);
+ return NULL;
+}
+
+void MenuController::OpenSubmenuChangeSelectionIfCan() {
+ MenuItemView* item = pending_state_.item;
+ if (item->HasSubmenu() && item->IsEnabled()) {
+ if (item->GetSubmenu()->GetMenuItemCount() > 0) {
+ SetSelection(item->GetSubmenu()->GetMenuItemAt(0),
+ SELECTION_UPDATE_IMMEDIATELY);
+ } else {
+ // No menu items, just show the sub-menu.
+ SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+ }
+ }
+}
+
+void MenuController::CloseSubmenu() {
+ MenuItemView* item = state_.item;
+ DCHECK(item);
+ if (!item->GetParentMenuItem())
+ return;
+ if (item->HasSubmenu() && item->GetSubmenu()->IsShowing())
+ SetSelection(item, SELECTION_UPDATE_IMMEDIATELY);
+ else if (item->GetParentMenuItem()->GetParentMenuItem())
+ SetSelection(item->GetParentMenuItem(), SELECTION_UPDATE_IMMEDIATELY);
+}
+
+MenuController::SelectByCharDetails MenuController::FindChildForMnemonic(
+ MenuItemView* parent,
+ char16 key,
+ bool (*match_function)(MenuItemView* menu, char16 mnemonic)) {
+ SubmenuView* submenu = parent->GetSubmenu();
+ DCHECK(submenu);
+ SelectByCharDetails details;
+
+ for (int i = 0, menu_item_count = submenu->GetMenuItemCount();
+ i < menu_item_count; ++i) {
+ MenuItemView* child = submenu->GetMenuItemAt(i);
+ if (child->IsEnabled() && child->IsVisible()) {
+ if (child == pending_state_.item)
+ details.index_of_item = i;
+ if (match_function(child, key)) {
+ if (details.first_match == -1)
+ details.first_match = i;
+ else
+ details.has_multiple = true;
+ if (details.next_match == -1 && details.index_of_item != -1 &&
+ i > details.index_of_item)
+ details.next_match = i;
+ }
+ }
+ }
+ return details;
+}
+
+bool MenuController::AcceptOrSelect(MenuItemView* parent,
+ const SelectByCharDetails& details) {
+ // This should only be invoked if there is a match.
+ DCHECK(details.first_match != -1);
+ DCHECK(parent->HasSubmenu());
+ SubmenuView* submenu = parent->GetSubmenu();
+ DCHECK(submenu);
+ if (!details.has_multiple) {
+ // There's only one match, activate it (or open if it has a submenu).
+ if (submenu->GetMenuItemAt(details.first_match)->HasSubmenu()) {
+ SetSelection(submenu->GetMenuItemAt(details.first_match),
+ SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
+ } else {
+ Accept(submenu->GetMenuItemAt(details.first_match), 0);
+ return true;
+ }
+ } else if (details.index_of_item == -1 || details.next_match == -1) {
+ SetSelection(submenu->GetMenuItemAt(details.first_match),
+ SELECTION_DEFAULT);
+ } else {
+ SetSelection(submenu->GetMenuItemAt(details.next_match),
+ SELECTION_DEFAULT);
+ }
+ return false;
+}
+
+bool MenuController::SelectByChar(char16 character) {
+ char16 char_array[] = { character, 0 };
+ char16 key = base::i18n::ToLower(char_array)[0];
+ MenuItemView* item = pending_state_.item;
+ if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing())
+ item = item->GetParentMenuItem();
+ DCHECK(item);
+ DCHECK(item->HasSubmenu());
+ DCHECK(item->GetSubmenu());
+ if (item->GetSubmenu()->GetMenuItemCount() == 0)
+ return false;
+
+ // Look for matches based on mnemonic first.
+ SelectByCharDetails details =
+ FindChildForMnemonic(item, key, &MatchesMnemonic);
+ if (details.first_match != -1)
+ return AcceptOrSelect(item, details);
+
+ if (item->GetRootMenuItem()->has_mnemonics()) {
+ // Don't guess at mnemonics if the menu explicitly has them.
+ return false;
+ }
+
+ // If no mnemonics found, look at first character of titles.
+ details = FindChildForMnemonic(item, key, &TitleMatchesMnemonic);
+ if (details.first_match != -1)
+ return AcceptOrSelect(item, details);
+
+ return false;
+}
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+void MenuController::RepostEvent(SubmenuView* source,
+ const MouseEvent& event) {
+ if (!state_.item) {
+ // We some times get an event after closing all the menus. Ignore it.
+ // Make sure the menu is in fact not visible. If the menu is visible, then
+ // we're in a bad state where we think the menu isn't visibile but it is.
+ DCHECK(!source->GetWidget()->IsVisible());
+ return;
+ }
+
+ gfx::Point screen_loc(event.location());
+ View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc);
+ HWND window = WindowFromPoint(screen_loc.ToPOINT());
+ if (window) {
+ // Release the capture.
+ SubmenuView* submenu = state_.item->GetRootMenuItem()->GetSubmenu();
+ submenu->ReleaseCapture();
+
+ if (submenu->GetWidget()->GetNativeView() &&
+ GetWindowThreadProcessId(submenu->GetWidget()->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.native_event().wParam,
+ MAKELPARAM(window_x, window_y));
+ } else {
+ PostMessage(window, event_type, nc_hit_result,
+ MAKELPARAM(screen_loc.x(), screen_loc.y()));
+ }
+ }
+ }
+}
+#endif // defined(OS_WIN)
+
+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);
+}
+
+void MenuController::UpdateActiveMouseView(SubmenuView* event_source,
+ const MouseEvent& event,
+ View* target_menu) {
+ View* target = NULL;
+ gfx::Point target_menu_loc(event.location());
+ if (target_menu && target_menu->has_children()) {
+ // Locate the deepest child view to send events to. This code assumes we
+ // don't have to walk up the tree to find a view interested in events. This
+ // is currently true for the cases we are embedding views, but if we embed
+ // more complex hierarchies it'll need to change.
+ View::ConvertPointToScreen(event_source->GetScrollViewContainer(),
+ &target_menu_loc);
+ View::ConvertPointToView(NULL, target_menu, &target_menu_loc);
+ target = target_menu->GetEventHandlerForPoint(target_menu_loc);
+ if (target == target_menu || !target->IsEnabled())
+ target = NULL;
+ }
+ if (target != active_mouse_view_) {
+ SendMouseCaptureLostToActiveView();
+ active_mouse_view_ = target;
+ if (active_mouse_view_) {
+ gfx::Point target_point(target_menu_loc);
+ View::ConvertPointToView(target_menu, active_mouse_view_, &target_point);
+ MouseEvent mouse_entered_event(ui::ET_MOUSE_ENTERED,
+ target_point.x(), target_point.y(), 0);
+ active_mouse_view_->OnMouseEntered(mouse_entered_event);
+
+ MouseEvent mouse_pressed_event(ui::ET_MOUSE_PRESSED,
+ target_point.x(), target_point.y(),
+ event.flags());
+ active_mouse_view_->OnMousePressed(mouse_pressed_event);
+ }
+ }
+
+ if (active_mouse_view_) {
+ gfx::Point target_point(target_menu_loc);
+ View::ConvertPointToView(target_menu, active_mouse_view_, &target_point);
+ MouseEvent mouse_dragged_event(ui::ET_MOUSE_DRAGGED,
+ target_point.x(), target_point.y(),
+ event.flags());
+ active_mouse_view_->OnMouseDragged(mouse_dragged_event);
+ }
+}
+
+void MenuController::SendMouseReleaseToActiveView(SubmenuView* event_source,
+ const MouseEvent& event) {
+ if (!active_mouse_view_)
+ return;
+
+ gfx::Point target_loc(event.location());
+ View::ConvertPointToScreen(event_source->GetScrollViewContainer(),
+ &target_loc);
+ View::ConvertPointToView(NULL, active_mouse_view_, &target_loc);
+ MouseEvent release_event(ui::ET_MOUSE_RELEASED, target_loc.x(),
+ target_loc.y(), event.flags());
+ // Reset the active_mouse_view_ before sending mouse released. That way if it
+ // calls back to us, we aren't in a weird state.
+ View* active_view = active_mouse_view_;
+ active_mouse_view_ = NULL;
+ active_view->OnMouseReleased(release_event);
+}
+
+void MenuController::SendMouseCaptureLostToActiveView() {
+ if (!active_mouse_view_)
+ return;
+
+ // Reset the active_mouse_view_ before sending mouse capture lost. That way if
+ // it calls back to us, we aren't in a weird state.
+ View* active_view = active_mouse_view_;
+ active_mouse_view_ = NULL;
+ active_view->OnMouseCaptureLost();
+}
+
+void MenuController::SetExitType(ExitType type) {
+ exit_type_ = type;
+#if defined(USE_AURA)
+ // On aura, closing menu may not trigger next native event, which
+ // is necessary to exit from nested loop (See Dispatch methods).
+ // Send non-op event so that Dispatch method will always be called.
+ // crbug.com/104684.
+ if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED)
+ aura::Desktop::GetInstance()->PostNativeEvent(ui::CreateNoopEvent());
+#endif
+}
+
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_controller.h b/ui/views/controls/menu/menu_controller.h
new file mode 100644
index 0000000..59bdc8b
--- /dev/null
+++ b/ui/views/controls/menu/menu_controller.h
@@ -0,0 +1,513 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
+#pragma once
+
+#include "build/build_config.h"
+
+#include <list>
+#include <set>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/timer.h"
+#include "ui/base/events.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+
+namespace ui {
+class OSExchangeData;
+}
+using ui::OSExchangeData;
+
+namespace views {
+
+class DropTargetEvent;
+class MenuButton;
+class MenuHostRootView;
+class MouseEvent;
+class SubmenuView;
+class View;
+
+namespace internal {
+class MenuControllerDelegate;
+class MenuRunnerImpl;
+}
+
+// MenuController -------------------------------------------------------------
+
+// MenuController is used internally by the various menu classes to manage
+// showing, selecting and drag/drop for menus. All relevant events are
+// forwarded to the MenuController from SubmenuView and MenuHost.
+class VIEWS_EXPORT MenuController : public MessageLoop::Dispatcher {
+ public:
+ // Enumeration of how the menu should exit.
+ enum ExitType {
+ // Don't exit.
+ EXIT_NONE,
+
+ // All menus, including nested, should be exited.
+ EXIT_ALL,
+
+ // Only the outermost menu should be exited.
+ EXIT_OUTERMOST,
+
+ // This is set if the menu is being closed as the result of one of the menus
+ // being destroyed.
+ EXIT_DESTROYED
+ };
+
+ // 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(Widget* parent,
+ MenuButton* button,
+ MenuItemView* root,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position,
+ int* mouse_event_flags);
+
+ // Whether or not Run blocks.
+ bool IsBlockingRun() const { return blocking_run_; }
+
+ // Whether or not drag operation is in progress.
+ bool drag_in_progress() const { return drag_in_progress_; }
+
+ // Cancels the current Run. See ExitType for a description of what happens
+ // with the various parameters.
+ void Cancel(ExitType type);
+
+ // An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
+ void CancelAll() { Cancel(EXIT_ALL); }
+
+ // Returns the current exit type. This returns a value other than EXIT_NONE if
+ // the menu is being canceled.
+ ExitType exit_type() const { return exit_type_; }
+
+ // 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);
+#if defined(OS_LINUX)
+ bool OnMouseWheel(SubmenuView* source, const MouseWheelEvent& event);
+#endif
+
+ bool GetDropFormats(
+ SubmenuView* source,
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats);
+ bool AreDropTypesRequired(SubmenuView* source);
+ 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);
+
+ // Invoked once for any Widget activation change. This allows the menu
+ // to be canceled if the window manager changes the active window.
+ void OnWidgetActivationChanged();
+
+ private:
+ friend class internal::MenuRunnerImpl;
+ friend class MenuHostRootView;
+ friend class MenuItemView;
+ friend class SubmenuView;
+
+ class MenuScrollTask;
+
+ struct SelectByCharDetails;
+
+ // Values supplied to SetSelection.
+ enum SetSelectionTypes {
+ SELECTION_DEFAULT = 0,
+
+ // If set submenus are opened immediately, otherwise submenus are only
+ // openned after a timer fires.
+ SELECTION_UPDATE_IMMEDIATELY = 1 << 0,
+
+ // If set and the menu_item has a submenu, the submenu is shown.
+ SELECTION_OPEN_SUBMENU = 1 << 1,
+
+ // SetSelection is being invoked as the result exiting or cancelling the
+ // menu. This is used for debugging.
+ SELECTION_EXIT = 1 << 2,
+ };
+
+ // Tracks selection information.
+ struct State {
+ State();
+ ~State();
+
+ // 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 GetMenuPart 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), parent(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 and parent is the menu the mouse was
+ // clicked on.
+ MenuItemView* menu;
+
+ // If type is MENU_ITEM but the mouse is not over a menu item this is the
+ // parent of the menu item the user clicked on. Otherwise this is NULL.
+ MenuItemView* parent;
+
+ // This is the submenu the mouse is over.
+ SubmenuView* submenu;
+ };
+
+ // Sets the selection to menu_item a value of NULL unselects
+ // everything. |types| is a bitmask of |SetSelectionTypes|.
+ //
+ // Internally this updates pending_state_ immediatley. state_ is only updated
+ // immediately if SELECTION_UPDATE_IMMEDIATELY is set. If
+ // SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked
+ // to show/hide submenus and update state_.
+ void SetSelection(MenuItemView* menu_item, int types);
+
+#if defined(OS_WIN)
+ // 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) OVERRIDE;
+#elif defined(USE_WAYLAND)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(
+ base::wayland::WaylandEvent* event) OVERRIDE;
+#elif defined(USE_AURA)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(
+ XEvent* xevent) OVERRIDE;
+#else
+ virtual bool Dispatch(GdkEvent* event) OVERRIDE;
+#endif
+
+ // Key processing. The return value of this is returned from Dispatch.
+ // In other words, if this returns false (which happens if escape was
+ // pressed, or a matching mnemonic was found) the message loop returns.
+ bool OnKeyDown(ui::KeyboardCode key_code);
+
+ // Creates a MenuController. If |blocking| is true a nested message loop is
+ // started in |Run|.
+ MenuController(bool blocking, internal::MenuControllerDelegate* delegate);
+
+ virtual ~MenuController();
+
+ // If there is a hot tracked view AcceleratorPressed is invoked on it and
+ // true is returned.
+ bool SendAcceleratorToHotTrackedView();
+
+ void UpdateInitialLocation(const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition position);
+
+ // 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);
+
+ bool ShowSiblingMenu(SubmenuView* source, const MouseEvent& event);
+
+ // 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. The coordinates are in terms of
+ // source's scroll view container.
+ MenuPart GetMenuPart(SubmenuView* source, const gfx::Point& source_loc);
+
+ // Returns the target for mouse events. The search is done through |item| and
+ // all its parents.
+ MenuPart GetMenuPartByScreenCoordinateUsingMenu(MenuItemView* item,
+ const gfx::Point& screen_loc);
+
+ // 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);
+
+ // Implementation of OpenMenu. If |show| is true, this invokes show on the
+ // menu, otherwise Reposition is invoked.
+ void OpenMenuImpl(MenuItemView* item, bool show);
+
+ // Invoked when the children of a menu change and the menu is showing.
+ // This closes any submenus and resizes the submenu.
+ void MenuChildrenChanged(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);
+
+ // Returns the next selectable child menu item of |parent| starting at |index|
+ // and incrementing index by |delta|. If there are no more selected menu items
+ // NULL is returned.
+ MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
+ int index,
+ 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 details about which menu items match the mnemonic |key|.
+ // |match_function| is used to determine which menus match.
+ SelectByCharDetails FindChildForMnemonic(
+ MenuItemView* parent,
+ char16 key,
+ bool (*match_function)(MenuItemView* menu, char16 mnemonic));
+
+ // Selects or accepts the appropriate menu item based on |details|. Returns
+ // true if |Accept| was invoked (which happens if there aren't multiple item
+ // with the same mnemonic and the item to select does not have a submenu).
+ bool AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details);
+
+ // 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(char16 key);
+
+#if defined(OS_WIN)
+ // 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);
+#endif
+
+ // 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();
+
+ // Updates |active_mouse_view_| from the location of the event and sends it
+ // the appropriate events. This is used to send mouse events to child views so
+ // that they react to click-drag-release as if the user clicked on the view
+ // itself.
+ void UpdateActiveMouseView(SubmenuView* event_source,
+ const MouseEvent& event,
+ View* target_menu);
+
+ // Sends a mouse release event to the current |active_mouse_view_| and sets
+ // it to null.
+ void SendMouseReleaseToActiveView(SubmenuView* event_source,
+ const MouseEvent& event);
+
+ // Sends a mouse capture lost event to the current |active_mouse_view_| and
+ // sets it to null.
+ void SendMouseCaptureLostToActiveView();
+
+ // Set exit type.
+ void SetExitType(ExitType type);
+
+ // 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_;
+
+ // Indicates what to exit.
+ ExitType exit_type_;
+
+ // 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 mouse 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.
+ Widget* owner_;
+
+ // Indicates a possible drag operation.
+ bool possible_drag_;
+
+ // True when drag operation is in progress.
+ bool drag_in_progress_;
+
+ // Location the mouse was pressed at. Used to detect d&d.
+ gfx::Point press_pt_;
+
+ // 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_;
+ gfx::Point drop_pt_;
+ 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_;
+
+ MenuButton* menu_button_;
+
+ // If non-null mouse drag events are forwarded to this view. See
+ // UpdateActiveMouseView for details.
+ View* active_mouse_view_;
+
+ internal::MenuControllerDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuController);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_H_
diff --git a/ui/views/controls/menu/menu_controller_delegate.h b/ui/views/controls/menu/menu_controller_delegate.h
new file mode 100644
index 0000000..9d31c95
--- /dev/null
+++ b/ui/views/controls/menu/menu_controller_delegate.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_
+#pragma once
+
+namespace views {
+
+class MenuItemView;
+
+// This is internal as there should be no need for usage of this class outside
+// of views.
+namespace internal {
+
+// Used by MenuController to notify of interesting events that are intended for
+// the class using MenuController. This is implemented by MenuRunnerImpl.
+class MenuControllerDelegate {
+ public:
+ enum NotifyType {
+ NOTIFY_DELEGATE,
+ DONT_NOTIFY_DELEGATE
+ };
+
+ // Invoked when MenuController closes a menu and the MenuController was
+ // configured for drop (MenuRunner::FOR_DROP).
+ virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) = 0;
+
+ // Invoked when the MenuDelegate::GetSiblingMenu() returns non-NULL.
+ virtual void SiblingMenuCreated(MenuItemView* menu) = 0;
+
+ protected:
+ virtual ~MenuControllerDelegate() {}
+};
+
+} // namespace internal
+
+} // namespace view
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_CONTROLLER_DELEGATE_H_
diff --git a/ui/views/controls/menu/menu_delegate.cc b/ui/views/controls/menu/menu_delegate.cc
new file mode 100644
index 0000000..0663659
--- /dev/null
+++ b/ui/views/controls/menu/menu_delegate.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+
+namespace views {
+
+MenuDelegate::~MenuDelegate() {}
+
+bool MenuDelegate::IsItemChecked(int id) const {
+ return false;
+}
+
+string16 MenuDelegate::GetLabel(int id) const {
+ return string16();
+}
+
+const gfx::Font& MenuDelegate::GetLabelFont(int id) const {
+ return MenuConfig::instance().font;
+}
+
+string16 MenuDelegate::GetTooltipText(int id,
+ const gfx::Point& screen_loc) const {
+ return string16();
+}
+
+bool MenuDelegate::GetAccelerator(int id, ui::Accelerator* accelerator) {
+ return false;
+}
+
+bool MenuDelegate::ShowContextMenu(MenuItemView* source,
+ int id,
+ const gfx::Point& p,
+ bool is_mouse_gesture) {
+ return false;
+}
+
+bool MenuDelegate::SupportsCommand(int id) const {
+ return true;
+}
+
+bool MenuDelegate::IsCommandEnabled(int id) const {
+ return true;
+}
+
+bool MenuDelegate::GetContextualLabel(int id, string16* out) const {
+ return false;
+}
+
+bool MenuDelegate::ShouldCloseAllMenusOnExecute(int id) {
+ return true;
+}
+
+void MenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
+ ExecuteCommand(id);
+}
+
+bool MenuDelegate::IsTriggerableEvent(MenuItemView* source,
+ const MouseEvent& e) {
+ return e.IsLeftMouseButton() || e.IsRightMouseButton();
+}
+
+bool MenuDelegate::CanDrop(MenuItemView* menu, const OSExchangeData& data) {
+ return false;
+}
+
+bool MenuDelegate::GetDropFormats(
+ MenuItemView* menu,
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) {
+ return false;
+}
+
+bool MenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
+ return false;
+}
+
+int MenuDelegate::GetDropOperation(MenuItemView* item,
+ const DropTargetEvent& event,
+ DropPosition* position) {
+ NOTREACHED() << "If you override CanDrop, you need to override this too";
+ return ui::DragDropTypes::DRAG_NONE;
+}
+
+int MenuDelegate::OnPerformDrop(MenuItemView* menu,
+ DropPosition position,
+ const DropTargetEvent& event) {
+ NOTREACHED() << "If you override CanDrop, you need to override this too";
+ return ui::DragDropTypes::DRAG_NONE;
+}
+
+bool MenuDelegate::CanDrag(MenuItemView* menu) {
+ return false;
+}
+
+void MenuDelegate::WriteDragData(MenuItemView* sender, OSExchangeData* data) {
+ NOTREACHED() << "If you override CanDrag, you must override this too.";
+}
+
+int MenuDelegate::GetDragOperations(MenuItemView* sender) {
+ NOTREACHED() << "If you override CanDrag, you must override this too.";
+ return 0;
+}
+
+MenuItemView* MenuDelegate::GetSiblingMenu(MenuItemView* menu,
+ const gfx::Point& screen_point,
+ MenuItemView::AnchorPosition* anchor,
+ bool* has_mnemonics,
+ MenuButton** button) {
+ return NULL;
+}
+
+int MenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
+ // NOTE: this needs to be large enough to accommodate the wrench menu with
+ // big fonts.
+ return 800;
+}
+
+void MenuDelegate::WillShowMenu(MenuItemView* menu) {
+}
+
+void MenuDelegate::WillHideMenu(MenuItemView* menu) {
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_delegate.h b/ui/views/controls/menu/menu_delegate.h
new file mode 100644
index 0000000..a740a43
--- /dev/null
+++ b/ui/views/controls/menu/menu_delegate.h
@@ -0,0 +1,209 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_DELEGATE_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_DELEGATE_H_
+#pragma once
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/string16.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/events/event.h"
+
+using ui::OSExchangeData;
+
+namespace gfx {
+
+class Font;
+
+} // namespace gfx
+
+namespace ui {
+
+class Accelerator;
+
+} // namespace ui
+
+namespace views {
+
+class DropTargetEvent;
+class MenuButton;
+
+// MenuDelegate --------------------------------------------------------------
+
+// Delegate for a menu. This class is used as part of MenuItemView, see it
+// for details.
+// TODO(sky): merge this with ui::MenuModel.
+class VIEWS_EXPORT MenuDelegate {
+ public:
+ // Used during drag and drop to indicate where the drop indicator should
+ // be rendered.
+ enum DropPosition {
+ DROP_UNKNOWN = -1,
+
+ // 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
+ };
+
+ virtual ~MenuDelegate();
+
+ // Whether or not an item should be shown as checked. This is invoked for
+ // radio buttons and check buttons.
+ virtual bool IsItemChecked(int id) const;
+
+ // The string shown for the menu item. This is only invoked when an item is
+ // added with an empty label.
+ virtual string16 GetLabel(int id) const;
+
+ // The font for the menu item label.
+ virtual const gfx::Font& GetLabelFont(int id) const;
+
+ // The tooltip shown for the menu item. This is invoked when the user
+ // hovers over the item, and no tooltip text has been set for that item.
+ virtual string16 GetTooltipText(int id, const gfx::Point& screen_loc) const;
+
+ // If there is an accelerator for the menu item with id |id| it is set in
+ // |accelerator| and true is returned.
+ virtual bool GetAccelerator(int id, ui::Accelerator* accelerator);
+
+ // 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 |p| is the recommended
+ // location to display the content menu at. In either case, |p| is in
+ // screen coordinates.
+ // Returns true if a context menu was displayed, otherwise false
+ virtual bool ShowContextMenu(MenuItemView* source,
+ int id,
+ const gfx::Point& p,
+ bool is_mouse_gesture);
+
+ // Controller
+ virtual bool SupportsCommand(int id) const;
+ virtual bool IsCommandEnabled(int id) const;
+ virtual bool GetContextualLabel(int id, string16* out) const;
+ virtual void ExecuteCommand(int id) {
+ }
+
+ // If nested menus are showing (nested menus occur when a menu shows a context
+ // menu) this is invoked to determine if all the menus should be closed when
+ // the user selects the menu with the command |id|. This returns true to
+ // indicate that all menus should be closed. Return false if only the
+ // context menu should be closed.
+ virtual bool ShouldCloseAllMenusOnExecute(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);
+
+ // 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(MenuItemView* view, const MouseEvent& e);
+
+ // 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);
+
+ // See view for a description of this method.
+ virtual bool GetDropFormats(
+ MenuItemView* menu,
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats);
+
+ // See view for a description of this method.
+ virtual bool AreDropTypesRequired(MenuItemView* menu);
+
+ // 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 ui::DragDropTypes::DRAG_NONE.
+ virtual int GetDropOperation(MenuItemView* item,
+ const DropTargetEvent& event,
+ DropPosition* position);
+
+ // 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 ui::DragDropTypes::DRAG_NONE.
+ //
+ // menu indicates the menu the drop occurred on.
+ virtual int OnPerformDrop(MenuItemView* menu,
+ DropPosition position,
+ const DropTargetEvent& event);
+
+ // Invoked to determine if it is possible for the user to drag the specified
+ // menu item.
+ virtual bool CanDrag(MenuItemView* menu);
+
+ // Invoked to write the data for a drag operation to data. sender is the
+ // MenuItemView being dragged.
+ virtual void WriteDragData(MenuItemView* sender, OSExchangeData* data);
+
+ // Invoked to determine the drag operations for a drag session of sender.
+ // See DragDropTypes for possible values.
+ virtual int GetDragOperations(MenuItemView* sender);
+
+ // 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) {
+ }
+
+ // If the user drags the mouse outside the bounds of the menu the delegate
+ // is queried for a sibling menu to show. If this returns non-null the
+ // current menu is hidden, and the menu returned from this method is shown.
+ //
+ // The delegate owns the returned menu, not the controller.
+ virtual MenuItemView* GetSiblingMenu(MenuItemView* menu,
+ const gfx::Point& screen_point,
+ MenuItemView::AnchorPosition* anchor,
+ bool* has_mnemonics,
+ MenuButton** button);
+
+ // Returns the max width menus can grow to be.
+ virtual int GetMaxWidthForMenu(MenuItemView* menu);
+
+ // Invoked prior to a menu being shown.
+ virtual void WillShowMenu(MenuItemView* menu);
+
+ // Invoked prior to a menu being hidden.
+ virtual void WillHideMenu(MenuItemView* menu);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_DELEGATE_H_
diff --git a/ui/views/controls/menu/menu_gtk.cc b/ui/views/controls/menu/menu_gtk.cc
new file mode 100644
index 0000000..49972b2
--- /dev/null
+++ b/ui/views/controls/menu/menu_gtk.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_gtk.h"
+
+#include "base/logging.h"
+
+namespace views {
+
+// static
+Menu* Menu::Create(Delegate* delegate,
+ AnchorPoint anchor,
+ gfx::NativeView parent) {
+ return new MenuGtk(delegate, anchor, parent);
+}
+
+// static
+Menu* Menu::GetSystemMenu(gfx::NativeWindow parent) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+MenuGtk::MenuGtk(Delegate* d, AnchorPoint anchor, gfx::NativeView owner)
+ : Menu(d, anchor) {
+ DCHECK(delegate());
+}
+
+MenuGtk::~MenuGtk() {
+}
+
+Menu* MenuGtk::AddSubMenuWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void MenuGtk::AddSeparator(int index) {
+ NOTIMPLEMENTED();
+}
+
+void MenuGtk::EnableMenuItemByID(int item_id, bool enabled) {
+ NOTIMPLEMENTED();
+}
+
+void MenuGtk::EnableMenuItemAt(int index, bool enabled) {
+ NOTIMPLEMENTED();
+}
+
+void MenuGtk::SetMenuLabel(int item_id, const string16& label) {
+ NOTIMPLEMENTED();
+}
+
+bool MenuGtk::SetIcon(const SkBitmap& icon, int item_id) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void MenuGtk::RunMenuAt(int x, int y) {
+ NOTIMPLEMENTED();
+}
+
+void MenuGtk::Cancel() {
+ NOTIMPLEMENTED();
+}
+
+int MenuGtk::ItemCount() {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+void MenuGtk::AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ MenuItemType type) {
+ NOTIMPLEMENTED();
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_gtk.h b/ui/views/controls/menu/menu_gtk.h
new file mode 100644
index 0000000..0d9eeaf
--- /dev/null
+++ b/ui/views/controls/menu/menu_gtk.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_GTK_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_GTK_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/menu/menu.h"
+
+namespace views {
+
+class MenuGtk : public Menu {
+ public:
+ // 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.
+ MenuGtk(Delegate* d, AnchorPoint anchor, gfx::NativeView owner);
+ virtual ~MenuGtk();
+
+ // Overridden from Menu:
+ virtual Menu* AddSubMenuWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) OVERRIDE;
+ virtual void AddSeparator(int index) OVERRIDE;
+ virtual void EnableMenuItemByID(int item_id, bool enabled) OVERRIDE;
+ virtual void EnableMenuItemAt(int index, bool enabled) OVERRIDE;
+ virtual void SetMenuLabel(int item_id, const string16& label) OVERRIDE;
+ virtual bool SetIcon(const SkBitmap& icon, int item_id) OVERRIDE;
+ virtual void RunMenuAt(int x, int y) OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual int ItemCount() OVERRIDE;
+
+ protected:
+ virtual void AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ MenuItemType type) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuGtk);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_GTK_H_
diff --git a/ui/views/controls/menu/menu_host.cc b/ui/views/controls/menu/menu_host.cc
new file mode 100644
index 0000000..701056f
--- /dev/null
+++ b/ui/views/controls/menu/menu_host.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_host.h"
+
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/menu_host_root_view.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/native_menu_host.h"
+#include "ui/views/controls/menu/submenu_view.h"
+#include "ui/views/widget/native_widget_private.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// MenuHost, public:
+
+MenuHost::MenuHost(SubmenuView* submenu)
+ : submenu_(submenu),
+ destroying_(false),
+ ignore_capture_lost_(false) {
+}
+
+MenuHost::~MenuHost() {
+}
+
+void MenuHost::InitMenuHost(Widget* parent,
+ const gfx::Rect& bounds,
+ View* contents_view,
+ bool do_capture) {
+ Widget::InitParams params(Widget::InitParams::TYPE_MENU);
+ params.has_dropshadow = true;
+ params.parent_widget = parent;
+ params.bounds = bounds;
+ Init(params);
+ SetContentsView(contents_view);
+ ShowMenuHost(do_capture);
+}
+
+bool MenuHost::IsMenuHostVisible() {
+ return IsVisible();
+}
+
+void MenuHost::ShowMenuHost(bool do_capture) {
+ // Doing a capture may make us get capture lost. Ignore it while we're in the
+ // process of showing.
+ ignore_capture_lost_ = true;
+ Show();
+ if (do_capture)
+ native_widget_private()->SetMouseCapture();
+ ignore_capture_lost_ = false;
+}
+
+void MenuHost::HideMenuHost() {
+ ignore_capture_lost_ = true;
+ ReleaseMenuHostCapture();
+ Hide();
+ ignore_capture_lost_ = false;
+}
+
+void MenuHost::DestroyMenuHost() {
+ HideMenuHost();
+ destroying_ = true;
+ static_cast<MenuHostRootView*>(GetRootView())->ClearSubmenu();
+ Close();
+}
+
+void MenuHost::SetMenuHostBounds(const gfx::Rect& bounds) {
+ SetBounds(bounds);
+}
+
+void MenuHost::ReleaseMenuHostCapture() {
+ if (native_widget_private()->HasMouseCapture())
+ native_widget_private()->ReleaseMouseCapture();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MenuHost, Widget overrides:
+
+internal::RootView* MenuHost::CreateRootView() {
+ return new MenuHostRootView(this, submenu_);
+}
+
+bool MenuHost::ShouldReleaseCaptureOnMouseReleased() const {
+ return false;
+}
+
+void MenuHost::OnMouseCaptureLost() {
+ if (destroying_ || ignore_capture_lost_)
+ return;
+ MenuController* menu_controller =
+ submenu_->GetMenuItem()->GetMenuController();
+ if (menu_controller && !menu_controller->drag_in_progress())
+ menu_controller->CancelAll();
+ Widget::OnMouseCaptureLost();
+}
+
+void MenuHost::OnNativeWidgetDestroyed() {
+ if (!destroying_) {
+ // We weren't explicitly told to destroy ourselves, which means the menu was
+ // deleted out from under us (the window we're parented to was closed). Tell
+ // the SubmenuView to drop references to us.
+ submenu_->MenuHostDestroyed();
+ }
+ Widget::OnNativeWidgetDestroyed();
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_host.h b/ui/views/controls/menu/menu_host.h
new file mode 100644
index 0000000..bbf9242
--- /dev/null
+++ b/ui/views/controls/menu/menu_host.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_HOST_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_HOST_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/controls/menu/native_menu_host_delegate.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+class NativeWidget;
+class SubmenuView;
+class View;
+class Widget;
+
+// SubmenuView uses a MenuHost to house the SubmenuView.
+//
+// SubmenuView owns the MenuHost. When SubmenuView is done with the MenuHost
+// |DestroyMenuHost| is invoked. The one exception to this is if the native
+// OS destroys the widget out from under us, in which case |MenuHostDestroyed|
+// is invoked back on the SubmenuView and the SubmenuView then drops references
+// to the MenuHost.
+class MenuHost : public Widget {
+ public:
+ explicit MenuHost(SubmenuView* submenu);
+ virtual ~MenuHost();
+
+ // Initializes and shows the MenuHost.
+ void InitMenuHost(Widget* parent,
+ const gfx::Rect& bounds,
+ View* contents_view,
+ bool do_capture);
+
+ // Returns true if the menu host is visible.
+ bool IsMenuHostVisible();
+
+ // Shows the menu host. If |do_capture| is true the menu host should do a
+ // mouse grab.
+ void ShowMenuHost(bool do_capture);
+
+ // Hides the menu host.
+ void HideMenuHost();
+
+ // Destroys and deletes the menu host.
+ void DestroyMenuHost();
+
+ // Sets the bounds of the menu host.
+ void SetMenuHostBounds(const gfx::Rect& bounds);
+
+ // Releases a mouse grab installed by |ShowMenuHost|.
+ void ReleaseMenuHostCapture();
+
+ private:
+ // Overridden from Widget:
+ virtual internal::RootView* CreateRootView() OVERRIDE;
+ virtual bool ShouldReleaseCaptureOnMouseReleased() const OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual void OnNativeWidgetDestroyed() OVERRIDE;
+
+ // The view we contain.
+ SubmenuView* submenu_;
+
+ // If true, DestroyMenuHost has been invoked.
+ bool destroying_;
+
+ // If true and capture is lost we don't notify the delegate.
+ bool ignore_capture_lost_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHost);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_HOST_H_
diff --git a/ui/views/controls/menu/menu_host_root_view.cc b/ui/views/controls/menu/menu_host_root_view.cc
new file mode 100644
index 0000000..44ed97f
--- /dev/null
+++ b/ui/views/controls/menu/menu_host_root_view.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_host_root_view.h"
+
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+namespace views {
+
+MenuHostRootView::MenuHostRootView(Widget* widget,
+ SubmenuView* submenu)
+ : internal::RootView(widget),
+ submenu_(submenu),
+ forward_drag_to_menu_controller_(true) {
+}
+
+bool MenuHostRootView::OnMousePressed(const MouseEvent& event) {
+ forward_drag_to_menu_controller_ =
+ !GetLocalBounds().Contains(event.location()) ||
+ !RootView::OnMousePressed(event);
+ if (forward_drag_to_menu_controller_ && GetMenuController())
+ GetMenuController()->OnMousePressed(submenu_, event);
+ return true;
+}
+
+bool MenuHostRootView::OnMouseDragged(const MouseEvent& event) {
+ if (forward_drag_to_menu_controller_ && GetMenuController()) {
+ GetMenuController()->OnMouseDragged(submenu_, event);
+ return true;
+ }
+ return RootView::OnMouseDragged(event);
+}
+
+void MenuHostRootView::OnMouseReleased(const MouseEvent& event) {
+ RootView::OnMouseReleased(event);
+ if (forward_drag_to_menu_controller_ && GetMenuController()) {
+ forward_drag_to_menu_controller_ = false;
+ GetMenuController()->OnMouseReleased(submenu_, event);
+ }
+}
+
+void MenuHostRootView::OnMouseMoved(const MouseEvent& event) {
+ RootView::OnMouseMoved(event);
+ if (GetMenuController())
+ GetMenuController()->OnMouseMoved(submenu_, event);
+}
+
+bool MenuHostRootView::OnMouseWheel(const MouseWheelEvent& event) {
+#if defined(OS_LINUX)
+ // ChromeOS uses MenuController to forward events like other
+ // mouse events.
+ return GetMenuController() &&
+ GetMenuController()->OnMouseWheel(submenu_, event);
+#else
+ // Windows uses focus_util_win::RerouteMouseWheel to forward events to
+ // the right menu.
+ // RootView::OnMouseWheel 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(event);
+#endif
+}
+
+MenuController* MenuHostRootView::GetMenuController() {
+ return submenu_ ? submenu_->GetMenuItem()->GetMenuController() : NULL;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_host_root_view.h b/ui/views/controls/menu/menu_host_root_view.h
new file mode 100644
index 0000000..15f61bb
--- /dev/null
+++ b/ui/views/controls/menu/menu_host_root_view.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_
+#pragma once
+
+#include "ui/views/widget/root_view.h"
+
+namespace views {
+
+class MenuController;
+class SubmenuView;
+
+// 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 internal::RootView {
+ public:
+ MenuHostRootView(Widget* widget, SubmenuView* submenu);
+
+ void ClearSubmenu() { submenu_ = NULL; }
+
+ // Overridden from View:
+ virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE;
+ virtual void OnMouseMoved(const MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseWheel(const MouseWheelEvent& event) OVERRIDE;
+
+ private:
+ // Returns the MenuController for this MenuHostRootView.
+ MenuController* GetMenuController();
+
+ // The SubmenuView we contain.
+ SubmenuView* submenu_;
+
+ // Whether mouse dragged/released should be forwarded to the MenuController.
+ bool forward_drag_to_menu_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHostRootView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_
diff --git a/ui/views/controls/menu/menu_image_util.cc b/ui/views/controls/menu/menu_image_util.cc
new file mode 100644
index 0000000..061b3f5
--- /dev/null
+++ b/ui/views/controls/menu/menu_image_util.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_image_util.h"
+
+#include "base/i18n/rtl.h"
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/point.h"
+
+namespace {
+
+// Size of the radio button inciator.
+const int kSelectedIndicatorSize = 5;
+const int kIndicatorSize = 10;
+
+// Used for the radio indicator. See theme_draw for details.
+const double kGradientStop = .5;
+const SkColor kGradient0 = SkColorSetRGB(255, 255, 255);
+const SkColor kGradient1 = SkColorSetRGB(255, 255, 255);
+const SkColor kGradient2 = SkColorSetRGB(0xD8, 0xD8, 0xD8);
+const SkColor kBaseStroke = SkColorSetRGB(0x8F, 0x8F, 0x8F);
+const SkColor kRadioButtonIndicatorGradient0 = SkColorSetRGB(0, 0, 0);
+const SkColor kRadioButtonIndicatorGradient1 = SkColorSetRGB(0x83, 0x83, 0x83);
+const SkColor kIndicatorStroke = SkColorSetRGB(0, 0, 0);
+
+SkBitmap* CreateRadioButtonImage(bool selected) {
+ // + 2 (1px on each side) to cover rounding error.
+ gfx::CanvasSkia canvas(kIndicatorSize + 2, kIndicatorSize + 2, false);
+ canvas.Translate(gfx::Point(1, 1));
+
+ SkPoint gradient_points[3];
+ gradient_points[0].set(SkIntToScalar(0), SkIntToScalar(0));
+ gradient_points[1].set(
+ SkIntToScalar(0),
+ SkIntToScalar(static_cast<int>(kIndicatorSize * kGradientStop)));
+ gradient_points[2].set(SkIntToScalar(0), SkIntToScalar(kIndicatorSize));
+ SkColor gradient_colors[3] = { kGradient0, kGradient1, kGradient2 };
+ SkShader* shader = SkGradientShader::CreateLinear(
+ gradient_points, gradient_colors, NULL, arraysize(gradient_points),
+ SkShader::kClamp_TileMode, NULL);
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setAntiAlias(true);
+ paint.setShader(shader);
+ shader->unref();
+ int radius = kIndicatorSize / 2;
+ canvas.sk_canvas()->drawCircle(radius, radius, radius, paint);
+
+ paint.setStrokeWidth(SkIntToScalar(0));
+ paint.setShader(NULL);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(kBaseStroke);
+ canvas.sk_canvas()->drawCircle(radius, radius, radius, paint);
+
+ if (selected) {
+ SkPoint selected_gradient_points[2];
+ selected_gradient_points[0].set(SkIntToScalar(0), SkIntToScalar(0));
+ selected_gradient_points[1].set(
+ SkIntToScalar(0),
+ SkIntToScalar(kSelectedIndicatorSize));
+ SkColor selected_gradient_colors[2] = { kRadioButtonIndicatorGradient0,
+ kRadioButtonIndicatorGradient1 };
+ shader = SkGradientShader::CreateLinear(
+ selected_gradient_points, selected_gradient_colors, NULL,
+ arraysize(selected_gradient_points), SkShader::kClamp_TileMode, NULL);
+ paint.setShader(shader);
+ shader->unref();
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas.sk_canvas()->drawCircle(radius, radius,
+ kSelectedIndicatorSize / 2, paint);
+
+ paint.setStrokeWidth(SkIntToScalar(0));
+ paint.setShader(NULL);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(kIndicatorStroke);
+ canvas.sk_canvas()->drawCircle(radius, radius,
+ kSelectedIndicatorSize / 2, paint);
+ }
+ return new SkBitmap(canvas.ExtractBitmap());
+}
+
+SkBitmap* GetRtlSubmenuArrowImage() {
+ static SkBitmap* kRtlArrow = NULL;
+ if (!kRtlArrow) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SkBitmap* r = rb.GetBitmapNamed(IDR_MENU_ARROW);
+ gfx::CanvasSkia canvas(r->width(), r->height(), false);
+ canvas.Scale(-1, 1);
+ canvas.DrawBitmapInt(*r, - r->width(), 0);
+ kRtlArrow = new SkBitmap(canvas.ExtractBitmap());
+ }
+ return kRtlArrow;
+}
+
+} // namespace
+
+namespace views {
+
+const SkBitmap* GetRadioButtonImage(bool selected) {
+ static const SkBitmap* kRadioOn = CreateRadioButtonImage(true);
+ static const SkBitmap* kRadioOff = CreateRadioButtonImage(false);
+
+ return selected ? kRadioOn : kRadioOff;
+}
+
+const SkBitmap* GetSubmenuArrowImage() {
+ return base::i18n::IsRTL() ?
+ GetRtlSubmenuArrowImage() :
+ ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_MENU_ARROW);
+}
+
+} // namespace views;
diff --git a/ui/views/controls/menu/menu_image_util.h b/ui/views/controls/menu/menu_image_util.h
new file mode 100644
index 0000000..7d98035
--- /dev/null
+++ b/ui/views/controls/menu/menu_image_util.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_IMAGE_UTIL_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_IMAGE_UTIL_H_
+#pragma once
+
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace views {
+
+// Return the RadioButton image for given state.
+// It returns the "selected" image when |selected| is
+// true, or the "unselected" image if false.
+// The returned image is global object and should not be freed.
+const SkBitmap* GetRadioButtonImage(bool selected);
+
+// Returns the image for submenu arrow for current RTL setting.
+const SkBitmap* GetSubmenuArrowImage();
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_IMAGE_UTIL_H_
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
new file mode 100644
index 0000000..372beae
--- /dev/null
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -0,0 +1,779 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_item_view.h"
+
+#include "base/i18n/case_conversion.h"
+#include "base/stl_util.h"
+#include "base/utf_string_conversions.h"
+#include "grit/ui_strings.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/menu_separator.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+namespace views {
+
+namespace {
+
+// 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:
+ explicit EmptyMenuMenuItem(MenuItemView* parent)
+ : MenuItemView(parent, 0, EMPTY) {
+ // Set this so that we're not identified as a normal menu item.
+ set_id(kEmptyMenuItemViewID);
+ SetTitle(l10n_util::GetStringUTF16(IDS_APP_MENU_EMPTY_SUBMENU));
+ SetEnabled(false);
+ }
+
+ virtual bool GetTooltipText(const gfx::Point& p,
+ string16* tooltip) const OVERRIDE {
+ // Empty menu items shouldn't have a tooltip.
+ return false;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem);
+};
+
+} // namespace
+
+// Padding between child views.
+static const int kChildXPadding = 8;
+
+// MenuItemView ---------------------------------------------------------------
+
+// static
+const int MenuItemView::kMenuItemViewID = 1001;
+
+// static
+const int MenuItemView::kEmptyMenuItemViewID =
+ MenuItemView::kMenuItemViewID + 1;
+
+// static
+int MenuItemView::label_start_;
+
+// static
+int MenuItemView::item_right_margin_;
+
+// static
+int MenuItemView::pref_menu_height_;
+
+// static
+const char MenuItemView::kViewClassName[] = "views/MenuItemView";
+
+MenuItemView::MenuItemView(MenuDelegate* delegate)
+ : delegate_(delegate),
+ controller_(NULL),
+ canceled_(false),
+ parent_menu_item_(NULL),
+ type_(SUBMENU),
+ selected_(false),
+ command_(0),
+ submenu_(NULL),
+ has_mnemonics_(false),
+ show_mnemonics_(false),
+ has_icons_(false),
+ top_margin_(-1),
+ bottom_margin_(-1),
+ requested_menu_position_(POSITION_BEST_FIT),
+ actual_menu_position_(requested_menu_position_) {
+ // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes supplies a
+ // NULL delegate.
+ Init(NULL, 0, SUBMENU, delegate);
+}
+
+void MenuItemView::ChildPreferredSizeChanged(View* child) {
+ pref_size_.SetSize(0, 0);
+ PreferredSizeChanged();
+}
+
+bool MenuItemView::GetTooltipText(const gfx::Point& p,
+ string16* tooltip) const {
+ *tooltip = tooltip_;
+ if (!tooltip->empty())
+ return true;
+
+ if (GetType() == SEPARATOR)
+ return false;
+
+ const MenuController* controller = GetMenuController();
+ if (!controller || controller->exit_type() != MenuController::EXIT_NONE) {
+ // Either the menu has been closed or we're in the process of closing the
+ // menu. Don't attempt to query the delegate as it may no longer be valid.
+ return false;
+ }
+
+ const MenuItemView* root_menu_item = GetRootMenuItem();
+ if (root_menu_item->canceled_) {
+ // TODO(sky): if |canceled_| is true, controller->exit_type() should be
+ // something other than EXIT_NONE, but crash reports seem to indicate
+ // otherwise. Figure out why this is needed.
+ return false;
+ }
+
+ const MenuDelegate* delegate = GetDelegate();
+ CHECK(delegate);
+ gfx::Point location(p);
+ ConvertPointToScreen(this, &location);
+ *tooltip = delegate->GetTooltipText(command_, location);
+ return !tooltip->empty();
+}
+
+void MenuItemView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_MENUITEM;
+
+ string16 item_text;
+ if (IsContainer()) {
+ // The first child is taking over, just use its accessible name instead of
+ // |title_|.
+ View* child = child_at(0);
+ ui::AccessibleViewState state;
+ child->GetAccessibleState(&state);
+ item_text = state.name;
+ } else {
+ item_text = title_;
+ }
+ state->name = GetAccessibleNameForMenuItem(item_text, GetAcceleratorText());
+
+ switch (GetType()) {
+ case SUBMENU:
+ state->state |= ui::AccessibilityTypes::STATE_HASPOPUP;
+ break;
+ case CHECKBOX:
+ case RADIO:
+ state->state |= GetDelegate()->IsItemChecked(GetCommand()) ?
+ ui::AccessibilityTypes::STATE_CHECKED : 0;
+ break;
+ case NORMAL:
+ case SEPARATOR:
+ case EMPTY:
+ // No additional accessibility states currently for these menu states.
+ break;
+ }
+}
+
+// static
+string16 MenuItemView::GetAccessibleNameForMenuItem(
+ const string16& item_text, const string16& accelerator_text) {
+ string16 accessible_name = item_text;
+
+ // Filter out the "&" for accessibility clients.
+ size_t index = 0;
+ const char16 amp = '&';
+ while ((index = accessible_name.find(amp, index)) != string16::npos &&
+ index + 1 < accessible_name.length()) {
+ accessible_name.replace(index, accessible_name.length() - index,
+ accessible_name.substr(index + 1));
+
+ // Special case for "&&" (escaped for "&").
+ if (accessible_name[index] == '&')
+ ++index;
+ }
+
+ // Append accelerator text.
+ if (!accelerator_text.empty()) {
+ accessible_name.push_back(' ');
+ accessible_name.append(accelerator_text);
+ }
+
+ return accessible_name;
+}
+
+void MenuItemView::Cancel() {
+ if (controller_ && !canceled_) {
+ canceled_ = true;
+ controller_->Cancel(MenuController::EXIT_ALL);
+ }
+}
+
+MenuItemView* MenuItemView::AddMenuItemAt(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ Type type) {
+ DCHECK_NE(type, EMPTY);
+ DCHECK_LE(0, index);
+ if (!submenu_)
+ CreateSubmenu();
+ DCHECK_GE(submenu_->child_count(), index);
+ if (type == SEPARATOR) {
+ submenu_->AddChildViewAt(new MenuSeparator(), index);
+ 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_->AddChildViewAt(item, index);
+ return item;
+}
+
+void MenuItemView::RemoveMenuItemAt(int index) {
+ DCHECK(submenu_);
+ DCHECK_LE(0, index);
+ DCHECK_GT(submenu_->child_count(), index);
+
+ View* item = submenu_->child_at(index);
+ DCHECK(item);
+ submenu_->RemoveChildView(item);
+
+ // RemoveChildView() does not delete the item, which is a good thing
+ // in case a submenu is being displayed while items are being removed.
+ // Deletion will be done by ChildrenChanged() or at destruction.
+ removed_items_.push_back(item);
+}
+
+MenuItemView* MenuItemView::AppendMenuItem(int item_id,
+ const string16& label,
+ Type type) {
+ return AppendMenuItemImpl(item_id, label, SkBitmap(), type);
+}
+
+MenuItemView* MenuItemView::AppendSubMenu(int item_id,
+ const string16& label) {
+ return AppendMenuItemImpl(item_id, label, SkBitmap(), SUBMENU);
+}
+
+MenuItemView* MenuItemView::AppendSubMenuWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ return AppendMenuItemImpl(item_id, label, icon, SUBMENU);
+}
+
+MenuItemView* MenuItemView::AppendMenuItemWithLabel(int item_id,
+ const string16& label) {
+ return AppendMenuItem(item_id, label, NORMAL);
+}
+
+MenuItemView* MenuItemView::AppendDelegateMenuItem(int item_id) {
+ return AppendMenuItem(item_id, string16(), NORMAL);
+}
+
+void MenuItemView::AppendSeparator() {
+ AppendMenuItemImpl(0, string16(), SkBitmap(), SEPARATOR);
+}
+
+MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ return AppendMenuItemImpl(item_id, label, icon, NORMAL);
+}
+
+MenuItemView* MenuItemView::AppendMenuItemFromModel(ui::MenuModel* model,
+ int index,
+ int id) {
+ SkBitmap icon;
+ string16 label;
+ MenuItemView::Type type;
+ ui::MenuModel::ItemType menu_type = model->GetTypeAt(index);
+ switch (menu_type) {
+ case ui::MenuModel::TYPE_COMMAND:
+ model->GetIconAt(index, &icon);
+ type = MenuItemView::NORMAL;
+ label = model->GetLabelAt(index);
+ break;
+ case ui::MenuModel::TYPE_CHECK:
+ type = MenuItemView::CHECKBOX;
+ label = model->GetLabelAt(index);
+ break;
+ case ui::MenuModel::TYPE_RADIO:
+ type = MenuItemView::RADIO;
+ label = model->GetLabelAt(index);
+ break;
+ case ui::MenuModel::TYPE_SEPARATOR:
+ type = MenuItemView::SEPARATOR;
+ break;
+ case ui::MenuModel::TYPE_SUBMENU:
+ model->GetIconAt(index, &icon);
+ type = MenuItemView::SUBMENU;
+ label = model->GetLabelAt(index);
+ break;
+ default:
+ NOTREACHED();
+ type = MenuItemView::NORMAL;
+ break;
+ }
+
+ return AppendMenuItemImpl(id, label, icon, type);
+}
+
+MenuItemView* MenuItemView::AppendMenuItemImpl(int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ Type type) {
+ const int index = submenu_ ? submenu_->child_count() : 0;
+ return AddMenuItemAt(index, item_id, label, icon, type);
+}
+
+SubmenuView* MenuItemView::CreateSubmenu() {
+ if (!submenu_)
+ submenu_ = new SubmenuView(this);
+ return submenu_;
+}
+
+bool MenuItemView::HasSubmenu() const {
+ return (submenu_ != NULL);
+}
+
+SubmenuView* MenuItemView::GetSubmenu() const {
+ return submenu_;
+}
+
+void MenuItemView::SetTitle(const string16& title) {
+ title_ = title;
+ pref_size_.SetSize(0, 0); // Triggers preferred size recalculation.
+}
+
+void MenuItemView::SetSelected(bool selected) {
+ selected_ = selected;
+ SchedulePaint();
+}
+
+void MenuItemView::SetTooltip(const string16& tooltip, int item_id) {
+ MenuItemView* item = GetMenuItemByID(item_id);
+ DCHECK(item);
+ item->tooltip_ = tooltip;
+}
+
+void MenuItemView::SetIcon(const SkBitmap& icon, int item_id) {
+ MenuItemView* item = GetMenuItemByID(item_id);
+ DCHECK(item);
+ item->SetIcon(icon);
+}
+
+void MenuItemView::SetIcon(const SkBitmap& icon) {
+ icon_ = icon;
+ SchedulePaint();
+}
+
+void MenuItemView::OnPaint(gfx::Canvas* canvas) {
+ PaintButton(canvas, PB_NORMAL);
+}
+
+gfx::Size MenuItemView::GetPreferredSize() {
+ if (pref_size_.IsEmpty())
+ pref_size_ = CalculatePreferredSize();
+ return pref_size_;
+}
+
+MenuController* MenuItemView::GetMenuController() {
+ return GetRootMenuItem()->controller_;
+}
+
+const MenuController* MenuItemView::GetMenuController() const {
+ return GetRootMenuItem()->controller_;
+}
+
+MenuDelegate* MenuItemView::GetDelegate() {
+ return GetRootMenuItem()->delegate_;
+}
+
+const MenuDelegate* MenuItemView::GetDelegate() const {
+ return GetRootMenuItem()->delegate_;
+}
+
+MenuItemView* MenuItemView::GetRootMenuItem() {
+ return const_cast<MenuItemView*>(
+ static_cast<const MenuItemView*>(this)->GetRootMenuItem());
+}
+
+const MenuItemView* MenuItemView::GetRootMenuItem() const {
+ const MenuItemView* item = this;
+ for (const MenuItemView* parent = GetParentMenuItem(); parent;
+ parent = item->GetParentMenuItem())
+ item = parent;
+ return item;
+}
+
+char16 MenuItemView::GetMnemonic() {
+ if (!GetRootMenuItem()->has_mnemonics_)
+ return 0;
+
+ size_t index = 0;
+ do {
+ index = title_.find('&', index);
+ if (index != string16::npos) {
+ if (index + 1 != title_.size() && title_[index + 1] != '&') {
+ char16 char_array[] = { title_[index + 1], 0 };
+ // TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
+ // If the mnemonic is capital I and the UI language is Turkish,
+ // lowercasing it results in 'small dotless i', which is different
+ // from a 'dotted i'. Similar issues may exist for az and lt locales.
+ return base::i18n::ToLower(char_array)[0];
+ }
+ index++;
+ }
+ } while (index != string16::npos);
+ return 0;
+}
+
+MenuItemView* MenuItemView::GetMenuItemByID(int id) {
+ if (GetCommand() == id)
+ return this;
+ if (!HasSubmenu())
+ return NULL;
+ for (int i = 0; i < GetSubmenu()->child_count(); ++i) {
+ View* child = GetSubmenu()->child_at(i);
+ if (child->id() == MenuItemView::kMenuItemViewID) {
+ MenuItemView* result = static_cast<MenuItemView*>(child)->
+ GetMenuItemByID(id);
+ if (result)
+ return result;
+ }
+ }
+ return NULL;
+}
+
+void MenuItemView::ChildrenChanged() {
+ MenuController* controller = GetMenuController();
+ if (controller) {
+ // Handles the case where we were empty and are no longer empty.
+ RemoveEmptyMenus();
+
+ // Handles the case where we were not empty, but now are.
+ AddEmptyMenus();
+
+ controller->MenuChildrenChanged(this);
+
+ if (submenu_) {
+ // Force a paint and layout. This handles the case of the top
+ // level window's size remaining the same, resulting in no
+ // change to the submenu's size and no layout.
+ submenu_->Layout();
+ submenu_->SchedulePaint();
+ }
+ }
+
+ STLDeleteElements(&removed_items_);
+}
+
+void MenuItemView::Layout() {
+ if (!has_children())
+ return;
+
+ if (IsContainer()) {
+ View* child = child_at(0);
+ gfx::Size size = child->GetPreferredSize();
+ child->SetBounds(0, GetTopMargin(), size.width(), size.height());
+ } else {
+ // Child views are laid out right aligned and given the full height. To
+ // right align start with the last view and progress to the first.
+ for (int i = child_count() - 1, x = width() - item_right_margin_; i >= 0;
+ --i) {
+ View* child = child_at(i);
+ int width = child->GetPreferredSize().width();
+ child->SetBounds(x - width, 0, width, height());
+ x -= width - kChildXPadding;
+ }
+ }
+}
+
+int MenuItemView::GetAcceleratorTextWidth() {
+ string16 text = GetAcceleratorText();
+ return text.empty() ? 0 : GetFont().GetStringWidth(text);
+}
+
+void MenuItemView::SetMargins(int top_margin, int bottom_margin) {
+ top_margin_ = top_margin;
+ bottom_margin_ = bottom_margin;
+
+ // invalidate GetPreferredSize() cache
+ pref_size_.SetSize(0,0);
+}
+
+MenuItemView::MenuItemView(MenuItemView* parent,
+ int command,
+ MenuItemView::Type type)
+ : delegate_(NULL),
+ controller_(NULL),
+ canceled_(false),
+ parent_menu_item_(parent),
+ type_(type),
+ selected_(false),
+ command_(command),
+ submenu_(NULL),
+ has_mnemonics_(false),
+ show_mnemonics_(false),
+ has_icons_(false),
+ top_margin_(-1),
+ bottom_margin_(-1),
+ requested_menu_position_(POSITION_BEST_FIT),
+ actual_menu_position_(requested_menu_position_) {
+ Init(parent, command, type, NULL);
+}
+
+MenuItemView::~MenuItemView() {
+ delete submenu_;
+ STLDeleteElements(&removed_items_);
+}
+
+std::string MenuItemView::GetClassName() const {
+ return kViewClassName;
+}
+
+// Calculates all sizes that we can from the OS.
+//
+// This is invoked prior to Running a menu.
+void MenuItemView::UpdateMenuPartSizes(bool has_icons) {
+ MenuConfig::Reset();
+ const MenuConfig& config = MenuConfig::instance();
+
+ item_right_margin_ = config.label_to_arrow_padding + config.arrow_width +
+ config.arrow_to_edge_padding;
+
+ if (has_icons) {
+ label_start_ = config.item_left_margin + config.check_width +
+ config.icon_to_label_padding;
+ } 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_ = config.item_left_margin + config.check_width;
+ }
+ if (config.render_gutter)
+ label_start_ += config.gutter_width + config.gutter_to_label;
+
+ MenuItemView menu_item(NULL);
+ menu_item.SetTitle(ASCIIToUTF16("blah")); // Text doesn't matter here.
+ pref_menu_height_ = menu_item.GetPreferredSize().height();
+}
+
+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;
+ show_mnemonics_ = false;
+ // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
+ set_id(kMenuItemViewID);
+ has_icons_ = false;
+
+ // Don't request enabled status from the root menu item as it is just
+ // a container for real items. EMPTY items will be disabled.
+ MenuDelegate* root_delegate = GetDelegate();
+ if (parent && type != EMPTY && root_delegate)
+ SetEnabled(root_delegate->IsCommandEnabled(command));
+}
+
+void MenuItemView::PrepareForRun(bool has_mnemonics, bool show_mnemonics) {
+ // Currently we only support showing the root.
+ DCHECK(!parent_menu_item_);
+
+ // Force us to have a submenu.
+ CreateSubmenu();
+ actual_menu_position_ = requested_menu_position_;
+ canceled_ = false;
+
+ has_mnemonics_ = has_mnemonics;
+ show_mnemonics_ = has_mnemonics && show_mnemonics;
+
+ AddEmptyMenus();
+
+ if (!MenuController::GetActiveInstance()) {
+ // Only update the menu size if there are no menus showing, otherwise
+ // things may shift around.
+ UpdateMenuPartSizes(has_icons_);
+ }
+}
+
+int MenuItemView::GetDrawStringFlags() {
+ int flags = 0;
+ if (base::i18n::IsRTL())
+ flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
+ else
+ flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
+
+ if (has_mnemonics_) {
+ if (MenuConfig::instance().show_mnemonics ||
+ GetRootMenuItem()->show_mnemonics_) {
+ flags |= gfx::Canvas::SHOW_PREFIX;
+ } else {
+ flags |= gfx::Canvas::HIDE_PREFIX;
+ }
+ }
+ return flags;
+}
+
+const gfx::Font& MenuItemView::GetFont() {
+ // Check for item-specific font.
+ const MenuDelegate* delegate = GetDelegate();
+ return delegate ?
+ delegate->GetLabelFont(GetCommand()) : MenuConfig::instance().font;
+}
+
+void MenuItemView::AddEmptyMenus() {
+ DCHECK(HasSubmenu());
+ if (!submenu_->has_children()) {
+ submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0);
+ } 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_->child_count() - 1; i >= 0; --i) {
+ View* child = submenu_->child_at(i);
+ if (child->id() == MenuItemView::kMenuItemViewID) {
+ MenuItemView* menu_item = static_cast<MenuItemView*>(child);
+ if (menu_item->HasSubmenu())
+ menu_item->RemoveEmptyMenus();
+ } else if (child->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID) {
+ submenu_->RemoveChildView(child);
+ delete child;
+ child = NULL;
+ }
+ }
+}
+
+void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
+ rect->set_x(GetMirroredXForRect(*rect));
+}
+
+void MenuItemView::PaintAccelerator(gfx::Canvas* canvas) {
+ string16 accel_text = GetAcceleratorText();
+ if (accel_text.empty())
+ return;
+
+ const gfx::Font& font = GetFont();
+ int available_height = height() - GetTopMargin() - GetBottomMargin();
+ int max_accel_width =
+ parent_menu_item_->GetSubmenu()->max_accelerator_width();
+ gfx::Rect accel_bounds(width() - item_right_margin_ - max_accel_width,
+ GetTopMargin(), max_accel_width, available_height);
+ accel_bounds.set_x(GetMirroredXForRect(accel_bounds));
+ int flags = GetRootMenuItem()->GetDrawStringFlags() |
+ gfx::Canvas::TEXT_VALIGN_MIDDLE;
+ flags &= ~(gfx::Canvas::TEXT_ALIGN_RIGHT | gfx::Canvas::TEXT_ALIGN_LEFT);
+ if (base::i18n::IsRTL())
+ flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
+ else
+ flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
+ canvas->DrawStringInt(
+ accel_text, font, TextButton::kDisabledColor,
+ accel_bounds.x(), accel_bounds.y(), accel_bounds.width(),
+ accel_bounds.height(), flags);
+}
+
+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() {
+ if (top_margin_ >= 0)
+ return top_margin_;
+
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_
+ ? MenuConfig::instance().item_top_margin :
+ MenuConfig::instance().item_no_icon_top_margin;
+}
+
+int MenuItemView::GetBottomMargin() {
+ if (bottom_margin_ >= 0)
+ return bottom_margin_;
+
+ MenuItemView* root = GetRootMenuItem();
+ return root && root->has_icons_
+ ? MenuConfig::instance().item_bottom_margin :
+ MenuConfig::instance().item_no_icon_bottom_margin;
+}
+
+gfx::Size MenuItemView::GetChildPreferredSize() {
+ if (!has_children())
+ return gfx::Size();
+
+ if (IsContainer()) {
+ View* child = child_at(0);
+ return child->GetPreferredSize();
+ }
+
+ int width = 0;
+ for (int i = 0; i < child_count(); ++i) {
+ if (i)
+ width += kChildXPadding;
+ width += child_at(i)->GetPreferredSize().width();
+ }
+ // Return a height of 0 to indicate that we should use the title height
+ // instead.
+ return gfx::Size(width, 0);
+}
+
+gfx::Size MenuItemView::CalculatePreferredSize() {
+ gfx::Size child_size = GetChildPreferredSize();
+ if (IsContainer()) {
+ return gfx::Size(
+ child_size.width(),
+ child_size.height() + GetBottomMargin() + GetTopMargin());
+ }
+
+ const gfx::Font& font = GetFont();
+ int height = font.GetHeight();
+ return gfx::Size(
+ font.GetStringWidth(title_) + label_start_ +
+ item_right_margin_ + child_size.width(),
+ std::max(height, child_size.height()) + GetBottomMargin() +
+ GetTopMargin());
+}
+
+string16 MenuItemView::GetAcceleratorText() {
+ if (id() == kEmptyMenuItemViewID) {
+ // Don't query the delegate for menus that represent no children.
+ return string16();
+ }
+
+ if(!MenuConfig::instance().show_accelerators)
+ return string16();
+
+ ui::Accelerator accelerator;
+ return (GetDelegate() &&
+ GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) ?
+ accelerator.GetShortcutText() : string16();
+}
+
+bool MenuItemView::IsContainer() const {
+ // Let the first child take over |this| when we only have one child and no
+ // title. Note that what child_count() returns is the number of children,
+ // not the number of menu items.
+ return child_count() == 1 && title_.empty();
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_item_view.h b/ui/views/controls/menu/menu_item_view.h
new file mode 100644
index 0000000..28481e8
--- /dev/null
+++ b/ui/views/controls/menu/menu_item_view.h
@@ -0,0 +1,473 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/string16.h"
+#include "build/build_config.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "views/view.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "ui/gfx/native_theme.h"
+#endif
+
+namespace gfx {
+class Font;
+}
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+
+namespace internal {
+class MenuRunnerImpl;
+}
+
+struct MenuConfig;
+class MenuController;
+class MenuDelegate;
+class SubmenuView;
+
+// 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 is normally NOT want you want, rather add other child
+// Views to the submenu of the MenuItemView. Any child views of the MenuItemView
+// that are focusable can be navigated to by way of the up/down arrow and can be
+// activated by way of space/return keys. Activating a focusable child results
+// in |AcceleratorPressed| being invoked. Note, that as menus try not to steal
+// focus from the hosting window child views do not actually get focus. Instead
+// |SetHotTracked| is used as the user navigates around.
+//
+// To show the menu use MenuRunner. See MenuRunner for details on how to run
+// (show) the menu as well as for details on the life time of the menu.
+
+class VIEWS_EXPORT MenuItemView : public View {
+ public:
+ friend class MenuController;
+
+ // The menu item view's class name.
+ static const char kViewClassName[];
+
+ // ID used to identify menu items.
+ static const int kMenuItemViewID;
+
+ // ID used to identify empty menu items.
+ static const int kEmptyMenuItemViewID;
+
+ // Different types of menu items. EMPTY is a special type for empty
+ // menus that is only used internally.
+ enum Type {
+ NORMAL,
+ SUBMENU,
+ CHECKBOX,
+ RADIO,
+ SEPARATOR,
+ EMPTY
+ };
+
+ // Where the menu should be anchored to for non-RTL languages. The
+ // opposite position will be used if base::i18n:IsRTL() is true.
+ enum AnchorPosition {
+ TOPLEFT,
+ TOPRIGHT
+ };
+
+ // Where the menu should be drawn, above or below the bounds (when
+ // the bounds is non-empty). POSITION_BEST_FIT (default) positions
+ // the menu below the bounds unless the menu does not fit on the
+ // screen and the re is more space above.
+ enum MenuPosition {
+ POSITION_BEST_FIT,
+ POSITION_ABOVE_BOUNDS,
+ POSITION_BELOW_BOUNDS
+ };
+
+ // 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);
+
+ // Overridden from View:
+ virtual bool GetTooltipText(const gfx::Point& p,
+ string16* tooltip) const OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // Returns the preferred height of menu items. This is only valid when the
+ // menu is about to be shown.
+ static int pref_menu_height() { return pref_menu_height_; }
+
+ // X-coordinate of where the label starts.
+ static int label_start() { return label_start_; }
+
+ // Returns the accessible name to be used with screen readers. Mnemonics are
+ // removed and the menu item accelerator text is appended.
+ static string16 GetAccessibleNameForMenuItem(
+ const string16& item_text, const string16& accelerator_text);
+
+ // Hides and cancels the menu. This does nothing if the menu is not open.
+ void Cancel();
+
+ // Add an item to the menu at a specified index. ChildrenChanged() should
+ // called after adding menu items if the menu may be active.
+ MenuItemView* AddMenuItemAt(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ Type type);
+
+ // Remove an item from the menu at a specified index.
+ // ChildrenChanged() should be called after removing menu items (whether
+ // the menu may be active or not).
+ void RemoveMenuItemAt(int index);
+
+ // Appends 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.
+ MenuItemView* AppendMenuItem(int item_id,
+ const string16& label,
+ Type type);
+
+ // Append a submenu to this menu.
+ // The returned pointer is owned by this menu.
+ MenuItemView* AppendSubMenu(int item_id,
+ const string16& label);
+
+ // Append a submenu with an icon to this menu.
+ // The returned pointer is owned by this menu.
+ MenuItemView* AppendSubMenuWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon);
+
+ // This is a convenience for standard text label menu items where the label
+ // is provided with this call.
+ MenuItemView* AppendMenuItemWithLabel(int item_id,
+ const string16& label);
+
+ // This is a convenience for text label menu items where the label is
+ // provided by the delegate.
+ MenuItemView* AppendDelegateMenuItem(int item_id);
+
+ // Adds a separator to this menu
+ void AppendSeparator();
+
+ // 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.
+ MenuItemView* AppendMenuItemWithIcon(int item_id,
+ const string16& label,
+ const SkBitmap& icon);
+
+ // Creates a menu item for the specified entry in the model and appends it as
+ // a child. |index| should be offset by GetFirstItemIndex() before calling
+ // this function.
+ MenuItemView* AppendMenuItemFromModel(ui::MenuModel* model,
+ int index,
+ int id);
+
+ // All the AppendXXX methods funnel into this.
+ MenuItemView* AppendMenuItemImpl(int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ Type type);
+
+ // 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;
+
+ // Returns the view containing child menu items.
+ virtual SubmenuView* GetSubmenu() const;
+
+ // Returns the parent menu item.
+ MenuItemView* GetParentMenuItem() { return parent_menu_item_; }
+ const MenuItemView* GetParentMenuItem() const { return parent_menu_item_; }
+
+ // Sets/Gets the title.
+ void SetTitle(const string16& title);
+ const string16& title() const { return title_; }
+
+ // Returns the type of this menu.
+ const Type& GetType() const { return type_; }
+
+ // 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 |tooltip| for a menu item view with |item_id| identifier.
+ void SetTooltip(const string16& tooltip, int item_id);
+
+ // 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 OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Returns the preferred size of this item.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Returns the object responsible for controlling showing the menu.
+ MenuController* GetMenuController();
+ const MenuController* GetMenuController() const;
+
+ // Returns the delegate. This returns the delegate of the root menu item.
+ MenuDelegate* GetDelegate();
+ const MenuDelegate* GetDelegate() const;
+ void set_delegate(MenuDelegate* delegate) { delegate_ = delegate; }
+
+ // Returns the root parent, or this if this has no parent.
+ MenuItemView* GetRootMenuItem();
+ const MenuItemView* GetRootMenuItem() const;
+
+ // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView
+ // doesn't have a mnemonic.
+ char16 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;
+ }
+
+ // Returns the descendant with the specified command.
+ MenuItemView* GetMenuItemByID(int id);
+
+ // Invoke if you remove/add children to the menu while it's showing. This
+ // recalculates the bounds.
+ void ChildrenChanged();
+
+ // Sizes any child views.
+ virtual void Layout() OVERRIDE;
+
+ // Returns the amount of space needed to accomodate the accelerator. The
+ // space needed for the accelerator is NOT included in the preferred width.
+ int GetAcceleratorTextWidth();
+
+ // Returns true if the menu has mnemonics. This only useful on the root menu
+ // item.
+ bool has_mnemonics() const { return has_mnemonics_; }
+
+ // Set top and bottom margins in pixels. If no margin is set or a
+ // negative margin is specified then MenuConfig values are used.
+ void SetMargins(int top_margin, int bottom_margin);
+
+ // Set the position of the menu with respect to the bounds (top
+ // level only).
+ void set_menu_position(MenuPosition menu_position) {
+ requested_menu_position_ = menu_position;
+ }
+
+ protected:
+ // Creates a MenuItemView. This is used by the various AddXXX methods.
+ MenuItemView(MenuItemView* parent, int command, Type type);
+
+ // MenuRunner owns MenuItemView and should be the only one deleting it.
+ virtual ~MenuItemView();
+
+ virtual void ChildPreferredSizeChanged(View* child) OVERRIDE;
+
+ virtual std::string GetClassName() const OVERRIDE;
+
+ private:
+ friend class internal::MenuRunnerImpl; // For access to ~MenuItemView.
+
+ // Calculates all sizes that we can from the OS.
+ //
+ // This is invoked prior to Running a menu.
+ static void UpdateMenuPartSizes(bool has_icons);
+
+ // Called by the two constructors to initialize this menu item.
+ void Init(MenuItemView* parent,
+ int command,
+ MenuItemView::Type type,
+ MenuDelegate* delegate);
+
+ // The RunXXX methods call into this to set up the necessary state before
+ // running.
+ void PrepareForRun(bool has_mnemonics, bool show_mnemonics);
+
+ // Returns the flags passed to DrawStringInt.
+ int GetDrawStringFlags();
+
+ // Returns the font to use for menu text.
+ const gfx::Font& GetFont();
+
+ // If this menu item has no children a child is added showing it has no
+ // children. Otherwise AddEmtpyMenus 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(gfx::Rect* rect) const;
+
+ // Actual paint implementation. If mode is PB_FOR_DRAG, portions of the menu
+ // are not rendered.
+ enum PaintButtonMode { PB_NORMAL, PB_FOR_DRAG };
+ void PaintButton(gfx::Canvas* canvas, PaintButtonMode mode);
+
+#if defined(OS_WIN)
+ enum SelectionState { SELECTED, UNSELECTED };
+
+ // Paints the check/radio button indicator.
+ void PaintCheck(gfx::Canvas* canvas,
+ gfx::NativeTheme::State state,
+ SelectionState selection_state,
+ const MenuConfig& config);
+#endif
+
+ // Paints the accelerator.
+ void PaintAccelerator(gfx::Canvas* canvas);
+
+ // Destroys the window used to display this menu and recursively destroys
+ // the windows used to display all descendants.
+ void DestroyAllMenuHosts();
+
+ // Returns the accelerator text.
+ string16 GetAcceleratorText();
+
+ // Returns the various margins.
+ int GetTopMargin();
+ int GetBottomMargin();
+
+ // Returns the preferred size (and padding) of any children.
+ gfx::Size GetChildPreferredSize();
+
+ // Calculates the preferred size.
+ gfx::Size CalculatePreferredSize();
+
+ // Used by MenuController to cache the menu position in use by the
+ // active menu.
+ MenuPosition actual_menu_position() const { return actual_menu_position_; }
+ void set_actual_menu_position(MenuPosition actual_menu_position) {
+ actual_menu_position_ = actual_menu_position;
+ }
+
+ void set_controller(MenuController* controller) { controller_ = controller; }
+
+ // Returns true if this MenuItemView contains a single child
+ // that is responsible for rendering the content.
+ bool IsContainer() const;
+
+ // 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_;
+
+ // 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_;
+
+ // Title.
+ string16 title_;
+
+ // Icon.
+ SkBitmap icon_;
+
+ // Does the title have a mnemonic? Only useful on the root menu item.
+ bool has_mnemonics_;
+
+ // Should we show the mnemonic? Mnemonics are shown if this is true or
+ // MenuConfig says mnemonics should be shown. Only used on the root menu item.
+ bool show_mnemonics_;
+
+ bool has_icons_;
+
+ // The tooltip to show on hover for this menu item.
+ string16 tooltip_;
+
+ // X-coordinate of where the label starts.
+ static int label_start_;
+
+ // Margins between the right of the item and the label.
+ static int item_right_margin_;
+
+ // Preferred height of menu items. Reset every time a menu is run.
+ static int pref_menu_height_;
+
+ // Previously calculated preferred size to reduce GetStringWidth calls in
+ // GetPreferredSize.
+ gfx::Size pref_size_;
+
+ // Removed items to be deleted in ChildrenChanged().
+ std::vector<View*> removed_items_;
+
+ // Margins in pixels.
+ int top_margin_;
+ int bottom_margin_;
+
+ // |menu_position_| is the requested position with respect to the bounds.
+ // |actual_menu_position_| is used by the controller to cache the
+ // position of the menu being shown.
+ MenuPosition requested_menu_position_;
+ MenuPosition actual_menu_position_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuItemView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
diff --git a/ui/views/controls/menu/menu_item_view_aura.cc b/ui/views/controls/menu/menu_item_view_aura.cc
new file mode 100644
index 0000000..33a8dff
--- /dev/null
+++ b/ui/views/controls/menu/menu_item_view_aura.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_item_view.h"
+
+#include "base/utf_string_conversions.h"
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_image_util.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+namespace views {
+
+// Background color when the menu item is selected.
+static const SkColor kSelectedBackgroundColor = SkColorSetRGB(246, 249, 253);
+
+void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
+ const MenuConfig& config = MenuConfig::instance();
+ bool render_selection =
+ (mode == PB_NORMAL && IsSelected() &&
+ parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
+ !has_children());
+
+ int icon_x = config.item_left_margin;
+ int top_margin = GetTopMargin();
+ int bottom_margin = GetBottomMargin();
+ int icon_y = top_margin + (height() - config.item_top_margin -
+ bottom_margin - config.check_height) / 2;
+ int icon_height = config.check_height;
+ int available_height = height() - top_margin - bottom_margin;
+
+ // Render the background. As MenuScrollViewContainer draws the background, we
+ // only need the background when we want it to look different, as when we're
+ // selected.
+ if (render_selection)
+ canvas->GetSkCanvas()->drawColor(kSelectedBackgroundColor,
+ SkXfermode::kSrc_Mode);
+
+ // Render the check.
+ if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SkBitmap* check = rb.GetBitmapNamed(IDR_MENU_CHECK);
+ // Don't use config.check_width here as it's padded to force more padding.
+ gfx::Rect check_bounds(icon_x, icon_y, check->width(), icon_height);
+ AdjustBoundsForRTLUI(&check_bounds);
+ canvas->DrawBitmapInt(*check, check_bounds.x(), check_bounds.y());
+ } else if (type_ == RADIO) {
+ const SkBitmap* image =
+ GetRadioButtonImage(GetDelegate()->IsItemChecked(GetCommand()));
+ gfx::Rect radio_bounds(icon_x,
+ top_margin +
+ (height() - top_margin - bottom_margin -
+ image->height()) / 2,
+ image->width(),
+ image->height());
+ AdjustBoundsForRTLUI(&radio_bounds);
+ canvas->DrawBitmapInt(*image, radio_bounds.x(), radio_bounds.y());
+ }
+
+ // Render the foreground.
+ SkColor fg_color =
+ IsEnabled() ? TextButton::kEnabledColor : TextButton::kDisabledColor;
+ const gfx::Font& font = GetFont();
+ int accel_width = parent_menu_item_->GetSubmenu()->max_accelerator_width();
+ int width = this->width() - item_right_margin_ - label_start_ - accel_width;
+ gfx::Rect text_bounds(label_start_, top_margin +
+ (available_height - font.GetHeight()) / 2, width,
+ font.GetHeight());
+ text_bounds.set_x(GetMirroredXForRect(text_bounds));
+ canvas->DrawStringInt(title(), font, fg_color,
+ text_bounds.x(), text_bounds.y(), text_bounds.width(),
+ text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+
+ PaintAccelerator(canvas);
+
+ // Render the icon.
+ if (icon_.width() > 0) {
+ gfx::Rect icon_bounds(config.item_left_margin,
+ top_margin + (height() - top_margin -
+ bottom_margin - icon_.height()) / 2,
+ icon_.width(),
+ icon_.height());
+ icon_bounds.set_x(GetMirroredXForRect(icon_bounds));
+ canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y());
+ }
+
+ // Render the submenu indicator (arrow).
+ if (HasSubmenu()) {
+ gfx::Rect arrow_bounds(this->width() - item_right_margin_ +
+ config.label_to_arrow_padding,
+ top_margin + (available_height -
+ config.arrow_width) / 2,
+ config.arrow_width, height());
+ AdjustBoundsForRTLUI(&arrow_bounds);
+ canvas->DrawBitmapInt(*GetSubmenuArrowImage(),
+ arrow_bounds.x(), arrow_bounds.y());
+ }
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_item_view_linux.cc b/ui/views/controls/menu/menu_item_view_linux.cc
new file mode 100644
index 0000000..a9e738d
--- /dev/null
+++ b/ui/views/controls/menu/menu_item_view_linux.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_item_view.h"
+
+#include "base/utf_string_conversions.h"
+#include "grit/ui_resources.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_image_util.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+namespace views {
+
+// Background color when the menu item is selected.
+#if defined(OS_CHROMEOS)
+static const SkColor kSelectedBackgroundColor = SkColorSetRGB(0xDC, 0xE4, 0xFA);
+#else
+static const SkColor kSelectedBackgroundColor = SkColorSetRGB(246, 249, 253);
+#endif
+
+void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
+ const MenuConfig& config = MenuConfig::instance();
+ bool render_selection =
+ (mode == PB_NORMAL && IsSelected() &&
+ parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
+ !has_children());
+
+ int icon_x = config.item_left_margin;
+ int top_margin = GetTopMargin();
+ int bottom_margin = GetBottomMargin();
+ int icon_y = top_margin + (height() - config.item_top_margin -
+ bottom_margin - config.check_height) / 2;
+ int icon_height = config.check_height;
+ int available_height = height() - top_margin - bottom_margin;
+
+ // Render the background. As MenuScrollViewContainer draws the background, we
+ // only need the background when we want it to look different, as when we're
+ // selected.
+ if (render_selection)
+ canvas->GetSkCanvas()->drawColor(kSelectedBackgroundColor,
+ SkXfermode::kSrc_Mode);
+
+ // Render the check.
+ if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SkBitmap* check = rb.GetBitmapNamed(IDR_MENU_CHECK);
+ // Don't use config.check_width here as it's padded to force more padding.
+ gfx::Rect check_bounds(icon_x, icon_y, check->width(), icon_height);
+ AdjustBoundsForRTLUI(&check_bounds);
+ canvas->DrawBitmapInt(*check, check_bounds.x(), check_bounds.y());
+ } else if (type_ == RADIO) {
+ const SkBitmap* image =
+ GetRadioButtonImage(GetDelegate()->IsItemChecked(GetCommand()));
+ gfx::Rect radio_bounds(icon_x,
+ top_margin +
+ (height() - top_margin - bottom_margin -
+ image->height()) / 2,
+ image->width(),
+ image->height());
+ AdjustBoundsForRTLUI(&radio_bounds);
+ canvas->DrawBitmapInt(*image, radio_bounds.x(), radio_bounds.y());
+ }
+
+ // Render the foreground.
+#if defined(OS_CHROMEOS)
+ SkColor fg_color =
+ IsEnabled() ? SK_ColorBLACK : SkColorSetRGB(0x80, 0x80, 0x80);
+#else
+ SkColor fg_color =
+ IsEnabled() ? TextButton::kEnabledColor : TextButton::kDisabledColor;
+#endif
+ const gfx::Font& font = GetFont();
+ int accel_width = parent_menu_item_->GetSubmenu()->max_accelerator_width();
+ int width = this->width() - item_right_margin_ - label_start_ - accel_width;
+ gfx::Rect text_bounds(label_start_, top_margin +
+ (available_height - font.GetHeight()) / 2, width,
+ font.GetHeight());
+ text_bounds.set_x(GetMirroredXForRect(text_bounds));
+ canvas->DrawStringInt(title(), font, fg_color,
+ text_bounds.x(), text_bounds.y(), text_bounds.width(),
+ text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+
+ PaintAccelerator(canvas);
+
+ // Render the icon.
+ if (icon_.width() > 0) {
+ gfx::Rect icon_bounds(config.item_left_margin,
+ top_margin + (height() - top_margin -
+ bottom_margin - icon_.height()) / 2,
+ icon_.width(),
+ icon_.height());
+ icon_bounds.set_x(GetMirroredXForRect(icon_bounds));
+ canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y());
+ }
+
+ // Render the submenu indicator (arrow).
+ if (HasSubmenu()) {
+ gfx::Rect arrow_bounds(this->width() - item_right_margin_ +
+ config.label_to_arrow_padding,
+ top_margin + (available_height -
+ config.arrow_width) / 2,
+ config.arrow_width, height());
+ AdjustBoundsForRTLUI(&arrow_bounds);
+ canvas->DrawBitmapInt(*GetSubmenuArrowImage(),
+ arrow_bounds.x(), arrow_bounds.y());
+ }
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_item_view_win.cc b/ui/views/controls/menu/menu_item_view_win.cc
new file mode 100644
index 0000000..b50a295
--- /dev/null
+++ b/ui/views/controls/menu/menu_item_view_win.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_item_view.h"
+
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+#include "grit/ui_strings.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/native_theme_win.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+using gfx::NativeTheme;
+
+namespace views {
+
+void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
+ const MenuConfig& config = MenuConfig::instance();
+ bool render_selection =
+ (mode == PB_NORMAL && IsSelected() &&
+ parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
+ !has_children());
+ int default_sys_color;
+ int state;
+ NativeTheme::State control_state;
+
+ if (IsEnabled()) {
+ if (render_selection) {
+ control_state = NativeTheme::kHovered;
+ state = MPI_HOT;
+ default_sys_color = COLOR_HIGHLIGHTTEXT;
+ } else {
+ control_state = NativeTheme::kNormal;
+ state = MPI_NORMAL;
+ default_sys_color = COLOR_MENUTEXT;
+ }
+ } else {
+ state = MPI_DISABLED;
+ default_sys_color = COLOR_GRAYTEXT;
+ control_state = NativeTheme::kDisabled;
+ }
+
+ // The gutter is rendered before the background.
+ if (config.render_gutter && mode == PB_NORMAL) {
+ gfx::Rect gutter_bounds(label_start_ - config.gutter_to_label -
+ config.gutter_width, 0, config.gutter_width,
+ height());
+ AdjustBoundsForRTLUI(&gutter_bounds);
+ NativeTheme::ExtraParams extra;
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuPopupGutter,
+ NativeTheme::kNormal,
+ gutter_bounds,
+ extra);
+ }
+
+ // Render the background.
+ if (mode == PB_NORMAL) {
+ gfx::Rect item_bounds(0, 0, width(), height());
+ NativeTheme::ExtraParams extra;
+ extra.menu_item.is_selected = render_selection;
+ AdjustBoundsForRTLUI(&item_bounds);
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuItemBackground, control_state, item_bounds, extra);
+ }
+
+ int top_margin = GetTopMargin();
+ int bottom_margin = GetBottomMargin();
+
+ if ((type_ == RADIO || type_ == CHECKBOX) &&
+ GetDelegate()->IsItemChecked(GetCommand())) {
+ PaintCheck(canvas, control_state, render_selection ? SELECTED : UNSELECTED,
+ config);
+ }
+
+ // Render the foreground.
+ // Menu color is specific to Vista, fallback to classic colors if can't
+ // get color.
+ SkColor fg_color = gfx::NativeThemeWin::instance()->GetThemeColorWithDefault(
+ gfx::NativeThemeWin::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR,
+ default_sys_color);
+ const gfx::Font& font = GetFont();
+ int accel_width = parent_menu_item_->GetSubmenu()->max_accelerator_width();
+ int width = this->width() - item_right_margin_ - label_start_ - accel_width;
+ gfx::Rect text_bounds(label_start_, top_margin, width, font.GetHeight());
+ text_bounds.set_x(GetMirroredXForRect(text_bounds));
+ if (mode == PB_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->AsCanvasSkia()->DrawStringWithHalo(
+ title(), font, 0x00000000, 0xFFFFFFFF, text_bounds.x(),
+ text_bounds.y(), text_bounds.width(), text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+ } else {
+ canvas->DrawStringInt(title(), font, fg_color,
+ text_bounds.x(), text_bounds.y(), text_bounds.width(),
+ text_bounds.height(),
+ GetRootMenuItem()->GetDrawStringFlags());
+ }
+
+ PaintAccelerator(canvas);
+
+ if (icon_.width() > 0) {
+ gfx::Rect icon_bounds(config.item_left_margin,
+ top_margin + (height() - top_margin -
+ bottom_margin - icon_.height()) / 2,
+ icon_.width(),
+ icon_.height());
+ icon_bounds.set_x(GetMirroredXForRect(icon_bounds));
+ canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y());
+ }
+
+ if (HasSubmenu()) {
+ int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED;
+ gfx::Rect arrow_bounds(this->width() - item_right_margin_ +
+ config.label_to_arrow_padding, 0,
+ config.arrow_width, height());
+ 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.
+ gfx::NativeTheme::ExtraParams extra;
+ extra.menu_arrow.pointing_right = !base::i18n::IsRTL();
+ extra.menu_arrow.is_selected = render_selection;
+ gfx::NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ gfx::NativeTheme::kMenuPopupArrow, control_state, arrow_bounds, extra);
+ }
+}
+
+void MenuItemView::PaintCheck(gfx::Canvas* canvas,
+ NativeTheme::State state,
+ SelectionState selection_state,
+ const MenuConfig& config) {
+ int icon_width;
+ int icon_height;
+ if (type_ == RADIO) {
+ icon_width = config.radio_width;
+ icon_height = config.radio_height;
+ } else {
+ icon_width = config.check_width;
+ icon_height = config.check_height;
+ }
+
+ int top_margin = GetTopMargin();
+ int icon_x = MenuConfig::instance().item_left_margin;
+ int icon_y = top_margin +
+ (height() - top_margin - GetBottomMargin() - icon_height) / 2;
+ NativeTheme::ExtraParams extra;
+ extra.menu_check.is_radio = type_ == RADIO;
+ extra.menu_check.is_selected = selection_state == SELECTED;
+
+ // Draw the background.
+ gfx::Rect bg_bounds(0, 0, icon_x + icon_width, height());
+ AdjustBoundsForRTLUI(&bg_bounds);
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuCheckBackground, state, bg_bounds, extra);
+
+ // And the check.
+ gfx::Rect icon_bounds(icon_x / 2, icon_y, icon_width, icon_height);
+ AdjustBoundsForRTLUI(&icon_bounds);
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuCheck, state, bg_bounds, extra);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_listener.h b/ui/views/controls/menu/menu_listener.h
new file mode 100644
index 0000000..37eb8c0
--- /dev/null
+++ b/ui/views/controls/menu/menu_listener.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_LISTENER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_LISTENER_H_
+#pragma once
+
+#include "views/views_export.h"
+
+namespace views {
+
+// An interface for clients that want a notification when a menu is opened.
+class VIEWS_EXPORT MenuListener {
+ public:
+ // This will be called after the menu has actually opened.
+ virtual void OnMenuOpened() = 0;
+
+ protected:
+ virtual ~MenuListener() {}
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_LISTENER_H_
diff --git a/ui/views/controls/menu/menu_model_adapter.cc b/ui/views/controls/menu/menu_model_adapter.cc
new file mode 100644
index 0000000..3848093
--- /dev/null
+++ b/ui/views/controls/menu/menu_model_adapter.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_model_adapter.h"
+
+#include "base/logging.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/views/controls/menu/submenu_view.h"
+#include "views/views_delegate.h"
+
+namespace views {
+
+MenuModelAdapter::MenuModelAdapter(ui::MenuModel* menu_model)
+ : menu_model_(menu_model),
+ triggerable_event_flags_(ui::EF_LEFT_BUTTON_DOWN |
+ ui::EF_RIGHT_BUTTON_DOWN) {
+ DCHECK(menu_model);
+}
+
+MenuModelAdapter::~MenuModelAdapter() {
+}
+
+void MenuModelAdapter::BuildMenu(MenuItemView* menu) {
+ DCHECK(menu);
+
+ // Clear the menu.
+ if (menu->HasSubmenu()) {
+ const int subitem_count = menu->GetSubmenu()->child_count();
+ for (int i = 0; i < subitem_count; ++i)
+ menu->RemoveMenuItemAt(0);
+ }
+
+ // Leave entries in the map if the menu is being shown. This
+ // allows the map to find the menu model of submenus being closed
+ // so ui::MenuModel::MenuClosed() can be called.
+ if (!menu->GetMenuController())
+ menu_map_.clear();
+ menu_map_[menu] = menu_model_;
+
+ // Repopulate the menu.
+ BuildMenuImpl(menu, menu_model_);
+ menu->ChildrenChanged();
+}
+
+MenuItemView* MenuModelAdapter::CreateMenu() {
+ MenuItemView* item = new MenuItemView(this);
+ BuildMenu(item);
+ return item;
+}
+
+// MenuModelAdapter, MenuDelegate implementation:
+
+void MenuModelAdapter::ExecuteCommand(int id) {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
+ model->ActivatedAt(index);
+ return;
+ }
+
+ NOTREACHED();
+}
+
+void MenuModelAdapter::ExecuteCommand(int id, int mouse_event_flags) {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
+ model->ActivatedAt(index, mouse_event_flags);
+ return;
+ }
+
+ NOTREACHED();
+}
+
+bool MenuModelAdapter::IsTriggerableEvent(MenuItemView* source,
+ const MouseEvent& e) {
+ return (triggerable_event_flags_ & e.flags()) != 0;
+}
+
+bool MenuModelAdapter::GetAccelerator(int id,
+ ui::Accelerator* accelerator) {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
+ return model->GetAcceleratorAt(index, accelerator);
+
+ NOTREACHED();
+ return false;
+}
+
+string16 MenuModelAdapter::GetLabel(int id) const {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
+ return model->GetLabelAt(index);
+
+ NOTREACHED();
+ return string16();
+}
+
+const gfx::Font& MenuModelAdapter::GetLabelFont(int id) const {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
+ const gfx::Font* font = model->GetLabelFontAt(index);
+ return font ? *font : MenuDelegate::GetLabelFont(id);
+ }
+
+ // This line may be reached for the empty menu item.
+ return MenuDelegate::GetLabelFont(id);
+}
+
+bool MenuModelAdapter::IsCommandEnabled(int id) const {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
+ return model->IsEnabledAt(index);
+
+ NOTREACHED();
+ return false;
+}
+
+bool MenuModelAdapter::IsItemChecked(int id) const {
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
+ return model->IsItemCheckedAt(index);
+
+ NOTREACHED();
+ return false;
+}
+
+void MenuModelAdapter::SelectionChanged(MenuItemView* menu) {
+ // Ignore selection of the root menu.
+ if (menu == menu->GetRootMenuItem())
+ return;
+
+ const int id = menu->GetCommand();
+ ui::MenuModel* model = menu_model_;
+ int index = 0;
+ if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
+ model->HighlightChangedTo(index);
+ return;
+ }
+
+ NOTREACHED();
+}
+
+void MenuModelAdapter::WillShowMenu(MenuItemView* menu) {
+ // Look up the menu model for this menu.
+ const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
+ menu_map_.find(menu);
+ if (map_iterator != menu_map_.end()) {
+ map_iterator->second->MenuWillShow();
+ return;
+ }
+
+ NOTREACHED();
+}
+
+void MenuModelAdapter::WillHideMenu(MenuItemView* menu) {
+ // Look up the menu model for this menu.
+ const std::map<MenuItemView*, ui::MenuModel*>::const_iterator map_iterator =
+ menu_map_.find(menu);
+ if (map_iterator != menu_map_.end()) {
+ map_iterator->second->MenuClosed();
+ return;
+ }
+
+ NOTREACHED();
+}
+
+// MenuModelAdapter, private:
+
+void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) {
+ DCHECK(menu);
+ DCHECK(model);
+ const int item_count = model->GetItemCount();
+ for (int i = 0; i < item_count; ++i) {
+ const int index = i + model->GetFirstItemIndex(NULL);
+ MenuItemView* item = menu->AppendMenuItemFromModel(
+ model, index, model->GetCommandIdAt(index));
+
+ if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SUBMENU) {
+ DCHECK(item);
+ DCHECK_EQ(MenuItemView::SUBMENU, item->GetType());
+ ui::MenuModel* submodel = model->GetSubmenuModelAt(index);
+ DCHECK(submodel);
+ BuildMenuImpl(item, submodel);
+
+ menu_map_[item] = submodel;
+ }
+ }
+
+ menu->set_has_icons(model->HasIcons());
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_model_adapter.h b/ui/views/controls/menu/menu_model_adapter.h
new file mode 100644
index 0000000..6bc9e51
--- /dev/null
+++ b/ui/views/controls/menu/menu_model_adapter.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
+#pragma once
+
+#include <map>
+
+#include "ui/views/controls/menu/menu_delegate.h"
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+class MenuItemView;
+
+// This class wraps an instance of ui::MenuModel with the
+// views::MenuDelegate interface required by views::MenuItemView.
+class VIEWS_EXPORT MenuModelAdapter : public MenuDelegate {
+ public:
+ // The caller retains ownership of the ui::MenuModel instance and
+ // must ensure it exists for the lifetime of the adapter.
+ explicit MenuModelAdapter(ui::MenuModel* menu_model);
+ virtual ~MenuModelAdapter();
+
+ // Populate a MenuItemView menu with the ui::MenuModel items
+ // (including submenus).
+ virtual void BuildMenu(MenuItemView* menu);
+
+ // Convenience for creating and populating a menu. The caller owns the
+ // returned MenuItemView.
+ MenuItemView* CreateMenu();
+
+ void set_triggerable_event_flags(int triggerable_event_flags) {
+ triggerable_event_flags_ = triggerable_event_flags;
+ }
+ int triggerable_event_flags() const { return triggerable_event_flags_; }
+
+ protected:
+ // views::MenuDelegate implementation.
+ virtual void ExecuteCommand(int id) OVERRIDE;
+ virtual void ExecuteCommand(int id, int mouse_event_flags) OVERRIDE;
+ virtual bool IsTriggerableEvent(MenuItemView* source,
+ const MouseEvent& e) OVERRIDE;
+ virtual bool GetAccelerator(int id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual string16 GetLabel(int id) const OVERRIDE;
+ virtual const gfx::Font& GetLabelFont(int id) const OVERRIDE;
+ virtual bool IsCommandEnabled(int id) const OVERRIDE;
+ virtual bool IsItemChecked(int id) const OVERRIDE;
+ virtual void SelectionChanged(MenuItemView* menu) OVERRIDE;
+ virtual void WillShowMenu(MenuItemView* menu) OVERRIDE;
+ virtual void WillHideMenu(MenuItemView* menu) OVERRIDE;
+
+ private:
+ // Implementation of BuildMenu(). index_offset is both input and output;
+ // on input it contains the offset from index to command id for the model,
+ // and on output it contains the offset for the next model.
+ void BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model);
+
+ // Container of ui::MenuModel pointers as encountered by preorder
+ // traversal. The first element is always the top-level model
+ // passed to the constructor.
+ ui::MenuModel* menu_model_;
+
+ // Mouse event flags which can trigger menu actions.
+ int triggerable_event_flags_;
+
+ // Map MenuItems to MenuModels. Used to implement WillShowMenu().
+ std::map<MenuItemView*, ui::MenuModel*> menu_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuModelAdapter);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_MODEL_ADAPTER_H_
diff --git a/ui/views/controls/menu/menu_model_adapter_unittest.cc b/ui/views/controls/menu/menu_model_adapter_unittest.cc
new file mode 100644
index 0000000..4b35883
--- /dev/null
+++ b/ui/views/controls/menu/menu_model_adapter_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (c) 2011 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 "base/utf_string_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/base/models/menu_model_delegate.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/menu/submenu_view.h"
+#include "ui/views/test/views_test_base.h"
+
+namespace {
+
+// Base command id for test menu and its submenu.
+const int kRootIdBase = 100;
+const int kSubmenuIdBase = 200;
+
+// Offset to return for GetFirstItemIndex(). This is an arbitrary
+// number to ensure that we aren't assuming it is 0.
+const int kFirstItemIndex = 25;
+
+class MenuModelBase : public ui::MenuModel {
+ public:
+ MenuModelBase(int command_id_base) : command_id_base_(command_id_base),
+ last_activation_(-1) {
+ }
+
+ virtual ~MenuModelBase() {
+ }
+
+ // ui::MenuModel implementation:
+
+ virtual bool HasIcons() const OVERRIDE {
+ return false;
+ }
+
+ virtual int GetFirstItemIndex(gfx::NativeMenu native_menu) const OVERRIDE {
+ return kFirstItemIndex;
+ }
+
+ virtual int GetItemCount() const OVERRIDE {
+ return static_cast<int>(items_.size());
+ }
+
+ virtual ItemType GetTypeAt(int index) const OVERRIDE {
+ return items_[index - GetFirstItemIndex(NULL)].type;
+ }
+
+ virtual int GetCommandIdAt(int index) const OVERRIDE {
+ return index - GetFirstItemIndex(NULL) + command_id_base_;
+ }
+
+ string16 GetLabelAt(int index) const OVERRIDE {
+ return items_[index - GetFirstItemIndex(NULL)].label;
+ }
+
+ virtual bool IsItemDynamicAt(int index) const OVERRIDE {
+ return false;
+ }
+
+ virtual const gfx::Font* GetLabelFontAt(int index) const OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool GetAcceleratorAt(int index,
+ ui::Accelerator* accelerator) const OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsItemCheckedAt(int index) const OVERRIDE {
+ return false;
+ }
+
+ virtual int GetGroupIdAt(int index) const OVERRIDE {
+ return 0;
+ }
+
+ virtual bool GetIconAt(int index, SkBitmap* icon) OVERRIDE {
+ return false;
+ }
+
+ virtual ui::ButtonMenuItemModel* GetButtonMenuItemAt(
+ int index) const OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool IsEnabledAt(int index) const OVERRIDE {
+ return true;
+ }
+
+ virtual bool IsVisibleAt(int index) const OVERRIDE {
+ return true;
+ }
+
+ virtual MenuModel* GetSubmenuModelAt(int index) const OVERRIDE {
+ return items_[index - GetFirstItemIndex(NULL)].submenu;
+ }
+
+ virtual void HighlightChangedTo(int index) OVERRIDE {
+ }
+
+ virtual void ActivatedAt(int index) OVERRIDE {
+ set_last_activation(index);
+ }
+
+ virtual void ActivatedAt(int index, int event_flags) OVERRIDE {
+ ActivatedAt(index);
+ }
+
+ virtual void MenuWillShow() OVERRIDE {
+ }
+
+ virtual void MenuClosed() OVERRIDE {
+ }
+
+ virtual void SetMenuModelDelegate(
+ ui::MenuModelDelegate* delegate) OVERRIDE {
+ }
+
+ // Item definition.
+ struct Item {
+ Item(ItemType item_type,
+ const std::string& item_label,
+ ui::MenuModel* item_submenu)
+ : type(item_type),
+ label(ASCIIToUTF16(item_label)),
+ submenu(item_submenu) {
+ }
+
+ ItemType type;
+ string16 label;
+ ui::MenuModel* submenu;
+ };
+
+ const Item& GetItemDefinition(int index) {
+ return items_[index];
+ }
+
+ // Access index argument to ActivatedAt().
+ int last_activation() const { return last_activation_; }
+ void set_last_activation(int last_activation) {
+ last_activation_ = last_activation;
+ }
+
+ protected:
+ std::vector<Item> items_;
+
+ private:
+ int command_id_base_;
+ int last_activation_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuModelBase);
+};
+
+class SubmenuModel : public MenuModelBase {
+ public:
+ SubmenuModel() : MenuModelBase(kSubmenuIdBase) {
+ items_.push_back(Item(TYPE_COMMAND, "submenu item 0", NULL));
+ items_.push_back(Item(TYPE_COMMAND, "submenu item 1", NULL));
+ }
+
+ virtual ~SubmenuModel() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SubmenuModel);
+};
+
+class RootModel : public MenuModelBase {
+ public:
+ RootModel() : MenuModelBase(kRootIdBase) {
+ submenu_model_.reset(new SubmenuModel);
+
+ items_.push_back(Item(TYPE_COMMAND, "command 0", NULL));
+ items_.push_back(Item(TYPE_CHECK, "check 1", NULL));
+ items_.push_back(Item(TYPE_SEPARATOR, "", NULL));
+ items_.push_back(Item(TYPE_SUBMENU, "submenu 3", submenu_model_.get()));
+ items_.push_back(Item(TYPE_RADIO, "radio 4", NULL));
+ }
+
+ virtual ~RootModel() {
+ }
+
+ private:
+ scoped_ptr<MenuModel> submenu_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(RootModel);
+};
+
+} // namespace
+
+namespace views {
+
+typedef ViewsTestBase MenuModelAdapterTest;
+
+TEST_F(MenuModelAdapterTest, BasicTest) {
+ // Build model and adapter.
+ RootModel model;
+ views::MenuModelAdapter delegate(&model);
+
+ // Create menu. Build menu twice to check that rebuilding works properly.
+ MenuItemView* menu = new views::MenuItemView(&delegate);
+ // MenuRunner takes ownership of menu.
+ scoped_ptr<MenuRunner> menu_runner(new MenuRunner(menu));
+ delegate.BuildMenu(menu);
+ delegate.BuildMenu(menu);
+ EXPECT_TRUE(menu->HasSubmenu());
+
+ // Check top level menu items.
+ views::SubmenuView* item_container = menu->GetSubmenu();
+ EXPECT_EQ(5, item_container->child_count());
+
+ for (int i = 0; i < item_container->child_count(); ++i) {
+ const MenuModelBase::Item& model_item = model.GetItemDefinition(i);
+
+ const int id = i + kRootIdBase;
+ MenuItemView* item = menu->GetMenuItemByID(id);
+ if (!item) {
+ EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
+ continue;
+ }
+
+ // Check placement.
+ EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item));
+
+ // Check type.
+ switch (model_item.type) {
+ case ui::MenuModel::TYPE_COMMAND:
+ EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_CHECK:
+ EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_RADIO:
+ EXPECT_EQ(views::MenuItemView::RADIO, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_SEPARATOR:
+ case ui::MenuModel::TYPE_BUTTON_ITEM:
+ break;
+ case ui::MenuModel::TYPE_SUBMENU:
+ EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
+ break;
+ }
+
+ // Check activation.
+ static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
+ EXPECT_EQ(i + kFirstItemIndex, model.last_activation());
+ model.set_last_activation(-1);
+ }
+
+ // Check submenu items.
+ views::MenuItemView* submenu = menu->GetMenuItemByID(103);
+ views::SubmenuView* subitem_container = submenu->GetSubmenu();
+ EXPECT_EQ(2, subitem_container->child_count());
+
+ for (int i = 0; i < subitem_container->child_count(); ++i) {
+ MenuModelBase* submodel = static_cast<MenuModelBase*>(
+ model.GetSubmenuModelAt(3 + kFirstItemIndex));
+ EXPECT_TRUE(submodel);
+
+ const MenuModelBase::Item& model_item = submodel->GetItemDefinition(i);
+
+ const int id = i + kSubmenuIdBase;
+ MenuItemView* item = menu->GetMenuItemByID(id);
+ if (!item) {
+ EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR, model_item.type);
+ continue;
+ }
+
+ // Check placement.
+ EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item));
+
+ // Check type.
+ switch (model_item.type) {
+ case ui::MenuModel::TYPE_COMMAND:
+ EXPECT_EQ(views::MenuItemView::NORMAL, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_CHECK:
+ EXPECT_EQ(views::MenuItemView::CHECKBOX, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_RADIO:
+ EXPECT_EQ(views::MenuItemView::RADIO, item->GetType());
+ break;
+ case ui::MenuModel::TYPE_SEPARATOR:
+ case ui::MenuModel::TYPE_BUTTON_ITEM:
+ break;
+ case ui::MenuModel::TYPE_SUBMENU:
+ EXPECT_EQ(views::MenuItemView::SUBMENU, item->GetType());
+ break;
+ }
+
+ // Check activation.
+ static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id);
+ EXPECT_EQ(i + kFirstItemIndex, submodel->last_activation());
+ submodel->set_last_activation(-1);
+ }
+
+ // Check that selecting the root item is safe. The MenuModel does
+ // not care about the root so MenuModelAdapter should do nothing
+ // (not hit the NOTREACHED check) when the root is selected.
+ static_cast<views::MenuDelegate*>(&delegate)->SelectionChanged(menu);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_runner.cc b/ui/views/controls/menu/menu_runner.cc
new file mode 100644
index 0000000..db33ad0
--- /dev/null
+++ b/ui/views/controls/menu/menu_runner.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_runner.h"
+
+#include <set>
+
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/menu_controller_delegate.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+
+#if defined(OS_WIN)
+#include "base/win/win_util.h"
+#endif
+
+namespace views {
+
+namespace internal {
+
+// Manages the menu. To destroy a MenuRunnerImpl invoke Release(). Release()
+// deletes immediately if the menu isn't showing. If the menu is showing
+// Release() cancels the menu and when the nested RunMenuAt() call returns
+// deletes itself and the menu.
+class MenuRunnerImpl : public internal::MenuControllerDelegate {
+ public:
+ explicit MenuRunnerImpl(MenuItemView* menu);
+
+ MenuItemView* menu() { return menu_; }
+
+ // See description above class for details.
+ void Release();
+
+ // Runs the menu.
+ MenuRunner::RunResult RunMenuAt(Widget* parent,
+ MenuButton* button,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition anchor,
+ int32 types) WARN_UNUSED_RESULT;
+
+ void Cancel();
+
+ // MenuControllerDelegate:
+ virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) OVERRIDE;
+ virtual void SiblingMenuCreated(MenuItemView* menu) OVERRIDE;
+
+ private:
+ ~MenuRunnerImpl();
+
+ // Cleans up after the menu is no longer showing. |result| is the menu that
+ // the user selected, or NULL if nothing was selected.
+ MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags);
+
+ // Returns true if mnemonics should be shown in the menu.
+ bool ShouldShowMnemonics(MenuButton* button);
+
+ // The menu. We own this. We don't use scoped_ptr as the destructor is
+ // protected and we're a friend.
+ MenuItemView* menu_;
+
+ // Any sibling menus. Does not include |menu_|. We own these too.
+ std::set<MenuItemView*> sibling_menus_;
+
+ // Created and set as the delegate of the MenuItemView if Release() is
+ // invoked. This is done to make sure the delegate isn't notified after
+ // Release() is invoked. We do this as we assume the delegate is no longer
+ // valid if MenuRunner has been deleted.
+ scoped_ptr<MenuDelegate> empty_delegate_;
+
+ // Are we in run waiting for it to return?
+ bool running_;
+
+ // Set if |running_| and Release() has been invoked.
+ bool delete_after_run_;
+
+ // Are we running for a drop?
+ bool for_drop_;
+
+ // The controller.
+ MenuController* controller_;
+
+ // Do we own the controller?
+ bool owns_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuRunnerImpl);
+};
+
+MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
+ : menu_(menu),
+ running_(false),
+ delete_after_run_(false),
+ for_drop_(false),
+ controller_(NULL),
+ owns_controller_(false) {
+}
+
+void MenuRunnerImpl::Release() {
+ if (running_) {
+ if (delete_after_run_)
+ return; // We already canceled.
+
+ // The menu is running a nested message loop, we can't delete it now
+ // otherwise the stack would be in a really bad state (many frames would
+ // have deleted objects on them). Instead cancel the menu, when it returns
+ // Holder will delete itself.
+ delete_after_run_ = true;
+
+ // Swap in a different delegate. That way we know the original MenuDelegate
+ // won't be notified later on (when it's likely already been deleted).
+ if (!empty_delegate_.get())
+ empty_delegate_.reset(new MenuDelegate());
+ menu_->set_delegate(empty_delegate_.get());
+
+ DCHECK(controller_);
+ // Release is invoked when MenuRunner is destroyed. Assume this is happening
+ // because the object referencing the menu has been destroyed and the menu
+ // button is no longer valid.
+ controller_->Cancel(MenuController::EXIT_DESTROYED);
+ } else {
+ delete this;
+ }
+}
+
+MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(
+ Widget* parent,
+ MenuButton* button,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition anchor,
+ int32 types) {
+ if (running_) {
+ // Ignore requests to show the menu while it's already showing. MenuItemView
+ // doesn't handle this very well (meaning it crashes).
+ return MenuRunner::NORMAL_EXIT;
+ }
+
+ MenuController* controller = MenuController::GetActiveInstance();
+ if (controller) {
+ if ((types & MenuRunner::IS_NESTED) != 0) {
+ if (!controller->IsBlockingRun()) {
+ controller->CancelAll();
+ controller = NULL;
+ }
+ } else {
+ // There's some other menu open and we're not nested. Cancel the menu.
+ controller->CancelAll();
+ if ((types & MenuRunner::FOR_DROP) == 0) {
+ // We can't open another menu, otherwise the message loop would become
+ // twice nested. This isn't necessarily a problem, but generally isn't
+ // expected.
+ return MenuRunner::NORMAL_EXIT;
+ }
+ // Drop menus don't block the message loop, so it's ok to create a new
+ // MenuController.
+ controller = NULL;
+ }
+ }
+ running_ = true;
+ for_drop_ = (types & MenuRunner::FOR_DROP) != 0;
+ bool has_mnemonics = (types & MenuRunner::HAS_MNEMONICS) != 0 && !for_drop_;
+ menu_->PrepareForRun(has_mnemonics,
+ !for_drop_ && ShouldShowMnemonics(button));
+ int mouse_event_flags = 0;
+ owns_controller_ = false;
+ if (!controller) {
+ // No menus are showing, show one.
+ controller = new MenuController(!for_drop_, this);
+ owns_controller_ = true;
+ }
+ controller_ = controller;
+ menu_->set_controller(controller_);
+
+ // Run the loop.
+ MenuItemView* result = controller->Run(parent, button, menu_, bounds, anchor,
+ &mouse_event_flags);
+
+ if (for_drop_) {
+ // Drop menus return immediately. We finish processing in DropMenuClosed.
+ return MenuRunner::NORMAL_EXIT;
+ }
+
+ return MenuDone(result, mouse_event_flags);
+}
+
+void MenuRunnerImpl::Cancel() {
+ if (running_)
+ controller_->Cancel(MenuController::EXIT_ALL);
+}
+
+void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) {
+ MenuDone(NULL, 0);
+
+ if (type == NOTIFY_DELEGATE && menu->GetDelegate()) {
+ // Delegate is null when invoked from the destructor.
+ menu->GetDelegate()->DropMenuClosed(menu);
+ }
+}
+
+void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
+ if (menu != menu_ && sibling_menus_.count(menu) == 0)
+ sibling_menus_.insert(menu);
+}
+
+MenuRunnerImpl::~MenuRunnerImpl() {
+ delete menu_;
+ for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin();
+ i != sibling_menus_.end(); ++i)
+ delete *i;
+}
+
+MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result,
+ int mouse_event_flags) {
+ menu_->RemoveEmptyMenus();
+ menu_->set_controller(NULL);
+
+ if (owns_controller_) {
+ // We created the controller and need to delete it.
+ delete controller_;
+ owns_controller_ = false;
+ }
+ controller_ = NULL;
+ // Make sure all the windows we created to show the menus have been
+ // destroyed.
+ menu_->DestroyAllMenuHosts();
+ if (delete_after_run_) {
+ delete this;
+ return MenuRunner::MENU_DELETED;
+ }
+ running_ = false;
+ if (result && menu_->GetDelegate()) {
+ menu_->GetDelegate()->ExecuteCommand(result->GetCommand(),
+ mouse_event_flags);
+ }
+ return MenuRunner::NORMAL_EXIT;
+}
+
+bool MenuRunnerImpl::ShouldShowMnemonics(MenuButton* button) {
+ // Show mnemonics if the button has focus or alt is pressed.
+ bool show_mnemonics = button ? button->HasFocus() : false;
+#if defined(OS_WIN)
+ // We don't currently need this on gtk as showing the menu gives focus to the
+ // button first.
+ if (!show_mnemonics)
+ show_mnemonics = base::win::IsAltPressed();
+#endif
+ return show_mnemonics;
+}
+
+} // namespace internal
+
+MenuRunner::MenuRunner(MenuItemView* menu)
+ : holder_(new internal::MenuRunnerImpl(menu)) {
+}
+
+MenuRunner::~MenuRunner() {
+ holder_->Release();
+}
+
+MenuItemView* MenuRunner::GetMenu() {
+ return holder_->menu();
+}
+
+MenuRunner::RunResult MenuRunner::RunMenuAt(Widget* parent,
+ MenuButton* button,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition anchor,
+ int32 types) {
+ return holder_->RunMenuAt(parent, button, bounds, anchor, types);
+}
+
+void MenuRunner::Cancel() {
+ holder_->Cancel();
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_runner.h b/ui/views/controls/menu/menu_runner.h
new file mode 100644
index 0000000..6a3c572
--- /dev/null
+++ b/ui/views/controls/menu/menu_runner.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_RUNNER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_RUNNER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+
+namespace views {
+
+class MenuButton;
+class Widget;
+
+namespace internal {
+class MenuRunnerImpl;
+}
+
+// MenuRunner is responsible for showing (running) the menu and additionally
+// owning the MenuItemView. RunMenuAt() runs a nested message loop. It is safe
+// to delete MenuRunner at any point, but MenuRunner internally only deletes the
+// MenuItemView *after* the nested message loop completes. If MenuRunner is
+// deleted while the menu is showing the delegate of the menu is reset. This is
+// done to ensure delegates aren't notified after they may have been deleted.
+//
+// NOTE: while you can delete a MenuRunner at any point, the nested message loop
+// won't return immediately. This means if you delete the object that owns
+// the MenuRunner while the menu is running, your object is effectively still
+// on the stack. A return value of MENU_DELETED indicated this. In most cases
+// if RunMenuAt() returns MENU_DELETED, you should return immediately.
+//
+// Similarly you should avoid creating MenuRunner on the stack. Doing so means
+// MenuRunner may not be immediately destroyed if your object is destroyed,
+// resulting in possible callbacks to your now deleted object. Instead you
+// should define MenuRunner as a scoped_ptr in your class so that when your
+// object is destroyed MenuRunner initiates the proper cleanup and ensures your
+// object isn't accessed again.
+class VIEWS_EXPORT MenuRunner {
+ public:
+ enum RunTypes {
+ // The menu has mnemonics.
+ HAS_MNEMONICS = 1 << 0,
+
+ // The menu is a nested context menu. For example, click a folder on the
+ // bookmark bar, then right click an entry to get its context menu.
+ IS_NESTED = 1 << 1,
+
+ // Used for showing a menu during a drop operation. This does NOT block the
+ // caller, instead the delegate is notified when the menu closes via the
+ // DropMenuClosed method.
+ FOR_DROP = 1 << 2,
+ };
+
+ enum RunResult {
+ // Indicates RunMenuAt is returning because the MenuRunner was deleted.
+ MENU_DELETED,
+
+ // Indicates RunMenuAt returned and MenuRunner was not deleted.
+ NORMAL_EXIT
+ };
+
+ // Creates a new MenuRunner. MenuRunner owns the supplied menu.
+ explicit MenuRunner(MenuItemView* menu);
+ ~MenuRunner();
+
+ // Returns the menu.
+ MenuItemView* GetMenu();
+
+ // Takes ownership of |menu|, deleting it when MenuRunner is deleted. You
+ // only need call this if you create additional menus from
+ // MenuDelegate::GetSiblingMenu.
+ void OwnMenu(MenuItemView* menu);
+
+ // Runs the menu. |types| is a bitmask of RunTypes. If this returns
+ // MENU_DELETED the method is returning because the MenuRunner was deleted.
+ // Typically callers should NOT do any processing if this returns
+ // MENU_DELETED.
+ RunResult RunMenuAt(Widget* parent,
+ MenuButton* button,
+ const gfx::Rect& bounds,
+ MenuItemView::AnchorPosition anchor,
+ int32 types) WARN_UNUSED_RESULT;
+
+ // Hides and cancels the menu. This does nothing if the menu is not open.
+ void Cancel();
+
+ private:
+ internal::MenuRunnerImpl* holder_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuRunner);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_RUNNER_H_
diff --git a/ui/views/controls/menu/menu_scroll_view_container.cc b/ui/views/controls/menu/menu_scroll_view_container.cc
new file mode 100644
index 0000000..775218f
--- /dev/null
+++ b/ui/views/controls/menu/menu_scroll_view_container.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_scroll_view_container.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+#endif
+
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/native_theme.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/submenu_view.h"
+#include "views/border.h"
+
+using gfx::NativeTheme;
+
+// Height of the scroll arrow.
+// This goes up to 4 with large fonts, but this is close enough for now.
+static const int scroll_arrow_height = 3;
+
+namespace views {
+
+namespace {
+
+// 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:
+ MenuScrollButton(SubmenuView* host, bool is_up)
+ : host_(host),
+ is_up_(is_up),
+ // Make our height the same as that of other MenuItemViews.
+ pref_height_(MenuItemView::pref_menu_height()) {
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(MenuConfig::instance().scroll_arrow_height * 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 ui::DragDropTypes::DRAG_NONE;
+ }
+
+ virtual void OnDragExited() {
+ DCHECK(host_->GetMenuItem()->GetMenuController());
+ host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_);
+ }
+
+ virtual int OnPerformDrop(const DropTargetEvent& event) {
+ return ui::DragDropTypes::DRAG_NONE;
+ }
+
+ virtual void OnPaint(gfx::Canvas* canvas) {
+ const MenuConfig& config = MenuConfig::instance();
+
+ // The background.
+ gfx::Rect item_bounds(0, 0, width(), height());
+ NativeTheme::ExtraParams extra;
+ extra.menu_item.is_selected = false;
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuItemBackground,
+ NativeTheme::kNormal, item_bounds, extra);
+#if defined(OS_WIN)
+ SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT);
+#else
+ SkColor arrow_color = SK_ColorBLACK;
+#endif
+
+ // Then the arrow.
+ int x = width() / 2;
+ int y = (height() - config.scroll_arrow_height) / 2;
+ int delta_y = 1;
+ if (!is_up_) {
+ delta_y = -1;
+ y += config.scroll_arrow_height;
+ }
+ for (int i = 0; i < config.scroll_arrow_height; ++i, --x, y += delta_y)
+ canvas->FillRect(arrow_color, gfx::Rect(x, y, (i * 2) + 1, 1));
+ }
+
+ 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);
+};
+
+} // namespace
+
+// 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, so we use a one off variant.
+
+class MenuScrollViewContainer::MenuScrollView : public View {
+ public:
+ explicit MenuScrollView(View* child) {
+ AddChildView(child);
+ }
+
+ virtual void ScrollRectToVisible(const gfx::Rect& rect) {
+ // NOTE: this assumes we only want to scroll in the y direction.
+
+ // Convert rect.y() to view's coordinates and make sure we don't show past
+ // the bottom of the view.
+ View* child = GetContents();
+ child->SetY(-std::max(0, std::min(
+ child->GetPreferredSize().height() - this->height(),
+ rect.y() - child->y())));
+ }
+
+ // Returns the contents, which is the SubmenuView.
+ View* GetContents() {
+ return child_at(0);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollView);
+};
+
+// MenuScrollViewContainer ----------------------------------------------------
+
+MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view)
+ : content_view_(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(
+ SubmenuView::kSubmenuBorderSize,
+ SubmenuView::kSubmenuBorderSize,
+ SubmenuView::kSubmenuBorderSize,
+ SubmenuView::kSubmenuBorderSize));
+}
+
+void MenuScrollViewContainer::OnPaintBackground(gfx::Canvas* canvas) {
+ if (background()) {
+ View::OnPaintBackground(canvas);
+ return;
+ }
+
+#if defined(OS_WIN)
+ HDC dc = canvas->BeginPlatformPaint();
+#endif
+ gfx::Rect bounds(0, 0, width(), height());
+ NativeTheme::ExtraParams extra;
+ NativeTheme::instance()->Paint(canvas->GetSkCanvas(),
+ NativeTheme::kMenuPopupBackground, NativeTheme::kNormal, bounds, extra);
+#if defined(OS_WIN)
+ canvas->EndPlatformPaint();
+#endif
+}
+
+void MenuScrollViewContainer::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();
+}
+
+gfx::Size MenuScrollViewContainer::GetPreferredSize() {
+ gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize();
+ gfx::Insets insets = GetInsets();
+ prefsize.Enlarge(insets.width(), insets.height());
+ return prefsize;
+}
+
+void MenuScrollViewContainer::GetAccessibleState(
+ ui::AccessibleViewState* state) {
+ // Get the name from the submenu view.
+ content_view_->GetAccessibleState(state);
+
+ // Now change the role.
+ state->role = ui::AccessibilityTypes::ROLE_MENUBAR;
+ // Some AT (like NVDA) will not process focus events on menu item children
+ // unless a parent claims to be focused.
+ state->state = ui::AccessibilityTypes::STATE_FOCUSED;
+}
+
+void MenuScrollViewContainer::OnBoundsChanged(
+ const gfx::Rect& previous_bounds) {
+ gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize();
+ scroll_up_button_->SetVisible(content_pref.height() > height());
+ scroll_down_button_->SetVisible(content_pref.height() > height());
+ Layout();
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_scroll_view_container.h b/ui/views/controls/menu/menu_scroll_view_container.h
new file mode 100644
index 0000000..a19b73a
--- /dev/null
+++ b/ui/views/controls/menu/menu_scroll_view_container.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_SCROLL_VIEW_CONTAINER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_SCROLL_VIEW_CONTAINER_H_
+#pragma once
+
+#include "views/view.h"
+
+namespace views {
+
+class SubmenuView;
+
+// 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);
+
+ // Returns the buttons for scrolling up/down.
+ View* scroll_down_button() const { return scroll_down_button_; }
+ View* scroll_up_button() const { return scroll_up_button_; }
+
+ // View overrides.
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ protected:
+ // View override.
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+
+ private:
+ class MenuScrollView;
+
+ // The scroll buttons.
+ View* scroll_up_button_;
+ View* scroll_down_button_;
+
+ // The scroll view.
+ MenuScrollView* scroll_view_;
+
+ // The content view.
+ SubmenuView* content_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuScrollViewContainer);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_SCROLL_VIEW_CONTAINER_H_
diff --git a/ui/views/controls/menu/menu_separator.h b/ui/views/controls/menu/menu_separator.h
new file mode 100644
index 0000000..4e968a3
--- /dev/null
+++ b/ui/views/controls/menu/menu_separator.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "views/view.h"
+
+namespace views {
+
+class MenuSeparator : public View {
+ public:
+ MenuSeparator() {}
+
+ // View overrides.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuSeparator);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_
diff --git a/ui/views/controls/menu/menu_separator_aura.cc b/ui/views/controls/menu/menu_separator_aura.cc
new file mode 100644
index 0000000..0b914cf
--- /dev/null
+++ b/ui/views/controls/menu/menu_separator_aura.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_separator.h"
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/menu/menu_config.h"
+
+namespace views {
+
+static const SkColor kSeparatorColor = SkColorSetARGB(50, 00, 00, 00);
+
+void MenuSeparator::OnPaint(gfx::Canvas* canvas) {
+ canvas->DrawLineInt(kSeparatorColor, 0, height() / 2, width(), height() / 2);
+}
+
+gfx::Size MenuSeparator::GetPreferredSize() {
+ return gfx::Size(10, // Just in case we're the only item in a menu.
+ MenuConfig::instance().separator_height);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_separator_linux.cc b/ui/views/controls/menu/menu_separator_linux.cc
new file mode 100644
index 0000000..0b914cf
--- /dev/null
+++ b/ui/views/controls/menu/menu_separator_linux.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_separator.h"
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/menu/menu_config.h"
+
+namespace views {
+
+static const SkColor kSeparatorColor = SkColorSetARGB(50, 00, 00, 00);
+
+void MenuSeparator::OnPaint(gfx::Canvas* canvas) {
+ canvas->DrawLineInt(kSeparatorColor, 0, height() / 2, width(), height() / 2);
+}
+
+gfx::Size MenuSeparator::GetPreferredSize() {
+ return gfx::Size(10, // Just in case we're the only item in a menu.
+ MenuConfig::instance().separator_height);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_separator_win.cc b/ui/views/controls/menu/menu_separator_win.cc
new file mode 100644
index 0000000..2692f79
--- /dev/null
+++ b/ui/views/controls/menu/menu_separator_win.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_separator.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <Vssym32.h>
+
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/native_theme.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+
+namespace views {
+
+void MenuSeparator::OnPaint(gfx::Canvas* canvas) {
+ const MenuConfig& config = MenuConfig::instance();
+ // The gutter is rendered before the background.
+ int start_x = 0;
+ const gfx::NativeTheme* theme = gfx::NativeTheme::instance();
+ if (config.render_gutter) {
+ // If render_gutter is true, we're on Vista and need to render the
+ // gutter, then indent the separator from the gutter.
+ gfx::Rect gutter_bounds(MenuItemView::label_start() -
+ config.gutter_to_label - config.gutter_width, 0,
+ config.gutter_width, height());
+ gfx::NativeTheme::ExtraParams extra;
+ theme->Paint(canvas->GetSkCanvas(), gfx::NativeTheme::kMenuPopupGutter,
+ gfx::NativeTheme::kNormal, gutter_bounds, extra);
+ start_x = gutter_bounds.x() + config.gutter_width;
+ }
+
+ gfx::Rect separator_bounds(start_x, 0, width(), height());
+ gfx::NativeTheme::ExtraParams extra;
+ extra.menu_separator.has_gutter = config.render_gutter;
+ theme->Paint(canvas->GetSkCanvas(), gfx::NativeTheme::kMenuPopupSeparator,
+ gfx::NativeTheme::kNormal, separator_bounds, extra);
+}
+
+gfx::Size MenuSeparator::GetPreferredSize() {
+ return gfx::Size(10, // Just in case we're the only item in a menu.
+ MenuConfig::instance().separator_height);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_win.cc b/ui/views/controls/menu/menu_win.cc
new file mode 100644
index 0000000..e1ad7a5
--- /dev/null
+++ b/ui/views/controls/menu/menu_win.cc
@@ -0,0 +1,574 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/menu_win.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/string_util.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/base/win/window_impl.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/rect.h"
+
+namespace views {
+
+// The width of an icon, including the pixels between the icon and
+// the item label.
+const int kIconWidth = 23;
+// Margins between the top of the item and the label.
+const int kItemTopMargin = 3;
+// Margins between the bottom of the item and the label.
+const int kItemBottomMargin = 4;
+// Margins between the left of the item and the icon.
+const int kItemLeftMargin = 4;
+// Margins between the right of the item and the label.
+const int kItemRightMargin = 10;
+// The width for displaying the sub-menu arrow.
+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 MenuWin::ItemData {
+ string16 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 ui::WindowImpl {
+ public:
+ MenuHostWindow(MenuWin* 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();
+ set_window_style(WS_CHILD);
+ set_window_ex_style(extended_style);
+ Init(parent_window, gfx::Rect());
+ }
+
+ ~MenuHostWindow() {
+ DestroyWindow(hwnd());
+ }
+
+ BEGIN_MSG_MAP_EX(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, gfx::Point(loc), true);
+ }
+
+ void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* lpmis) {
+ MenuWin::ItemData* data =
+ reinterpret_cast<MenuWin::ItemData*>(lpmis->itemData);
+ if (data != NULL) {
+ gfx::Font 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') != string16::npos)
+ lpmis->itemWidth += font.GetStringWidth(L" ");
+ lpmis->itemHeight = font.GetHeight() + 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) {
+ MenuWin::ItemData* data =
+ reinterpret_cast<MenuWin::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;
+ gfx::Font font;
+ HGDIOBJ old_font =
+ static_cast<HFONT>(SelectObject(hDC, font.GetNativeFont()));
+
+ // 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.
+ string16 label = data->label;
+ string16 accel;
+ string16::size_type tab_pos = label.find(L'\t');
+ if (tab_pos != string16::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) {
+ gfx::CanvasSkia canvas(data->icon.width(), data->icon.height(), false);
+ canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
+ canvas.DrawBitmapInt(data->icon, 0, 0);
+ skia::DrawToNativeContext(
+ canvas.sk_canvas(), 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(MenuWin* 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<MenuWin*>::iterator i = menu->submenus_.begin();
+ i != menu->submenus_.end(); ++i) {
+ if (FindMenuIDByLocation(*i, loc, id))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // The menu that created us.
+ MenuWin* menu_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHostWindow);
+};
+
+} // namespace
+
+// static
+Menu* Menu::Create(Delegate* delegate,
+ AnchorPoint anchor,
+ gfx::NativeView parent) {
+ return new MenuWin(delegate, anchor, parent);
+}
+
+// static
+Menu* Menu::GetSystemMenu(gfx::NativeWindow parent) {
+ return new views::MenuWin(::GetSystemMenu(parent, FALSE));
+}
+
+MenuWin::MenuWin(Delegate* d, AnchorPoint anchor, HWND owner)
+ : Menu(d, anchor),
+ menu_(CreatePopupMenu()),
+ owner_(owner),
+ is_menu_visible_(false),
+ owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL)) {
+ DCHECK(delegate());
+}
+
+MenuWin::MenuWin(HMENU hmenu)
+ : Menu(NULL, TOPLEFT),
+ menu_(hmenu),
+ owner_(NULL),
+ is_menu_visible_(false),
+ owner_draw_(false) {
+ DCHECK(menu_);
+}
+
+MenuWin::~MenuWin() {
+ STLDeleteContainerPointers(submenus_.begin(), submenus_.end());
+ STLDeleteContainerPointers(item_data_.begin(), item_data_.end());
+ DestroyMenu(menu_);
+}
+
+void MenuWin::AddMenuItemWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ owner_draw_ = true;
+ Menu::AddMenuItemWithIcon(index, item_id, label, icon);
+}
+
+Menu* MenuWin::AddSubMenuWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) {
+ MenuWin* submenu = new MenuWin(this);
+ submenus_.push_back(submenu);
+ AddMenuItemInternal(index, item_id, label, icon, submenu->menu_, NORMAL);
+ return submenu;
+}
+
+void MenuWin::AddSeparator(int index) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(menu_, index, TRUE, &mii);
+}
+
+void MenuWin::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 MenuWin::EnableMenuItemAt(int index, bool enabled) {
+ UINT enable_flags = enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
+ EnableMenuItem(menu_, index, MF_BYPOSITION | enable_flags);
+}
+
+void MenuWin::SetMenuLabel(int item_id, const string16& 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);
+}
+
+bool MenuWin::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 MenuWin::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->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 MenuWin::Cancel() {
+ DCHECK(is_menu_visible_);
+ EndMenu();
+}
+
+int MenuWin::ItemCount() {
+ return GetMenuItemCount(menu_);
+}
+
+void MenuWin::AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ MenuItemType type) {
+ AddMenuItemInternal(index, item_id, label, icon, NULL, type);
+}
+
+void MenuWin::AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ HMENU submenu,
+ MenuItemType type) {
+ DCHECK(type != SEPARATOR) << "Call AddSeparator instead!";
+
+ if (!owner_draw_ && !icon.empty())
+ owner_draw_ = true;
+
+ 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::GetStringUTF16(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.
+ MenuWin::ItemData* data = new ItemData;
+ item_data_.push_back(data);
+ data->submenu = submenu != NULL;
+
+ string16 actual_label(label.empty() ? delegate()->GetLabel(item_id) : label);
+
+ // Find out if there is a shortcut we need to append to the label.
+ ui::Accelerator accelerator(ui::VKEY_UNKNOWN, 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);
+}
+
+MenuWin::MenuWin(MenuWin* parent)
+ : Menu(parent->delegate(), parent->anchor()),
+ menu_(CreatePopupMenu()),
+ owner_(parent->owner_),
+ is_menu_visible_(false),
+ owner_draw_(parent->owner_draw_) {
+}
+
+void MenuWin::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
+ string16 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();
+}
+
+UINT MenuWin::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;
+}
+
+DWORD MenuWin::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;
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/menu_win.h b/ui/views/controls/menu/menu_win.h
new file mode 100644
index 0000000..3b20f27
--- /dev/null
+++ b/ui/views/controls/menu/menu_win.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_WIN_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_WIN_H_
+#pragma once
+
+#include <windows.h>
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/menu/menu.h"
+
+namespace views {
+
+namespace {
+class MenuHostWindow;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Menu class
+//
+// A wrapper around a Win32 HMENU handle that provides convenient APIs for
+// menu construction, display and subsequent command execution.
+//
+///////////////////////////////////////////////////////////////////////////////
+class MenuWin : public Menu {
+ public:
+ // 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.
+ MenuWin(Delegate* d, 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 MenuWin(HMENU hmenu);
+ virtual ~MenuWin();
+
+ // Overridden from Menu:
+ virtual void AddMenuItemWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) OVERRIDE;
+ virtual Menu* AddSubMenuWithIcon(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon) OVERRIDE;
+ virtual void AddSeparator(int index) OVERRIDE;
+ virtual void EnableMenuItemByID(int item_id, bool enabled) OVERRIDE;
+ virtual void EnableMenuItemAt(int index, bool enabled) OVERRIDE;
+ virtual void SetMenuLabel(int item_id, const string16& label) OVERRIDE;
+ virtual bool SetIcon(const SkBitmap& icon, int item_id) OVERRIDE;
+ virtual void RunMenuAt(int x, int y) OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual int ItemCount() OVERRIDE;
+
+ virtual HMENU GetMenuHandle() const {
+ return menu_;
+ }
+
+ // Gets the Win32 TPM alignment flags for the specified AnchorPoint.
+ DWORD GetTPMAlignFlags() const;
+
+ protected:
+ virtual void AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ MenuItemType type) OVERRIDE;
+
+ private:
+ friend class MenuHostWindow;
+
+ // The data of menu items needed to display.
+ struct ItemData;
+
+ void AddMenuItemInternal(int index,
+ int item_id,
+ const string16& label,
+ const SkBitmap& icon,
+ HMENU submenu,
+ MenuItemType type);
+
+ explicit MenuWin(MenuWin* parent);
+
+ // 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;
+
+ // 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<string16> 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_;
+
+ // 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<MenuWin*> submenus_;
+
+ // Whether the menu is visible.
+ bool is_menu_visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuWin);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_WIN_H_
diff --git a/ui/views/controls/menu/menu_wrapper.h b/ui/views/controls/menu/menu_wrapper.h
new file mode 100644
index 0000000..2d21953
--- /dev/null
+++ b/ui/views/controls/menu/menu_wrapper.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_MENU_WRAPPER_H_
+#define UI_VIEWS_CONTROLS_MENU_MENU_WRAPPER_H_
+#pragma once
+
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+
+namespace gfx {
+class Point;
+}
+
+namespace views {
+
+class Menu2;
+class MenuListener;
+
+// An interface that wraps an object that implements a menu.
+class VIEWS_EXPORT MenuWrapper {
+ public:
+ // All of the possible actions that can result from RunMenuAt.
+ enum MenuAction {
+ MENU_ACTION_NONE, // Menu cancelled, or never opened.
+ MENU_ACTION_SELECTED, // An item was selected.
+ MENU_ACTION_PREVIOUS, // User wants to navigate to the previous menu.
+ MENU_ACTION_NEXT, // User wants to navigate to the next menu.
+ };
+
+ virtual ~MenuWrapper() {}
+
+ // Runs the menu at the specified point. This blocks until done.
+ virtual void RunMenuAt(const gfx::Point& point, int alignment) = 0;
+
+ // Cancels the active menu.
+ virtual void CancelMenu() = 0;
+
+ // Called when the model supplying data to this menu has changed, and the menu
+ // must be rebuilt.
+ virtual void Rebuild() = 0;
+
+ // Called when the states of the items in the menu must be updated from the
+ // model.
+ virtual void UpdateStates() = 0;
+
+ // Retrieve a native menu handle.
+ virtual gfx::NativeMenu GetNativeMenu() const = 0;
+
+ // Get the result of the last call to RunMenuAt to determine whether an
+ // item was selected, the user navigated to a next or previous menu, or
+ // nothing.
+ virtual MenuAction GetMenuAction() const = 0;
+
+ // Add a listener to receive a callback when the menu opens.
+ virtual void AddMenuListener(MenuListener* listener) = 0;
+
+ // Remove a menu listener.
+ virtual void RemoveMenuListener(MenuListener* listener) = 0;
+
+ // Sets the minimum width of the menu.
+ virtual void SetMinimumWidth(int width) = 0;
+
+ // Creates the appropriate instance of this wrapper for the current platform.
+ static MenuWrapper* CreateWrapper(Menu2* menu);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_MENU_WRAPPER_H_
diff --git a/ui/views/controls/menu/native_menu_gtk.cc b/ui/views/controls/menu/native_menu_gtk.cc
new file mode 100644
index 0000000..97ca02a
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_gtk.cc
@@ -0,0 +1,628 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/native_menu_gtk.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/i18n/rtl.h"
+#include "base/message_loop.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/views/controls/menu/menu_2.h"
+#include "ui/views/controls/menu/menu_listener.h"
+#include "ui/views/controls/menu/nested_dispatcher_gtk.h"
+#include "ui/views/widget/native_widget_gtk.h"
+#include "views/views_delegate.h"
+
+namespace {
+
+const char kPositionString[] = "position";
+const char kAccelGroupString[] = "accel_group";
+
+// Key for the property set on the gtk menu that gives the handle to the hosting
+// NativeMenuGtk.
+const char kNativeMenuGtkString[] = "native_menu_gtk";
+
+// Data passed to the MenuPositionFunc from gtk_menu_popup
+struct Position {
+ // The point to run the menu at.
+ gfx::Point point;
+ // The alignment of the menu at that point.
+ views::Menu2::Alignment alignment;
+};
+
+// Returns true if the menu item type specified can be executed as a command.
+bool MenuTypeCanExecute(ui::MenuModel::ItemType type) {
+ return type == ui::MenuModel::TYPE_COMMAND ||
+ type == ui::MenuModel::TYPE_CHECK ||
+ type == ui::MenuModel::TYPE_RADIO;
+}
+
+// A callback to gtk_container_foreach to remove all children.
+// See |NativeMenuGtk::ResetMenu| for the usage.
+void RemoveChildWidget(GtkWidget* widget, gpointer data) {
+ GtkWidget* parent = gtk_widget_get_parent(widget);
+ gtk_container_remove(GTK_CONTAINER(parent), widget);
+}
+
+} // namespace
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuGtk, public:
+
+NativeMenuGtk::NativeMenuGtk(Menu2* menu)
+ : parent_(NULL),
+ model_(menu->model()),
+ menu_(NULL),
+ menu_hidden_(true),
+ suppress_activate_signal_(false),
+ activated_menu_(NULL),
+ activated_index_(-1),
+ activate_factory_(this),
+ host_menu_(menu),
+ destroy_handler_id_(0),
+ expose_handler_id_(0),
+ menu_action_(MENU_ACTION_NONE),
+ nested_dispatcher_(NULL),
+ ignore_button_release_(true) {
+}
+
+NativeMenuGtk::~NativeMenuGtk() {
+ if (nested_dispatcher_) {
+ // Menu is destroyed while its in message loop.
+ // Let nested dispatcher know the creator is deleted.
+ nested_dispatcher_->CreatorDestroyed();
+ }
+ if (menu_) {
+ DCHECK(destroy_handler_id_);
+ // Don't call MenuDestroyed because menu2 has already been destroyed.
+ g_signal_handler_disconnect(menu_, destroy_handler_id_);
+ gtk_widget_destroy(menu_);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuGtk, MenuWrapper implementation:
+
+void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) {
+ activated_menu_ = NULL;
+ activated_index_ = -1;
+ menu_action_ = MENU_ACTION_NONE;
+ // ignore button release event unless mouse is pressed or moved.
+ ignore_button_release_ = true;
+
+ UpdateStates();
+ // Set the FREEZE UPDATE property to the menu's window so that WM maps
+ // the menu after the menu painted itself.
+ GtkWidget* popup_window = gtk_widget_get_ancestor(menu_, GTK_TYPE_WINDOW);
+ CHECK(popup_window);
+ NativeWidgetGtk::UpdateFreezeUpdatesProperty(GTK_WINDOW(popup_window),
+ true /* add */);
+ expose_handler_id_ = g_signal_connect_after(G_OBJECT(menu_), "expose_event",
+ G_CALLBACK(&OnExposeThunk), this);
+
+ Position position = { point, static_cast<Menu2::Alignment>(alignment) };
+ // TODO(beng): value of '1' will not work for context menus!
+ gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, MenuPositionFunc, &position, 1,
+ gtk_get_current_event_time());
+ DCHECK(menu_hidden_);
+ menu_hidden_ = false;
+
+ FOR_EACH_OBSERVER(MenuListener, listeners_, OnMenuOpened());
+
+ // Listen for "hide" signal so that we know when to return from the blocking
+ // RunMenuAt call.
+ gint hide_handle_id =
+ g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
+
+ gint move_handle_id =
+ g_signal_connect(menu_, "move-current",
+ G_CALLBACK(OnMenuMoveCurrentThunk), this);
+ gint after_move_handle_id =
+ g_signal_connect_after(menu_, "move-current",
+ G_CALLBACK(AfterMenuMoveCurrentThunk), this);
+
+ model_->MenuWillShow();
+
+ // Block until menu is no longer shown by running a nested message loop.
+ nested_dispatcher_ = new NestedDispatcherGtk(this, true);
+ bool deleted = nested_dispatcher_->RunAndSelfDestruct();
+ if (deleted) {
+ // The menu was destryed while menu is shown, so return immediately.
+ // Don't touch the instance which is already deleted.
+ return;
+ }
+ nested_dispatcher_ = NULL;
+ if (!menu_hidden_) {
+ // If this happens it means we haven't yet gotten the hide signal and
+ // someone else quit the message loop on us.
+ NOTREACHED();
+ menu_hidden_ = true;
+ }
+
+ g_signal_handler_disconnect(G_OBJECT(menu_), hide_handle_id);
+ g_signal_handler_disconnect(G_OBJECT(menu_), move_handle_id);
+ g_signal_handler_disconnect(G_OBJECT(menu_), after_move_handle_id);
+
+ if (activated_menu_) {
+ MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&NativeMenuGtk::ProcessActivate,
+ activate_factory_.GetWeakPtr()));
+ }
+
+ model_->MenuClosed();
+}
+
+void NativeMenuGtk::CancelMenu() {
+ gtk_widget_hide(menu_);
+}
+
+void NativeMenuGtk::Rebuild() {
+ activated_menu_ = NULL;
+
+ ResetMenu();
+
+ // Try to retrieve accelerator group as data from menu_; if null, create new
+ // one and store it as data into menu_.
+ // We store it as data so as to use the destroy notifier to get rid of initial
+ // reference count. For some reason, when we unref it ourselves (even in
+ // destructor), it would cause random crashes, depending on when gtk tries to
+ // access it.
+ GtkAccelGroup* accel_group = static_cast<GtkAccelGroup*>(
+ g_object_get_data(G_OBJECT(menu_), kAccelGroupString));
+ if (!accel_group) {
+ accel_group = gtk_accel_group_new();
+ g_object_set_data_full(G_OBJECT(menu_), kAccelGroupString, accel_group,
+ g_object_unref);
+ }
+
+ std::map<int, GtkRadioMenuItem*> radio_groups_;
+ for (int i = 0; i < model_->GetItemCount(); ++i) {
+ ui::MenuModel::ItemType type = model_->GetTypeAt(i);
+ if (type == ui::MenuModel::TYPE_SEPARATOR) {
+ AddSeparatorAt(i);
+ } else if (type == ui::MenuModel::TYPE_RADIO) {
+ const int radio_group_id = model_->GetGroupIdAt(i);
+ std::map<int, GtkRadioMenuItem*>::const_iterator iter
+ = radio_groups_.find(radio_group_id);
+ if (iter == radio_groups_.end()) {
+ GtkWidget* new_menu_item = AddMenuItemAt(i, NULL, accel_group);
+ // |new_menu_item| is the first menu item for |radio_group_id| group.
+ radio_groups_.insert(
+ std::make_pair(radio_group_id, GTK_RADIO_MENU_ITEM(new_menu_item)));
+ } else {
+ AddMenuItemAt(i, iter->second, accel_group);
+ }
+ } else {
+ AddMenuItemAt(i, NULL, accel_group);
+ }
+ }
+ if (!menu_hidden_)
+ gtk_menu_reposition(GTK_MENU(menu_));
+}
+
+void NativeMenuGtk::UpdateStates() {
+ gtk_container_foreach(GTK_CONTAINER(menu_), &UpdateStateCallback, this);
+}
+
+gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const {
+ return menu_;
+}
+
+NativeMenuGtk::MenuAction NativeMenuGtk::GetMenuAction() const {
+ return menu_action_;
+}
+
+void NativeMenuGtk::AddMenuListener(MenuListener* listener) {
+ listeners_.AddObserver(listener);
+}
+
+void NativeMenuGtk::RemoveMenuListener(MenuListener* listener) {
+ listeners_.RemoveObserver(listener);
+}
+
+void NativeMenuGtk::SetMinimumWidth(int width) {
+ gtk_widget_set_size_request(menu_, width, -1);
+}
+
+bool NativeMenuGtk::Dispatch(GdkEvent* event) {
+ if (menu_hidden_) {
+ // The menu has been closed but the message loop is still nested. Don't
+ // dispatch a message, otherwise we might spawn another message loop.
+ return false; // Exits the nested message loop.
+ }
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS: {
+ ignore_button_release_ = false;
+ gpointer data = NULL;
+ gdk_window_get_user_data(((GdkEventAny*)event)->window, &data);
+ GtkWidget* widget = reinterpret_cast<GtkWidget*>(data);
+ if (widget) {
+ GtkWidget* root = gtk_widget_get_toplevel(widget);
+ if (!g_object_get_data(G_OBJECT(root), kNativeMenuGtkString)) {
+ // The button event is not targeted at a menu, hide the menu and eat
+ // the event. If we didn't do this the button press is dispatched from
+ // the nested message loop and bad things can happen (like trying to
+ // spawn another menu.
+ gtk_menu_popdown(GTK_MENU(menu_));
+ // In some cases we may not have gotten the hide event, but the menu
+ // will be in the process of hiding so set menu_hidden_ anyway.
+ menu_hidden_ = true;
+ return false; // Exits the nested message loop.
+ }
+ }
+ break;
+ }
+ case GDK_MOTION_NOTIFY: {
+ ignore_button_release_ = false;
+ break;
+ }
+ case GDK_BUTTON_RELEASE: {
+ if (ignore_button_release_) {
+ // Ignore if a release event happened without press event.
+ // Normally, release event is eaten by gtk when menu is opened
+ // in response to mouse press event. Since the renderer opens
+ // the context menu asyncrhonous after press event is handled,
+ // gtk sometimes does not eat it, which causes the menu to be
+ // closed.
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ gtk_main_do_event(event);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuGtk, private:
+
+void NativeMenuGtk::OnMenuHidden(GtkWidget* widget) {
+ if (menu_hidden_) {
+ // The menu has been already hidden by us and we're in the process of
+ // quiting the message loop..
+ return;
+ }
+ // Quit the nested message loop we spawned in RunMenuAt.
+ MessageLoop::current()->Quit();
+
+ // Menu can be closed before the menu is shown.
+ if (expose_handler_id_) {
+ g_signal_handler_disconnect(menu_, expose_handler_id_);
+ expose_handler_id_ = 0;
+ }
+
+ menu_hidden_ = true;
+}
+
+void NativeMenuGtk::OnMenuMoveCurrent(GtkWidget* menu_widget,
+ GtkMenuDirectionType focus_direction) {
+ GtkWidget* parent = GTK_MENU_SHELL(menu_widget)->parent_menu_shell;
+ GtkWidget* menu_item = GTK_MENU_SHELL(menu_widget)->active_menu_item;
+ GtkWidget* submenu = NULL;
+ if (menu_item) {
+ submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
+ }
+ if (focus_direction == GTK_MENU_DIR_CHILD && submenu == NULL) {
+ GetAncestor()->menu_action_ = MENU_ACTION_NEXT;
+ gtk_menu_popdown(GTK_MENU(menu_widget));
+ } else if (focus_direction == GTK_MENU_DIR_PARENT && parent == NULL) {
+ GetAncestor()->menu_action_ = MENU_ACTION_PREVIOUS;
+ gtk_menu_popdown(GTK_MENU(menu_widget));
+ }
+}
+
+void NativeMenuGtk::AfterMenuMoveCurrent(GtkWidget* menu_widget,
+ GtkMenuDirectionType focus_direction) {
+ SendAccessibilityEvent();
+}
+
+gboolean NativeMenuGtk::OnExpose(GtkWidget* widget, GdkEventExpose* event) {
+ GtkWidget* popup_window = gtk_widget_get_ancestor(menu_, GTK_TYPE_WINDOW);
+ CHECK(popup_window);
+ DCHECK(expose_handler_id_);
+ NativeWidgetGtk::UpdateFreezeUpdatesProperty(GTK_WINDOW(popup_window),
+ false /* remove */);
+ if (expose_handler_id_) {
+ g_signal_handler_disconnect(menu_, expose_handler_id_);
+ expose_handler_id_ = 0;
+ }
+ return false;
+}
+
+void NativeMenuGtk::AddSeparatorAt(int index) {
+ GtkWidget* separator = gtk_separator_menu_item_new();
+ gtk_widget_show(separator);
+ gtk_menu_append(menu_, separator);
+}
+
+GtkWidget* NativeMenuGtk::AddMenuItemAt(int index,
+ GtkRadioMenuItem* radio_group,
+ GtkAccelGroup* accel_group) {
+ GtkWidget* menu_item = NULL;
+ std::string label = gfx::ConvertAcceleratorsFromWindowsStyle(UTF16ToUTF8(
+ model_->GetLabelAt(index)));
+
+ ui::MenuModel::ItemType type = model_->GetTypeAt(index);
+ switch (type) {
+ case ui::MenuModel::TYPE_CHECK:
+ menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
+ break;
+ case ui::MenuModel::TYPE_RADIO:
+ if (radio_group) {
+ menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
+ radio_group, label.c_str());
+ } else {
+ // The item does not belong to any existing radio button groups.
+ menu_item = gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str());
+ }
+ break;
+ case ui::MenuModel::TYPE_SUBMENU:
+ case ui::MenuModel::TYPE_COMMAND: {
+ SkBitmap icon;
+ // Create menu item with icon if icon exists.
+ if (model_->HasIcons() && model_->GetIconAt(index, &icon)) {
+ menu_item = gtk_image_menu_item_new_with_mnemonic(label.c_str());
+ GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+
+ // Show the image even if the "gtk-menu-images" setting is turned off.
+ gtk_image_menu_item_set_always_show_image(
+ GTK_IMAGE_MENU_ITEM(menu_item), TRUE);
+ } else {
+ menu_item = gtk_menu_item_new_with_mnemonic(label.c_str());
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Label font.
+ const gfx::Font* font = model_->GetLabelFontAt(index);
+ if (font) {
+ // The label item is the first child of the menu item.
+ GtkWidget* label_widget = GTK_BIN(menu_item)->child;
+ DCHECK(label_widget && GTK_IS_LABEL(label_widget));
+ PangoFontDescription* pfd = font->GetNativeFont();
+ gtk_widget_modify_font(label_widget, pfd);
+ pango_font_description_free(pfd);
+ }
+
+ if (type == ui::MenuModel::TYPE_SUBMENU) {
+ Menu2* submenu = new Menu2(model_->GetSubmenuModelAt(index));
+ static_cast<NativeMenuGtk*>(submenu->wrapper_.get())->set_parent(this);
+ g_object_set_data(G_OBJECT(menu_item), "submenu", submenu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+ submenu->GetNativeMenu());
+ g_signal_connect(submenu->GetNativeMenu(), "move-current",
+ G_CALLBACK(OnMenuMoveCurrentThunk), this);
+ }
+
+ ui::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false);
+ if (accel_group && model_->GetAcceleratorAt(index, &accelerator)) {
+ int gdk_modifiers = 0;
+ if (accelerator.IsShiftDown())
+ gdk_modifiers |= GDK_SHIFT_MASK;
+ if (accelerator.IsCtrlDown())
+ gdk_modifiers |= GDK_CONTROL_MASK;
+ if (accelerator.IsAltDown())
+ gdk_modifiers |= GDK_MOD1_MASK;
+ gtk_widget_add_accelerator(menu_item, "activate", accel_group,
+ ui::GdkKeyCodeForWindowsKeyCode(accelerator.key_code(), false),
+ static_cast<GdkModifierType>(gdk_modifiers), GTK_ACCEL_VISIBLE);
+ }
+
+ g_object_set_data(G_OBJECT(menu_item), kPositionString,
+ reinterpret_cast<void*>(index));
+ g_signal_connect(menu_item, "activate", G_CALLBACK(CallActivate), this);
+ UpdateMenuItemState(menu_item, false);
+ gtk_widget_show(menu_item);
+ gtk_menu_append(menu_, menu_item);
+
+ return menu_item;
+}
+
+void NativeMenuGtk::ResetMenu() {
+ if (!menu_) {
+ menu_ = gtk_menu_new();
+ g_object_set_data(
+ G_OBJECT(GTK_MENU(menu_)->toplevel), kNativeMenuGtkString, this);
+ destroy_handler_id_ = g_signal_connect(
+ menu_, "destroy", G_CALLBACK(NativeMenuGtk::MenuDestroyed), host_menu_);
+ } else {
+ gtk_container_foreach(GTK_CONTAINER(menu_), RemoveChildWidget, NULL);
+ }
+}
+
+void NativeMenuGtk::UpdateMenuItemState(GtkWidget* menu_item, bool recurse) {
+ int index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item),
+ kPositionString));
+
+ gtk_widget_set_sensitive(menu_item, model_->IsEnabledAt(index));
+ if (GTK_IS_CHECK_MENU_ITEM(menu_item)) {
+ suppress_activate_signal_ = true;
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item),
+ model_->IsItemCheckedAt(index));
+ suppress_activate_signal_ = false;
+ }
+
+ if (recurse && GTK_IS_MENU_ITEM(menu_item)) {
+ // Recurse into submenus.
+ if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))) {
+ Menu2* submenu =
+ reinterpret_cast<Menu2*>(g_object_get_data(G_OBJECT(menu_item),
+ "submenu"));
+ if (submenu)
+ submenu->UpdateStates();
+ }
+ }
+}
+
+// static
+void NativeMenuGtk::UpdateStateCallback(GtkWidget* menu_item, gpointer data) {
+ NativeMenuGtk* menu = reinterpret_cast<NativeMenuGtk*>(data);
+ menu->UpdateMenuItemState(menu_item, true);
+}
+
+// static
+void NativeMenuGtk::MenuPositionFunc(GtkMenu* menu,
+ int* x,
+ int* y,
+ gboolean* push_in,
+ void* data) {
+ Position* position = reinterpret_cast<Position*>(data);
+
+ GtkRequisition menu_req;
+ gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
+
+ *x = position->point.x();
+ *y = position->point.y();
+ views::Menu2::Alignment alignment = position->alignment;
+ if (base::i18n::IsRTL()) {
+ switch (alignment) {
+ case Menu2::ALIGN_TOPRIGHT:
+ alignment = Menu2::ALIGN_TOPLEFT;
+ break;
+ case Menu2::ALIGN_TOPLEFT:
+ alignment = Menu2::ALIGN_TOPRIGHT;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ if (alignment == Menu2::ALIGN_TOPRIGHT)
+ *x -= menu_req.width;
+
+ // Make sure the popup fits on screen.
+ GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(menu));
+ *x = std::max(0, std::min(gdk_screen_get_width(screen) - menu_req.width, *x));
+ *y = std::max(0, std::min(gdk_screen_get_height(screen) - menu_req.height,
+ *y));
+
+ *push_in = FALSE;
+}
+
+void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) {
+ if (suppress_activate_signal_)
+ return;
+ int position = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item),
+ kPositionString));
+ // Ignore the signal if it's sent to an inactive checked radio item.
+ //
+ // Suppose there are three radio items A, B, C, and A is now being
+ // checked. If you click C, "activate" signal will be sent to A and C.
+ // Here, we ignore the signal sent to A.
+ if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
+ !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
+ return;
+ }
+
+ // NOTE: we get activate messages for submenus when first shown.
+ if (model_->IsEnabledAt(position) &&
+ MenuTypeCanExecute(model_->GetTypeAt(position))) {
+ NativeMenuGtk* ancestor = GetAncestor();
+ ancestor->activated_menu_ = this;
+ activated_index_ = position;
+ ancestor->menu_action_ = MENU_ACTION_SELECTED;
+ }
+}
+
+// static
+void NativeMenuGtk::CallActivate(GtkMenuItem* menu_item,
+ NativeMenuGtk* native_menu) {
+ native_menu->OnActivate(menu_item);
+}
+
+NativeMenuGtk* NativeMenuGtk::GetAncestor() {
+ NativeMenuGtk* ancestor = this;
+ while (ancestor->parent_)
+ ancestor = ancestor->parent_;
+ return ancestor;
+}
+
+void NativeMenuGtk::ProcessActivate() {
+ if (activated_menu_)
+ activated_menu_->Activate();
+}
+
+void NativeMenuGtk::Activate() {
+ if (model_->IsEnabledAt(activated_index_) &&
+ MenuTypeCanExecute(model_->GetTypeAt(activated_index_))) {
+ model_->ActivatedAt(activated_index_);
+ }
+}
+
+void NativeMenuGtk::SendAccessibilityEvent() {
+ // Find the focused menu item, recursing into submenus as needed.
+ GtkWidget* menu = menu_;
+ GtkWidget* menu_item = GTK_MENU_SHELL(menu_)->active_menu_item;
+ if (!menu_item)
+ return;
+ GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
+ while (submenu && GTK_MENU_SHELL(submenu)->active_menu_item) {
+ menu = submenu;
+ menu_item = GTK_MENU_SHELL(menu)->active_menu_item;
+ if (!menu_item)
+ return;
+ submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
+ }
+
+ // Figure out the item index and total number of items.
+ GList* items = gtk_container_get_children(GTK_CONTAINER(menu));
+ guint count = g_list_length(items);
+ int index = g_list_index(items, static_cast<gconstpointer>(menu_item));
+
+ // Get the menu item's label.
+ std::string name;
+ name = gtk_menu_item_get_label(GTK_MENU_ITEM(menu_item));
+
+ if (ViewsDelegate::views_delegate) {
+ ViewsDelegate::views_delegate->NotifyMenuItemFocused(string16(),
+ UTF8ToUTF16(name),
+ index,
+ count,
+ submenu != NULL);
+ }
+}
+
+// static
+void NativeMenuGtk::MenuDestroyed(GtkWidget* widget, Menu2* menu2) {
+ NativeMenuGtk* native_menu =
+ static_cast<NativeMenuGtk*>(menu2->wrapper_.get());
+ // The native gtk widget has already been destroyed.
+ native_menu->menu_ = NULL;
+ delete menu2;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MenuWrapper, public:
+
+// static
+MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
+ return new NativeMenuGtk(menu);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/native_menu_gtk.h b/ui/views/controls/menu/native_menu_gtk.h
new file mode 100644
index 0000000..debc254
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_gtk.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_
+#define UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "base/observer_list.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/views/controls/menu/menu_wrapper.h"
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+
+class NestedDispatcherGtk;
+
+// A Gtk implementation of MenuWrapper.
+//
+// NOTE: On windows the activate message is not sent immediately when an item
+// is selected. Instead a message is added to the queue that is processed later.
+// To simulate that (and avoid having to deal with different behavior between
+// the platforms) we mimick that by posting a task after a menu item is selected
+// then notify.
+//
+// TODO(beng): rename to MenuGtk once the old class is dead.
+class NativeMenuGtk : public MenuWrapper,
+ public MessageLoopForUI::Dispatcher {
+ public:
+ explicit NativeMenuGtk(Menu2* menu);
+ virtual ~NativeMenuGtk();
+
+ // Overridden from MenuWrapper:
+ virtual void RunMenuAt(const gfx::Point& point, int alignment) OVERRIDE;
+ virtual void CancelMenu() OVERRIDE;
+ virtual void Rebuild() OVERRIDE;
+ virtual void UpdateStates() OVERRIDE;
+ virtual gfx::NativeMenu GetNativeMenu() const OVERRIDE;
+ virtual MenuAction GetMenuAction() const OVERRIDE;
+ virtual void AddMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void RemoveMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void SetMinimumWidth(int width) OVERRIDE;
+
+ // Overriden from MessageLoopForUI::Dispatcher:
+ virtual bool Dispatch(GdkEvent* event) OVERRIDE;
+
+ private:
+ CHROMEGTK_CALLBACK_0(NativeMenuGtk, void, OnMenuHidden);
+ CHROMEGTK_CALLBACK_1(NativeMenuGtk, void, OnMenuMoveCurrent,
+ GtkMenuDirectionType);
+ CHROMEGTK_CALLBACK_1(NativeMenuGtk, void, AfterMenuMoveCurrent,
+ GtkMenuDirectionType);
+ CHROMEGTK_CALLBACK_1(NativeMenuGtk, gboolean, OnExpose, GdkEventExpose*);
+
+ void AddSeparatorAt(int index);
+ GtkWidget* AddMenuItemAt(int index, GtkRadioMenuItem* radio_group,
+ GtkAccelGroup* accel_group);
+
+ void ResetMenu();
+
+ // Updates the menu item's state.
+ void UpdateMenuItemState(GtkWidget* menu_item, bool recurse);
+
+ static void UpdateStateCallback(GtkWidget* menu_item, gpointer data);
+
+ // Callback for gtk_menu_popup to position the menu.
+ static void MenuPositionFunc(GtkMenu* menu, int* x, int* y, gboolean* push_in,
+ void* data);
+
+ // Event handlers:
+ void OnActivate(GtkMenuItem* menu_item);
+
+ // Gtk signal handlers.
+ static void CallActivate(GtkMenuItem* menu_item, NativeMenuGtk* native_menu);
+
+ // Sets the parent of this menu.
+ void set_parent(NativeMenuGtk* parent) { parent_ = parent; }
+
+ // Returns the root of the menu tree.
+ NativeMenuGtk* GetAncestor();
+
+ // Callback that we should really process the menu activation.
+ // See description above class for why we delay processing activation.
+ void ProcessActivate();
+
+ // Notifies the model the user selected an item.
+ void Activate();
+
+ void SendAccessibilityEvent();
+
+ // A callback to delete menu2 object when the native widget is
+ // destroyed first.
+ static void MenuDestroyed(GtkWidget* widget, Menu2* menu2);
+
+ // If we're a submenu, this is the parent.
+ NativeMenuGtk* parent_;
+
+ ui::MenuModel* model_;
+
+ GtkWidget* menu_;
+
+ // Has the menu been hidden?
+ // NOTE: this is maintained by us and do to the asynchronous nature of X may
+ // be out of sync with whether the window is actually hidden. None-the-less if
+ // true the menu is either truly hidden or in the process of hiding.
+ bool menu_hidden_;
+
+ // A flag used to avoid misfiring ActivateAt call on the menu model.
+ // This is necessary as GTK menu fires an activate signal even when the
+ // state is changed by |UpdateStates()| API.
+ bool suppress_activate_signal_;
+
+ // If the user selects something from the menu this is the menu they selected
+ // it from. When an item is selected menu_activated_ on the root ancestor is
+ // set to the menu the user selected and after the nested message loop exits
+ // Activate is invoked on this menu.
+ NativeMenuGtk* activated_menu_;
+
+ // The index of the item the user selected. This is set on the actual menu the
+ // user selects and not the root.
+ int activated_index_;
+
+ // Used when a menu item is selected. See description above class as to why
+ // we do this.
+ base::WeakPtrFactory<NativeMenuGtk> activate_factory_;
+
+ // A eference to the hosting menu2 object and signal handler id
+ // used to delete the menu2 when its native menu gtk is destroyed first.
+ Menu2* host_menu_;
+ gulong destroy_handler_id_;
+ gulong expose_handler_id_;
+
+ // The action that took place during the call to RunMenuAt.
+ MenuAction menu_action_;
+
+ // A list of listeners to call when the menu opens.
+ ObserverList<MenuListener> listeners_;
+
+ // Nested dispatcher object that can outlive this object.
+ // This is to deal with the menu being deleted while the nested
+ // message loop is handled. see http://crosbug.com/7228 .
+ NestedDispatcherGtk* nested_dispatcher_;
+
+ // A flag used to detect a button release event without button press or move.
+ // see http://crosbug.com/8718.
+ bool ignore_button_release_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeMenuGtk);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_
diff --git a/ui/views/controls/menu/native_menu_host.h b/ui/views/controls/menu/native_menu_host.h
new file mode 100644
index 0000000..789f6f6
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_host.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_H_
+#define UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_H_
+#pragma once
+
+#include "ui/gfx/native_widget_types.h"
+
+namespace views {
+
+class NativeWidget;
+
+namespace internal {
+class NativeMenuHostDelegate;
+}
+
+class NativeMenuHost {
+ public:
+ virtual ~NativeMenuHost() {}
+
+ static NativeMenuHost* CreateNativeMenuHost(
+ internal::NativeMenuHostDelegate* delegate);
+
+ // Starts capturing input events.
+ virtual void StartCapturing() = 0;
+
+ virtual NativeWidget* AsNativeWidget() = 0;
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_H_
diff --git a/ui/views/controls/menu/native_menu_host_delegate.h b/ui/views/controls/menu/native_menu_host_delegate.h
new file mode 100644
index 0000000..592cf6c
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_host_delegate.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_DELEGATE_H_
+#define UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_DELEGATE_H_
+#pragma once
+namespace views {
+
+class MenuHost;
+
+namespace internal {
+
+class NativeWidgetDelegate;
+
+class NativeMenuHostDelegate {
+ public:
+ virtual ~NativeMenuHostDelegate() {}
+
+ // Called when the NativeMenuHost is being destroyed.
+ virtual void OnNativeMenuHostDestroy() = 0;
+
+ // Called when the NativeMenuHost is losing input capture.
+ virtual void OnNativeMenuHostCancelCapture() = 0;
+
+ virtual NativeWidgetDelegate* AsNativeWidgetDelegate() = 0;
+};
+
+} // namespace internal
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_HOST_DELEGATE_H_
diff --git a/ui/views/controls/menu/native_menu_views.cc b/ui/views/controls/menu/native_menu_views.cc
new file mode 100644
index 0000000..9abc19d
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_views.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/native_menu_views.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/controls/menu/menu_2.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_listener.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/controls/menu/submenu_view.h"
+
+namespace views {
+
+NativeMenuViews::NativeMenuViews(Menu2* menu)
+ : model_(menu->model()),
+ ALLOW_THIS_IN_INITIALIZER_LIST(root_(new MenuItemView(this))),
+ menu_runner_(new MenuRunner(root_)) {
+}
+
+NativeMenuViews::~NativeMenuViews() {
+}
+
+// MenuWrapper implementation:
+void NativeMenuViews::RunMenuAt(const gfx::Point& point, int alignment) {
+ // TODO: this should really return the value from MenuRunner.
+ UpdateStates();
+ if (menu_runner_->RunMenuAt(NULL, NULL, gfx::Rect(point, gfx::Size()),
+ alignment == Menu2::ALIGN_TOPLEFT ? MenuItemView::TOPLEFT :
+ MenuItemView::TOPRIGHT, MenuRunner::HAS_MNEMONICS) ==
+ MenuRunner::MENU_DELETED)
+ return;
+}
+
+void NativeMenuViews::CancelMenu() {
+ NOTIMPLEMENTED();
+}
+
+void NativeMenuViews::Rebuild() {
+ if (SubmenuView* submenu = root_->GetSubmenu())
+ submenu->RemoveAllChildViews(true);
+ AddMenuItemsFromModel(root_, model_);
+}
+
+void NativeMenuViews::UpdateStates() {
+ SubmenuView* submenu = root_->CreateSubmenu();
+ UpdateMenuFromModel(submenu, model_);
+}
+
+gfx::NativeMenu NativeMenuViews::GetNativeMenu() const {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+MenuWrapper::MenuAction NativeMenuViews::GetMenuAction() const {
+ NOTIMPLEMENTED();
+ return MENU_ACTION_NONE;
+}
+
+void NativeMenuViews::AddMenuListener(MenuListener* listener) {
+ NOTIMPLEMENTED();
+}
+
+void NativeMenuViews::RemoveMenuListener(MenuListener* listener) {
+ NOTIMPLEMENTED();
+}
+
+void NativeMenuViews::SetMinimumWidth(int width) {
+ NOTIMPLEMENTED();
+}
+
+// MenuDelegate implementation
+
+bool NativeMenuViews::IsItemChecked(int cmd) const {
+ int index;
+ ui::MenuModel* model = model_;
+ if (!ui::MenuModel::GetModelAndIndexForCommandId(cmd, &model, &index))
+ return false;
+ return model->IsItemCheckedAt(index);
+}
+
+bool NativeMenuViews::IsCommandEnabled(int cmd) const {
+ int index;
+ ui::MenuModel* model = model_;
+ if (!ui::MenuModel::GetModelAndIndexForCommandId(cmd, &model, &index))
+ return false;
+ return model->IsEnabledAt(index);
+}
+
+void NativeMenuViews::ExecuteCommand(int cmd) {
+ int index;
+ ui::MenuModel* model = model_;
+ if (!ui::MenuModel::GetModelAndIndexForCommandId(cmd, &model, &index))
+ return;
+ model->ActivatedAt(index);
+}
+
+bool NativeMenuViews::GetAccelerator(int id, ui::Accelerator* accelerator) {
+ int index;
+ ui::MenuModel* model = model_;
+ if (!ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index))
+ return false;
+
+ ui::Accelerator menu_accelerator;
+ if (!model->GetAcceleratorAt(index, &menu_accelerator))
+ return false;
+
+ *accelerator = ui::Accelerator(menu_accelerator.key_code(),
+ menu_accelerator.modifiers());
+ return true;
+}
+
+// private
+void NativeMenuViews::AddMenuItemsFromModel(MenuItemView* parent,
+ ui::MenuModel* model) {
+ for (int i = 0; i < model->GetItemCount(); ++i) {
+ int index = i + model->GetFirstItemIndex(NULL);
+ MenuItemView* child = parent->AppendMenuItemFromModel(model, index,
+ model->GetCommandIdAt(index));
+
+ if (child && child->GetType() == MenuItemView::SUBMENU) {
+ AddMenuItemsFromModel(child, model->GetSubmenuModelAt(index));
+ }
+ }
+}
+
+void NativeMenuViews::UpdateMenuFromModel(SubmenuView* menu,
+ ui::MenuModel* model) {
+ for (int i = 0, sep = 0; i < model->GetItemCount(); ++i) {
+ int index = i + model->GetFirstItemIndex(NULL);
+ if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) {
+ ++sep;
+ continue;
+ }
+
+ // The submenu excludes the separators when counting the menu-items
+ // in it. So exclude the number of separators to get the correct index.
+ MenuItemView* mitem = menu->GetMenuItemAt(index - sep);
+ mitem->SetVisible(model->IsVisibleAt(index));
+ mitem->SetEnabled(model->IsEnabledAt(index));
+ if (model->IsItemDynamicAt(index)) {
+ mitem->SetTitle(model->GetLabelAt(index));
+ }
+
+ SkBitmap icon;
+ if (model->GetIconAt(index, &icon)) {
+ // TODO(atwilson): Support removing the icon dynamically
+ // (http://crbug.com/66508).
+ mitem->SetIcon(icon);
+ }
+
+ if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SUBMENU) {
+ DCHECK(mitem->HasSubmenu());
+ UpdateMenuFromModel(mitem->GetSubmenu(), model->GetSubmenuModelAt(index));
+ }
+ }
+}
+
+// MenuWrapper, public:
+
+// static
+MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
+ return new NativeMenuViews(menu);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/native_menu_views.h b/ui/views/controls/menu/native_menu_views.h
new file mode 100644
index 0000000..9f5d92d
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_views.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_VIEWS_H_
+#define UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_VIEWS_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+#include "ui/views/controls/menu/menu_wrapper.h"
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+
+class MenuItemView;
+class MenuRunner;
+
+// A views implementation of MenuWrapper, used currently for touchui.
+class NativeMenuViews : public MenuWrapper,
+ public MenuDelegate {
+ public:
+ explicit NativeMenuViews(Menu2* menu);
+ virtual ~NativeMenuViews();
+
+ // Overridden from MenuWrapper:
+ virtual void RunMenuAt(const gfx::Point& point, int alignment) OVERRIDE;
+ virtual void CancelMenu() OVERRIDE;
+ virtual void Rebuild() OVERRIDE;
+ virtual void UpdateStates() OVERRIDE;
+ virtual gfx::NativeMenu GetNativeMenu() const OVERRIDE;
+ virtual MenuAction GetMenuAction() const OVERRIDE;
+ virtual void AddMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void RemoveMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void SetMinimumWidth(int width) OVERRIDE;
+
+ // Overridden from MenuDelegate:
+ virtual bool IsItemChecked(int id) const OVERRIDE;
+ virtual bool IsCommandEnabled(int id) const OVERRIDE;
+ virtual void ExecuteCommand(int id) OVERRIDE;
+ virtual bool GetAccelerator(int id, ui::Accelerator* accelerator) OVERRIDE;
+
+ private:
+ void AddMenuItemsFromModel(MenuItemView* parent, ui::MenuModel* model);
+ void UpdateMenuFromModel(SubmenuView* menu, ui::MenuModel* model);
+
+ // The attached model and delegate. Does not assume ownership.
+ ui::MenuModel* model_;
+ MenuItemView* root_;
+ scoped_ptr<MenuRunner> menu_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeMenuViews);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_VIEWS_H_
diff --git a/ui/views/controls/menu/native_menu_win.cc b/ui/views/controls/menu/native_menu_win.cc
new file mode 100644
index 0000000..f0dff18
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_win.cc
@@ -0,0 +1,765 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/native_menu_win.h"
+
+#include <Windowsx.h>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/stl_util.h"
+#include "base/string_util.h"
+#include "base/win/wrapped_window_proc.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_win.h"
+#include "ui/base/win/hwnd_util.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/native_theme.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/controls/menu/menu_2.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_listener.h"
+
+using gfx::NativeTheme;
+
+namespace views {
+
+// 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;
+
+struct NativeMenuWin::ItemData {
+ // The Windows API requires that whoever creates the menus must own the
+ // strings used for labels, and keep them around for the lifetime of the
+ // created menu. So be it.
+ string16 label;
+
+ // Someone needs to own submenus, it may as well be us.
+ scoped_ptr<Menu2> submenu;
+
+ // We need a pointer back to the containing menu in various circumstances.
+ NativeMenuWin* native_menu_win;
+
+ // The index of the item within the menu's model.
+ int model_index;
+};
+
+// Returns the NativeMenuWin for a particular HMENU.
+static NativeMenuWin* GetNativeMenuWinFromHMENU(HMENU hmenu) {
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(mi);
+ mi.fMask = MIM_MENUDATA | MIM_STYLE;
+ GetMenuInfo(hmenu, &mi);
+ return reinterpret_cast<NativeMenuWin*>(mi.dwMenuData);
+}
+
+// A window that receives messages from Windows relevant to the native menu
+// structure we have constructed in NativeMenuWin.
+class NativeMenuWin::MenuHostWindow {
+ public:
+ MenuHostWindow(NativeMenuWin* parent) : parent_(parent) {
+ RegisterClass();
+ hwnd_ = CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName,
+ L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
+ ui::CheckWindowCreated(hwnd_);
+ ui::SetWindowUserData(hwnd_, this);
+ }
+
+ ~MenuHostWindow() {
+ DestroyWindow(hwnd_);
+ }
+
+ HWND hwnd() const { return hwnd_; }
+
+ private:
+ static const wchar_t* kWindowClassName;
+
+ void RegisterClass() {
+ static bool registered = false;
+ if (registered)
+ return;
+
+ WNDCLASSEX wcex = {0};
+ wcex.cbSize = sizeof(WNDCLASSEX);
+ wcex.style = CS_DBLCLKS;
+ wcex.lpfnWndProc = base::win::WrappedWindowProc<&MenuHostWindowProc>;
+ wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);
+ wcex.lpszClassName = kWindowClassName;
+ ATOM clazz = RegisterClassEx(&wcex);
+ DCHECK(clazz);
+ registered = true;
+ }
+
+ // Converts the WPARAM value passed to WM_MENUSELECT into an index
+ // corresponding to the menu item that was selected.
+ int GetMenuItemIndexFromWPARAM(HMENU menu, WPARAM w_param) const {
+ int count = GetMenuItemCount(menu);
+ // For normal command menu items, Windows passes a command id as the LOWORD
+ // of WPARAM for WM_MENUSELECT. We need to walk forward through the menu
+ // items to find an item with a matching ID. Ugh!
+ for (int i = 0; i < count; ++i) {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_ID;
+ GetMenuItemInfo(menu, i, MF_BYPOSITION, &mii);
+ if (mii.wID == w_param)
+ return i;
+ }
+ // If we didn't find a matching command ID, this means a submenu has been
+ // selected instead, and rather than passing a command ID in
+ // LOWORD(w_param), Windows has actually passed us a position, so we just
+ // return it.
+ return w_param;
+ }
+
+ NativeMenuWin::ItemData* GetItemData(ULONG_PTR item_data) {
+ return reinterpret_cast<NativeMenuWin::ItemData*>(item_data);
+ }
+
+ // Called when the user selects a specific item.
+ void OnMenuCommand(int position, HMENU menu) {
+ NativeMenuWin* menu_win = GetNativeMenuWinFromHMENU(menu);
+ ui::MenuModel* model = menu_win->model_;
+ NativeMenuWin* root_menu = menu_win;
+ while (root_menu->parent_)
+ root_menu = root_menu->parent_;
+
+ // Only notify the model if it didn't already send out notification.
+ // See comment in MenuMessageHook for details.
+ if (root_menu->menu_action_ == MenuWrapper::MENU_ACTION_NONE)
+ model->ActivatedAt(position);
+ }
+
+ // Called as the user moves their mouse or arrows through the contents of the
+ // menu.
+ void OnMenuSelect(WPARAM w_param, HMENU menu) {
+ if (!menu)
+ return; // menu is null when closing on XP.
+
+ int position = GetMenuItemIndexFromWPARAM(menu, w_param);
+ if (position >= 0)
+ GetNativeMenuWinFromHMENU(menu)->model_->HighlightChangedTo(position);
+ }
+
+ // Called by Windows to measure the size of an owner-drawn menu item.
+ void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* measure_item_struct) {
+ NativeMenuWin::ItemData* data = GetItemData(measure_item_struct->itemData);
+ if (data) {
+ gfx::Font font;
+ measure_item_struct->itemWidth = font.GetStringWidth(data->label) +
+ kIconWidth + kItemLeftMargin + kItemRightMargin -
+ GetSystemMetrics(SM_CXMENUCHECK);
+ if (data->submenu.get())
+ measure_item_struct->itemWidth += kArrowWidth;
+ // If the label contains an accelerator, make room for tab.
+ if (data->label.find(L'\t') != string16::npos)
+ measure_item_struct->itemWidth += font.GetStringWidth(L" ");
+ measure_item_struct->itemHeight =
+ font.GetHeight() + kItemBottomMargin + kItemTopMargin;
+ } else {
+ // Measure separator size.
+ measure_item_struct->itemHeight = GetSystemMetrics(SM_CYMENU) / 2;
+ measure_item_struct->itemWidth = 0;
+ }
+ }
+
+ // Called by Windows to paint an owner-drawn menu item.
+ void OnDrawItem(UINT w_param, DRAWITEMSTRUCT* draw_item_struct) {
+ HDC dc = draw_item_struct->hDC;
+ COLORREF prev_bg_color, prev_text_color;
+
+ // Set background color and text color
+ if (draw_item_struct->itemState & ODS_SELECTED) {
+ prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT));
+ prev_text_color = SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ } else {
+ prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_MENU));
+ if (draw_item_struct->itemState & ODS_DISABLED)
+ prev_text_color = SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT));
+ else
+ prev_text_color = SetTextColor(dc, GetSysColor(COLOR_MENUTEXT));
+ }
+
+ if (draw_item_struct->itemData) {
+ NativeMenuWin::ItemData* data = GetItemData(draw_item_struct->itemData);
+ // Draw the background.
+ HBRUSH hbr = CreateSolidBrush(GetBkColor(dc));
+ FillRect(dc, &draw_item_struct->rcItem, hbr);
+ DeleteObject(hbr);
+
+ // Draw the label.
+ RECT rect = draw_item_struct->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;
+ gfx::Font font;
+ HGDIOBJ old_font =
+ static_cast<HFONT>(SelectObject(dc, font.GetNativeFont()));
+
+ // 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.
+ string16 label = data->label;
+ string16 accel;
+ string16::size_type tab_pos = label.find(L'\t');
+ if (tab_pos != string16::npos) {
+ accel = label.substr(tab_pos);
+ label = label.substr(0, tab_pos);
+ }
+ DrawTextEx(dc, const_cast<wchar_t*>(label.data()),
+ static_cast<int>(label.size()), &rect, format | DT_LEFT, NULL);
+ if (!accel.empty())
+ DrawTextEx(dc, const_cast<wchar_t*>(accel.data()),
+ static_cast<int>(accel.size()), &rect,
+ format | DT_RIGHT, NULL);
+ SelectObject(dc, old_font);
+
+ ui::MenuModel::ItemType type =
+ data->native_menu_win->model_->GetTypeAt(data->model_index);
+
+ // Draw the icon after the label, otherwise it would be covered
+ // by the label.
+ SkBitmap icon;
+ if (data->native_menu_win->model_->GetIconAt(data->model_index, &icon)) {
+ // We currently don't support items with both icons and checkboxes.
+ DCHECK(type != ui::MenuModel::TYPE_CHECK);
+ gfx::CanvasSkia canvas(icon.width(), icon.height(), false);
+ canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
+ canvas.DrawBitmapInt(icon, 0, 0);
+ skia::DrawToNativeContext(
+ canvas.sk_canvas(), dc,
+ draw_item_struct->rcItem.left + kItemLeftMargin,
+ draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom -
+ draw_item_struct->rcItem.top - icon.height()) / 2, NULL);
+ } else if (type == ui::MenuModel::TYPE_CHECK &&
+ data->native_menu_win->model_->IsItemCheckedAt(
+ data->model_index)) {
+ // Manually render a checkbox.
+ const MenuConfig& config = MenuConfig::instance();
+ NativeTheme::State state;
+ if (draw_item_struct->itemState & ODS_DISABLED) {
+ state = NativeTheme::kDisabled;
+ } else {
+ state = draw_item_struct->itemState & ODS_SELECTED ?
+ NativeTheme::kHovered : NativeTheme::kNormal;
+ }
+ int height =
+ draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top;
+ int icon_y = kItemTopMargin +
+ (height - kItemTopMargin - kItemBottomMargin -
+ config.check_height) / 2;
+ gfx::CanvasSkia canvas(config.check_width, config.check_height, false);
+ NativeTheme::ExtraParams extra;
+ extra.menu_check.is_radio = false;
+ gfx::Rect bounds(0, 0, config.check_width, config.check_height);
+
+ // Draw the background and the check.
+ NativeTheme::instance()->Paint(
+ canvas.sk_canvas(), NativeTheme::kMenuCheckBackground,
+ state, bounds, extra);
+ NativeTheme::instance()->Paint(
+ canvas.sk_canvas(), NativeTheme::kMenuCheck, state, bounds, extra);
+
+ // Draw checkbox to menu.
+ skia::DrawToNativeContext(canvas.sk_canvas(), dc,
+ draw_item_struct->rcItem.left + kItemLeftMargin,
+ draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom -
+ draw_item_struct->rcItem.top - config.check_height) / 2, NULL);
+ }
+
+ } else {
+ // Draw the separator
+ draw_item_struct->rcItem.top +=
+ (draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top) / 3;
+ DrawEdge(dc, &draw_item_struct->rcItem, EDGE_ETCHED, BF_TOP);
+ }
+
+ SetBkColor(dc, prev_bg_color);
+ SetTextColor(dc, prev_text_color);
+ }
+
+ bool ProcessWindowMessage(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param,
+ LRESULT* l_result) {
+ switch (message) {
+ case WM_MENUCOMMAND:
+ OnMenuCommand(w_param, reinterpret_cast<HMENU>(l_param));
+ *l_result = 0;
+ return true;
+ case WM_MENUSELECT:
+ OnMenuSelect(LOWORD(w_param), reinterpret_cast<HMENU>(l_param));
+ *l_result = 0;
+ return true;
+ case WM_MEASUREITEM:
+ OnMeasureItem(w_param, reinterpret_cast<MEASUREITEMSTRUCT*>(l_param));
+ *l_result = 0;
+ return true;
+ case WM_DRAWITEM:
+ OnDrawItem(w_param, reinterpret_cast<DRAWITEMSTRUCT*>(l_param));
+ *l_result = 0;
+ return true;
+ // TODO(beng): bring over owner draw from old menu system.
+ }
+ return false;
+ }
+
+ static LRESULT CALLBACK MenuHostWindowProc(HWND window,
+ UINT message,
+ WPARAM w_param,
+ LPARAM l_param) {
+ MenuHostWindow* host =
+ reinterpret_cast<MenuHostWindow*>(ui::GetWindowUserData(window));
+ // host is null during initial construction.
+ LRESULT l_result = 0;
+ if (!host || !host->ProcessWindowMessage(window, message, w_param, l_param,
+ &l_result)) {
+ return DefWindowProc(window, message, w_param, l_param);
+ }
+ return l_result;
+ }
+
+ HWND hwnd_;
+ NativeMenuWin* parent_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHostWindow);
+};
+
+struct NativeMenuWin::HighlightedMenuItemInfo {
+ HighlightedMenuItemInfo()
+ : has_parent(false),
+ has_submenu(false),
+ menu(NULL),
+ position(-1) {
+ }
+
+ bool has_parent;
+ bool has_submenu;
+
+ // The menu and position. These are only set for non-disabled menu items.
+ NativeMenuWin* menu;
+ int position;
+};
+
+// static
+const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName =
+ L"ViewsMenuHostWindow";
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuWin, public:
+
+NativeMenuWin::NativeMenuWin(ui::MenuModel* model, HWND system_menu_for)
+ : model_(model),
+ menu_(NULL),
+ owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL) &&
+ !system_menu_for),
+ system_menu_for_(system_menu_for),
+ first_item_index_(0),
+ menu_action_(MENU_ACTION_NONE),
+ menu_to_select_(NULL),
+ position_to_select_(-1),
+ ALLOW_THIS_IN_INITIALIZER_LIST(menu_to_select_factory_(this)),
+ parent_(NULL),
+ destroyed_flag_(NULL) {
+}
+
+NativeMenuWin::~NativeMenuWin() {
+ if (destroyed_flag_)
+ *destroyed_flag_ = true;
+ STLDeleteContainerPointers(items_.begin(), items_.end());
+ DestroyMenu(menu_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuWin, MenuWrapper implementation:
+
+void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) {
+ CreateHostWindow();
+ UpdateStates();
+ UINT flags = TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RECURSE;
+ flags |= GetAlignmentFlags(alignment);
+ menu_action_ = MENU_ACTION_NONE;
+
+ // Set a hook function so we can listen for keyboard events while the
+ // menu is open, and store a pointer to this object in a static
+ // variable so the hook has access to it (ugly, but it's the
+ // only way).
+ open_native_menu_win_ = this;
+ HHOOK hhook = SetWindowsHookEx(WH_MSGFILTER, MenuMessageHook,
+ GetModuleHandle(NULL), ::GetCurrentThreadId());
+
+ // Mark that any registered listeners have not been called for this particular
+ // opening of the menu.
+ listeners_called_ = false;
+
+ // Command dispatch is done through WM_MENUCOMMAND, handled by the host
+ // window.
+ HWND hwnd = host_window_->hwnd();
+ menu_to_select_ = NULL;
+ position_to_select_ = -1;
+ menu_to_select_factory_.InvalidateWeakPtrs();
+ bool destroyed = false;
+ destroyed_flag_ = &destroyed;
+ model_->MenuWillShow();
+ TrackPopupMenu(menu_, flags, point.x(), point.y(), 0, host_window_->hwnd(),
+ NULL);
+ UnhookWindowsHookEx(hhook);
+ open_native_menu_win_ = NULL;
+ if (destroyed)
+ return;
+ destroyed_flag_ = NULL;
+ if (menu_to_select_) {
+ // Folks aren't too happy if we notify immediately. In particular, notifying
+ // the delegate can cause destruction leaving the stack in a weird
+ // state. Instead post a task, then notify. This mirrors what WM_MENUCOMMAND
+ // does.
+ menu_to_select_factory_.InvalidateWeakPtrs();
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&NativeMenuWin::DelayedSelect,
+ menu_to_select_factory_.GetWeakPtr()));
+ menu_action_ = MENU_ACTION_SELECTED;
+ }
+ // Send MenuClosed after we schedule the select, otherwise MenuClosed is
+ // processed after the select (MenuClosed posts a delayed task too).
+ model_->MenuClosed();
+}
+
+void NativeMenuWin::CancelMenu() {
+ EndMenu();
+}
+
+void NativeMenuWin::Rebuild() {
+ ResetNativeMenu();
+ items_.clear();
+
+ owner_draw_ = model_->HasIcons() || owner_draw_;
+ first_item_index_ = model_->GetFirstItemIndex(GetNativeMenu());
+ for (int menu_index = first_item_index_;
+ menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) {
+ int model_index = menu_index - first_item_index_;
+ if (model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SEPARATOR)
+ AddSeparatorItemAt(menu_index, model_index);
+ else
+ AddMenuItemAt(menu_index, model_index);
+ }
+}
+
+void NativeMenuWin::UpdateStates() {
+ // A depth-first walk of the menu items, updating states.
+ int model_index = 0;
+ std::vector<ItemData*>::const_iterator it;
+ for (it = items_.begin(); it != items_.end(); ++it, ++model_index) {
+ int menu_index = model_index + first_item_index_;
+ SetMenuItemState(menu_index, model_->IsEnabledAt(model_index),
+ model_->IsItemCheckedAt(model_index), false);
+ if (model_->IsItemDynamicAt(model_index)) {
+ // TODO(atwilson): Update the icon as well (http://crbug.com/66508).
+ SetMenuItemLabel(menu_index, model_index,
+ model_->GetLabelAt(model_index));
+ }
+ Menu2* submenu = (*it)->submenu.get();
+ if (submenu)
+ submenu->UpdateStates();
+ }
+}
+
+gfx::NativeMenu NativeMenuWin::GetNativeMenu() const {
+ return menu_;
+}
+
+NativeMenuWin::MenuAction NativeMenuWin::GetMenuAction() const {
+ return menu_action_;
+}
+
+void NativeMenuWin::AddMenuListener(MenuListener* listener) {
+ listeners_.AddObserver(listener);
+}
+
+void NativeMenuWin::RemoveMenuListener(MenuListener* listener) {
+ listeners_.RemoveObserver(listener);
+}
+
+void NativeMenuWin::SetMinimumWidth(int width) {
+ NOTIMPLEMENTED();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuWin, private:
+
+// static
+NativeMenuWin* NativeMenuWin::open_native_menu_win_ = NULL;
+
+void NativeMenuWin::DelayedSelect() {
+ if (menu_to_select_)
+ menu_to_select_->model_->ActivatedAt(position_to_select_);
+}
+
+// static
+bool NativeMenuWin::GetHighlightedMenuItemInfo(
+ HMENU menu,
+ HighlightedMenuItemInfo* info) {
+ for (int i = 0; i < ::GetMenuItemCount(menu); i++) {
+ UINT state = ::GetMenuState(menu, i, MF_BYPOSITION);
+ if (state & MF_HILITE) {
+ if (state & MF_POPUP) {
+ HMENU submenu = GetSubMenu(menu, i);
+ if (GetHighlightedMenuItemInfo(submenu, info))
+ info->has_parent = true;
+ else
+ info->has_submenu = true;
+ } else if (!(state & MF_SEPARATOR) && !(state & MF_DISABLED)) {
+ info->menu = GetNativeMenuWinFromHMENU(menu);
+ info->position = i;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+LRESULT CALLBACK NativeMenuWin::MenuMessageHook(
+ int n_code, WPARAM w_param, LPARAM l_param) {
+ LRESULT result = CallNextHookEx(NULL, n_code, w_param, l_param);
+
+ NativeMenuWin* this_ptr = open_native_menu_win_;
+ if (!this_ptr)
+ return result;
+
+ // The first time this hook is called, that means the menu has successfully
+ // opened, so call the callback function on all of our listeners.
+ if (!this_ptr->listeners_called_) {
+ FOR_EACH_OBSERVER(MenuListener, this_ptr->listeners_, OnMenuOpened());
+ this_ptr->listeners_called_ = true;
+ }
+
+ MSG* msg = reinterpret_cast<MSG*>(l_param);
+ if (msg->message == WM_LBUTTONUP || msg->message == WM_RBUTTONUP) {
+ HighlightedMenuItemInfo info;
+ if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info) && info.menu) {
+ // It appears that when running a menu by way of TrackPopupMenu(Ex) win32
+ // gets confused if the underlying window paints itself. As its very easy
+ // for the underlying window to repaint itself (especially since some menu
+ // items trigger painting of the tabstrip on mouse over) we have this
+ // workaround. When the mouse is released on a menu item we remember the
+ // menu item and end the menu. When the nested message loop returns we
+ // schedule a task to notify the model. It's still possible to get a
+ // WM_MENUCOMMAND, so we have to be careful that we don't notify the model
+ // twice.
+ this_ptr->menu_to_select_ = info.menu;
+ this_ptr->position_to_select_ = info.position;
+ EndMenu();
+ }
+ } else if (msg->message == WM_KEYDOWN) {
+ HighlightedMenuItemInfo info;
+ if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info)) {
+ if (msg->wParam == VK_LEFT && !info.has_parent) {
+ this_ptr->menu_action_ = MENU_ACTION_PREVIOUS;
+ ::EndMenu();
+ } else if (msg->wParam == VK_RIGHT && !info.has_parent &&
+ !info.has_submenu) {
+ this_ptr->menu_action_ = MENU_ACTION_NEXT;
+ ::EndMenu();
+ }
+ }
+ }
+
+ return result;
+}
+
+bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
+ return !!(mii.fType & MF_SEPARATOR);
+}
+
+void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA;
+ if (!owner_draw_)
+ mii.fType = MFT_STRING;
+ else
+ mii.fType = MFT_OWNERDRAW;
+
+ ItemData* item_data = new ItemData;
+ item_data->label = string16();
+ ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
+ if (type == ui::MenuModel::TYPE_SUBMENU) {
+ item_data->submenu.reset(new Menu2(model_->GetSubmenuModelAt(model_index)));
+ mii.fMask |= MIIM_SUBMENU;
+ mii.hSubMenu = item_data->submenu->GetNativeMenu();
+ GetNativeMenuWinFromHMENU(mii.hSubMenu)->parent_ = this;
+ } else {
+ if (type == ui::MenuModel::TYPE_RADIO)
+ mii.fType |= MFT_RADIOCHECK;
+ mii.wID = model_->GetCommandIdAt(model_index);
+ }
+ item_data->native_menu_win = this;
+ item_data->model_index = model_index;
+ items_.insert(items_.begin() + model_index, item_data);
+ mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data);
+ UpdateMenuItemInfoForString(&mii, model_index,
+ model_->GetLabelAt(model_index));
+ InsertMenuItem(menu_, menu_index, TRUE, &mii);
+}
+
+void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) {
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_FTYPE;
+ mii.fType = MFT_SEPARATOR;
+ // Insert a dummy entry into our label list so we can index directly into it
+ // using item indices if need be.
+ items_.insert(items_.begin() + model_index, new ItemData);
+ InsertMenuItem(menu_, menu_index, TRUE, &mii);
+}
+
+void NativeMenuWin::SetMenuItemState(int menu_index, bool enabled, bool checked,
+ bool is_default) {
+ if (IsSeparatorItemAt(menu_index))
+ return;
+
+ UINT state = enabled ? MFS_ENABLED : MFS_DISABLED;
+ if (checked)
+ state |= MFS_CHECKED;
+ if (is_default)
+ state |= MFS_DEFAULT;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_STATE;
+ mii.fState = state;
+ SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
+}
+
+void NativeMenuWin::SetMenuItemLabel(int menu_index,
+ int model_index,
+ const string16& label) {
+ if (IsSeparatorItemAt(menu_index))
+ return;
+
+ MENUITEMINFO mii = {0};
+ mii.cbSize = sizeof(mii);
+ UpdateMenuItemInfoForString(&mii, model_index, label);
+ SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
+}
+
+void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
+ int model_index,
+ const string16& label) {
+ string16 formatted = label;
+ ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
+ // Strip out any tabs, otherwise they get interpreted as accelerators and can
+ // lead to weird behavior.
+ ReplaceSubstringsAfterOffset(&formatted, 0, L"\t", L" ");
+ if (type != ui::MenuModel::TYPE_SUBMENU) {
+ // Add accelerator details to the label if provided.
+ ui::Accelerator accelerator(ui::VKEY_UNKNOWN, false, false, false);
+ if (model_->GetAcceleratorAt(model_index, &accelerator)) {
+ formatted += L"\t";
+ formatted += accelerator.GetShortcutText();
+ }
+ }
+
+ // Update the owned string, since Windows will want us to keep this new
+ // version around.
+ items_[model_index]->label = formatted;
+
+ // Give Windows a pointer to the label string.
+ mii->fMask |= MIIM_STRING;
+ mii->dwTypeData =
+ const_cast<wchar_t*>(items_[model_index]->label.c_str());
+}
+
+UINT NativeMenuWin::GetAlignmentFlags(int alignment) const {
+ UINT alignment_flags = TPM_TOPALIGN;
+ if (alignment == Menu2::ALIGN_TOPLEFT)
+ alignment_flags |= TPM_LEFTALIGN;
+ else if (alignment == Menu2::ALIGN_TOPRIGHT)
+ alignment_flags |= TPM_RIGHTALIGN;
+ return alignment_flags;
+}
+
+void NativeMenuWin::ResetNativeMenu() {
+ if (IsWindow(system_menu_for_)) {
+ if (menu_)
+ GetSystemMenu(system_menu_for_, TRUE);
+ menu_ = GetSystemMenu(system_menu_for_, FALSE);
+ } else {
+ if (menu_)
+ DestroyMenu(menu_);
+ menu_ = CreatePopupMenu();
+ // Rather than relying on the return value of TrackPopupMenuEx, which is
+ // always a command identifier, instead we tell the menu to notify us via
+ // our host window and the WM_MENUCOMMAND message.
+ MENUINFO mi = {0};
+ mi.cbSize = sizeof(mi);
+ mi.fMask = MIM_STYLE | MIM_MENUDATA;
+ mi.dwStyle = MNS_NOTIFYBYPOS;
+ mi.dwMenuData = reinterpret_cast<ULONG_PTR>(this);
+ SetMenuInfo(menu_, &mi);
+ }
+}
+
+void NativeMenuWin::CreateHostWindow() {
+ // This only gets called from RunMenuAt, and as such there is only ever one
+ // host window per menu hierarchy, no matter how many NativeMenuWin objects
+ // exist wrapping submenus.
+ if (!host_window_.get())
+ host_window_.reset(new MenuHostWindow(this));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SystemMenuModel:
+
+SystemMenuModel::SystemMenuModel(ui::SimpleMenuModel::Delegate* delegate)
+ : SimpleMenuModel(delegate) {
+}
+
+SystemMenuModel::~SystemMenuModel() {
+}
+
+int SystemMenuModel::GetFirstItemIndex(gfx::NativeMenu native_menu) const {
+ // We allow insertions before last item (Close).
+ return std::max(0, GetMenuItemCount(native_menu) - 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MenuWrapper, public:
+
+// static
+MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
+ return new NativeMenuWin(menu->model(), NULL);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/native_menu_win.h b/ui/views/controls/menu/native_menu_win.h
new file mode 100644
index 0000000..91c7db5
--- /dev/null
+++ b/ui/views/controls/menu/native_menu_win.h
@@ -0,0 +1,184 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_
+#define UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_
+#pragma once
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/views/controls/menu/menu_wrapper.h"
+#include "views/views_export.h"
+
+namespace views {
+
+// A Windows implementation of MenuWrapper.
+// TODO(beng): rename to MenuWin once the old class is dead.
+class VIEWS_EXPORT NativeMenuWin : public MenuWrapper {
+ public:
+ // Construct a NativeMenuWin, with a model and delegate. If |system_menu_for|
+ // is non-NULL, the NativeMenuWin wraps the system menu for that window.
+ // The caller owns the model and the delegate.
+ NativeMenuWin(ui::MenuModel* model, HWND system_menu_for);
+ virtual ~NativeMenuWin();
+
+ // Overridden from MenuWrapper:
+ virtual void RunMenuAt(const gfx::Point& point, int alignment) OVERRIDE;
+ virtual void CancelMenu() OVERRIDE;
+ virtual void Rebuild() OVERRIDE;
+ virtual void UpdateStates() OVERRIDE;
+ virtual gfx::NativeMenu GetNativeMenu() const OVERRIDE;
+ virtual MenuAction GetMenuAction() const OVERRIDE;
+ virtual void AddMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void RemoveMenuListener(MenuListener* listener) OVERRIDE;
+ virtual void SetMinimumWidth(int width) OVERRIDE;
+
+ private:
+ // IMPORTANT: Note about indices.
+ // Functions in this class deal in two index spaces:
+ // 1. menu_index - the index of an item within the actual Windows
+ // native menu.
+ // 2. model_index - the index of the item within our model.
+ // These two are most often but not always the same value! The
+ // notable exception is when this object is used to wrap the
+ // Windows System Menu. In this instance, the model indices start
+ // at 0, but the insertion index into the existing menu is not.
+ // It is important to take this into consideration when editing the
+ // code in the functions in this class.
+
+ struct HighlightedMenuItemInfo;
+
+ // Returns true if the item at the specified index is a separator.
+ bool IsSeparatorItemAt(int menu_index) const;
+
+ // Add items. See note above about indices.
+ void AddMenuItemAt(int menu_index, int model_index);
+ void AddSeparatorItemAt(int menu_index, int model_index);
+
+ // Sets the state of the item at the specified index.
+ void SetMenuItemState(int menu_index,
+ bool enabled,
+ bool checked,
+ bool is_default);
+
+ // Sets the label of the item at the specified index.
+ void SetMenuItemLabel(int menu_index,
+ int model_index,
+ const string16& label);
+
+ // Updates the local data structure with the correctly formatted version of
+ // |label| at the specified model_index, and adds string data to |mii| if
+ // the menu is not owner-draw. That's a mouthful. This function exists because
+ // of the peculiarities of the Windows menu API.
+ void UpdateMenuItemInfoForString(MENUITEMINFO* mii,
+ int model_index,
+ const string16& label);
+
+ // Returns the alignment flags to be passed to TrackPopupMenuEx, based on the
+ // supplied alignment and the UI text direction.
+ UINT GetAlignmentFlags(int alignment) const;
+
+ // Resets the native menu stored in |menu_| by destroying any old menu then
+ // creating a new empty one.
+ void ResetNativeMenu();
+
+ // Creates the host window that receives notifications from the menu.
+ void CreateHostWindow();
+
+ // Callback from task to notify menu it was selected.
+ void DelayedSelect();
+
+ // Given a menu that's currently popped-up, find the currently highlighted
+ // item. Returns true if a highlighted item was found.
+ static bool GetHighlightedMenuItemInfo(HMENU menu,
+ HighlightedMenuItemInfo* info);
+
+ // Hook to receive keyboard events while the menu is open.
+ static LRESULT CALLBACK MenuMessageHook(
+ int n_code, WPARAM w_param, LPARAM l_param);
+
+ // Our attached model and delegate.
+ ui::MenuModel* model_;
+
+ HMENU menu_;
+
+ // True if the contents of menu items in this menu are drawn by the menu host
+ // window, rather than Windows.
+ bool owner_draw_;
+
+ // An object that collects all of the data associated with an individual menu
+ // item.
+ struct ItemData;
+ std::vector<ItemData*> items_;
+
+ // The window that receives notifications from the menu.
+ class MenuHostWindow;
+ friend MenuHostWindow;
+ scoped_ptr<MenuHostWindow> host_window_;
+
+ // The HWND this menu is the system menu for, or NULL if the menu is not a
+ // system menu.
+ HWND system_menu_for_;
+
+ // The index of the first item in the model in the menu.
+ int first_item_index_;
+
+ // The action that took place during the call to RunMenuAt.
+ MenuAction menu_action_;
+
+ // A list of listeners to call when the menu opens.
+ ObserverList<MenuListener> listeners_;
+
+ // Keep track of whether the listeners have already been called at least
+ // once.
+ bool listeners_called_;
+
+ // See comment in MenuMessageHook for details on these.
+ NativeMenuWin* menu_to_select_;
+ int position_to_select_;
+ base::WeakPtrFactory<NativeMenuWin> menu_to_select_factory_;
+
+ // If we're a submenu, this is our parent.
+ NativeMenuWin* parent_;
+
+ // If non-null the destructor sets this to true. This is set to non-null while
+ // the menu is showing. It is used to detect if the menu was deleted while
+ // running.
+ bool* destroyed_flag_;
+
+ // Ugly: a static pointer to the instance of this class that currently
+ // has a menu open, because our hook function that receives keyboard
+ // events doesn't have a mechanism to get a user data pointer.
+ static NativeMenuWin* open_native_menu_win_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeMenuWin);
+};
+
+// A SimpleMenuModel subclass that allows the system menu for a window to be
+// wrapped.
+class VIEWS_EXPORT SystemMenuModel : public ui::SimpleMenuModel {
+ public:
+ explicit SystemMenuModel(Delegate* delegate);
+ virtual ~SystemMenuModel();
+
+ // Overridden from ui::MenuModel:
+ virtual int GetFirstItemIndex(gfx::NativeMenu native_menu) const;
+
+ protected:
+ // Overridden from SimpleMenuModel:
+ virtual int FlipIndex(int index) const { return GetItemCount() - index - 1; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemMenuModel);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_
diff --git a/ui/views/controls/menu/nested_dispatcher_gtk.cc b/ui/views/controls/menu/nested_dispatcher_gtk.cc
new file mode 100644
index 0000000..d83b9cc
--- /dev/null
+++ b/ui/views/controls/menu/nested_dispatcher_gtk.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/nested_dispatcher_gtk.h"
+
+namespace views {
+
+NestedDispatcherGtk::NestedDispatcherGtk(MessageLoopForUI::Dispatcher* creator,
+ bool allow_nested_task)
+ : creator_(creator),
+ allow_nested_task_(allow_nested_task) {
+}
+
+bool NestedDispatcherGtk::RunAndSelfDestruct() {
+ bool nestable = MessageLoopForUI::current()->NestableTasksAllowed();
+ if (allow_nested_task_)
+ MessageLoopForUI::current()->SetNestableTasksAllowed(true);
+ MessageLoopForUI::current()->RunWithDispatcher(this);
+ if (allow_nested_task_)
+ MessageLoopForUI::current()->SetNestableTasksAllowed(nestable);
+ bool creator_is_deleted = creator_ == NULL;
+ delete this;
+ return creator_is_deleted;
+}
+
+void NestedDispatcherGtk::CreatorDestroyed() {
+ creator_ = NULL;
+}
+
+bool NestedDispatcherGtk::Dispatch(GdkEvent* event) {
+ return creator_ && creator_->Dispatch(event);
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/nested_dispatcher_gtk.h b/ui/views/controls/menu/nested_dispatcher_gtk.h
new file mode 100644
index 0000000..dc02f36
--- /dev/null
+++ b/ui/views/controls/menu/nested_dispatcher_gtk.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_NESTED_DISPATCHER_GTK_H_
+#define UI_VIEWS_CONTROLS_MENU_NESTED_DISPATCHER_GTK_H_
+#pragma once
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+
+namespace views {
+
+// A nested dispatcher that can out-live the creator of this
+// dispatcher. This is to deal with the scenario where a menu object
+// is deleted while it's handling the message loop. Note that
+// |RunAndSelfDestruct| method always delete itself regardless of
+// whether or not the menu object is deleted, so a menu object should
+// create a new instance for each open request.
+// http://crosbug.com/7228, http://crosbug.com/7929
+class NestedDispatcherGtk : public MessageLoopForUI::Dispatcher {
+ public:
+ NestedDispatcherGtk(MessageLoopForUI::Dispatcher* creator,
+ bool allow_nested_task);
+
+ // Run the messsage loop and returns if the menu has been
+ // deleted in the nested loop. Returns true if the menu is
+ // deleted, or false otherwise.
+ bool RunAndSelfDestruct();
+
+ // Tells the nested dispatcher that creator has been destroyed.
+ void CreatorDestroyed();
+
+ private:
+ virtual ~NestedDispatcherGtk() {}
+
+ // Overriden from MessageLoopForUI::Dispatcher:
+ virtual bool Dispatch(GdkEvent* event) OVERRIDE;
+
+ // Creator of the nested loop.
+ MessageLoopForUI::Dispatcher* creator_;
+
+ // True to allow nested task in the message loop.
+ bool allow_nested_task_;
+
+ DISALLOW_COPY_AND_ASSIGN(NestedDispatcherGtk);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_NESTED_DISPATCHER_GTK_H_
diff --git a/ui/views/controls/menu/submenu_view.cc b/ui/views/controls/menu/submenu_view.cc
new file mode 100644
index 0000000..a462689
--- /dev/null
+++ b/ui/views/controls/menu/submenu_view.cc
@@ -0,0 +1,398 @@
+// Copyright (c) 2011 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 "ui/views/controls/menu/submenu_view.h"
+
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/menu/menu_config.h"
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/controls/menu/menu_host.h"
+#include "ui/views/controls/menu/menu_scroll_view_container.h"
+#include "ui/views/widget/root_view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// Height of the drop indicator. This should be an even number.
+const int kDropIndicatorHeight = 2;
+
+// Color of the drop indicator.
+const SkColor kDropIndicatorColor = SK_ColorBLACK;
+
+} // namespace
+
+namespace views {
+
+// static
+const int SubmenuView::kSubmenuBorderSize = 3;
+
+// static
+const char SubmenuView::kViewClassName[] = "views/SubmenuView";
+
+SubmenuView::SubmenuView(MenuItemView* parent)
+ : parent_menu_item_(parent),
+ host_(NULL),
+ drop_item_(NULL),
+ drop_position_(MenuDelegate::DROP_NONE),
+ scroll_view_container_(NULL),
+ max_accelerator_width_(0),
+ minimum_preferred_width_(0),
+ resize_open_menu_(false) {
+ DCHECK(parent);
+ // We'll delete ourselves, otherwise the ScrollView would delete us on close.
+ set_parent_owned(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 < child_count(); ++i) {
+ if (child_at(i)->id() == MenuItemView::kMenuItemViewID)
+ count++;
+ }
+ return count;
+}
+
+MenuItemView* SubmenuView::GetMenuItemAt(int index) {
+ for (int i = 0, count = 0; i < child_count(); ++i) {
+ if (child_at(i)->id() == MenuItemView::kMenuItemViewID &&
+ count++ == index) {
+ return static_cast<MenuItemView*>(child_at(i));
+ }
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+void SubmenuView::ChildPreferredSizeChanged(View* child) {
+ if (!resize_open_menu_)
+ return;
+
+ MenuItemView *item = GetMenuItem();
+ MenuController* controller = item->GetMenuController();
+
+ if (controller) {
+ bool dir;
+ gfx::Rect bounds = controller->CalculateMenuBounds(item, false, &dir);
+ Reposition(bounds);
+ }
+}
+
+void SubmenuView::Layout() {
+ // We're in a ScrollView, and need to set our width/height ourselves.
+ if (!parent())
+ return;
+
+ // Use our current y, unless it means part of the menu isn't visible anymore.
+ int pref_height = GetPreferredSize().height();
+ int new_y;
+ if (pref_height > parent()->height())
+ new_y = std::max(parent()->height() - pref_height, y());
+ else
+ new_y = 0;
+ SetBounds(x(), new_y, parent()->width(), pref_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 < child_count(); ++i) {
+ View* child = child_at(i);
+ if (child->IsVisible()) {
+ 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 (!has_children())
+ return gfx::Size();
+
+ max_accelerator_width_ = 0;
+ int max_width = 0;
+ int height = 0;
+ for (int i = 0; i < child_count(); ++i) {
+ View* child = child_at(i);
+ gfx::Size child_pref_size = child->IsVisible() ?
+ child->GetPreferredSize() : gfx::Size();
+ max_width = std::max(max_width, child_pref_size.width());
+ height += child_pref_size.height();
+ if (child->id() == MenuItemView::kMenuItemViewID) {
+ MenuItemView* menu = static_cast<MenuItemView*>(child);
+ max_accelerator_width_ =
+ std::max(max_accelerator_width_, menu->GetAcceleratorTextWidth());
+ }
+ }
+ if (max_accelerator_width_ > 0) {
+ max_accelerator_width_ +=
+ MenuConfig::instance().label_to_accelerator_padding;
+ }
+ gfx::Insets insets = GetInsets();
+ return gfx::Size(
+ std::max(max_width + max_accelerator_width_ + insets.width(),
+ minimum_preferred_width_ - 2 * kSubmenuBorderSize),
+ height + insets.height());
+}
+
+void SubmenuView::GetAccessibleState(ui::AccessibleViewState* state) {
+ // Inherit most of the state from the parent menu item, except the role.
+ if (GetMenuItem())
+ GetMenuItem()->GetAccessibleState(state);
+ state->role = ui::AccessibilityTypes::ROLE_MENUPOPUP;
+}
+
+void SubmenuView::PaintChildren(gfx::Canvas* canvas) {
+ View::PaintChildren(canvas);
+
+ if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON)
+ PaintDropIndicator(canvas, drop_item_, drop_position_);
+}
+
+bool SubmenuView::GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) {
+ DCHECK(GetMenuItem()->GetMenuController());
+ return GetMenuItem()->GetMenuController()->GetDropFormats(this, formats,
+ custom_formats);
+}
+
+bool SubmenuView::AreDropTypesRequired() {
+ DCHECK(GetMenuItem()->GetMenuController());
+ return GetMenuItem()->GetMenuController()->AreDropTypesRequired(this);
+}
+
+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 i = 0;
+ while ((i < menu_item_count) && (GetMenuItemAt(i)->y() < vis_bounds.y()))
+ ++i;
+ if (i == menu_item_count)
+ return true;
+ int first_vis_index = std::max(0,
+ (GetMenuItemAt(i)->y() == vis_bounds.y()) ? i : i - 1);
+
+ // If the first item isn't entirely visible, make it visible, otherwise make
+ // the next/previous one entirely visible.
+ int delta = abs(e.offset() / MouseWheelEvent::kWheelDelta);
+ for (bool scroll_up = (e.offset() > 0); delta != 0; --delta) {
+ int scroll_target;
+ if (scroll_up) {
+ if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) {
+ if (first_vis_index == 0)
+ break;
+ first_vis_index--;
+ }
+ scroll_target = GetMenuItemAt(first_vis_index)->y();
+ } else {
+ if (first_vis_index + 1 == menu_item_count)
+ break;
+ scroll_target = GetMenuItemAt(first_vis_index + 1)->y();
+ if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y())
+ first_vis_index++;
+ }
+ ScrollRectToVisible(gfx::Rect(gfx::Point(0, scroll_target),
+ vis_bounds.size()));
+ vis_bounds = GetVisibleBounds();
+ }
+
+ return true;
+}
+
+bool SubmenuView::IsShowing() {
+ return host_ && host_->IsMenuHostVisible();
+}
+
+void SubmenuView::ShowAt(Widget* parent,
+ const gfx::Rect& bounds,
+ bool do_capture) {
+ if (host_) {
+ host_->ShowMenuHost(do_capture);
+ } else {
+ host_ = new MenuHost(this);
+ // Force construction of the scroll view container.
+ GetScrollViewContainer();
+ // Make sure the first row is visible.
+ ScrollRectToVisible(gfx::Rect(gfx::Point(), gfx::Size(1, 1)));
+ host_->InitMenuHost(parent, bounds, scroll_view_container_, do_capture);
+ }
+
+ GetScrollViewContainer()->GetWidget()->NotifyAccessibilityEvent(
+ GetScrollViewContainer(),
+ ui::AccessibilityTypes::EVENT_MENUSTART,
+ true);
+ GetWidget()->NotifyAccessibilityEvent(
+ this,
+ ui::AccessibilityTypes::EVENT_MENUPOPUPSTART,
+ true);
+}
+
+void SubmenuView::Reposition(const gfx::Rect& bounds) {
+ if (host_)
+ host_->SetMenuHostBounds(bounds);
+}
+
+void SubmenuView::Close() {
+ if (host_) {
+ GetWidget()->NotifyAccessibilityEvent(
+ this,
+ ui::AccessibilityTypes::EVENT_MENUPOPUPEND,
+ true);
+ GetScrollViewContainer()->GetWidget()->NotifyAccessibilityEvent(
+ GetScrollViewContainer(),
+ ui::AccessibilityTypes::EVENT_MENUEND,
+ true);
+
+ host_->DestroyMenuHost();
+ host_ = NULL;
+ }
+}
+
+void SubmenuView::Hide() {
+ if (host_)
+ host_->HideMenuHost();
+}
+
+void SubmenuView::ReleaseCapture() {
+ if (host_)
+ host_->ReleaseMenuHostCapture();
+}
+
+bool SubmenuView::SkipDefaultKeyEventProcessing(const views::KeyEvent& e) {
+ return views::FocusManager::IsTabTraversalKeyEvent(e);
+}
+
+MenuItemView* SubmenuView::GetMenuItem() const {
+ return parent_menu_item_;
+}
+
+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_->set_parent_owned(false);
+ }
+ return scroll_view_container_;
+}
+
+void SubmenuView::MenuHostDestroyed() {
+ host_ = NULL;
+ GetMenuItem()->GetMenuController()->Cancel(MenuController::EXIT_DESTROYED);
+}
+
+std::string SubmenuView::GetClassName() const {
+ return kViewClassName;
+}
+
+void SubmenuView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ SchedulePaint();
+}
+
+void SubmenuView::PaintDropIndicator(gfx::Canvas* canvas,
+ MenuItemView* item,
+ MenuDelegate::DropPosition position) {
+ if (position == MenuDelegate::DROP_NONE)
+ return;
+
+ gfx::Rect bounds = CalculateDropIndicatorBounds(item, position);
+ canvas->FillRect(kDropIndicatorColor, bounds);
+}
+
+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) {
+ SchedulePaintInRect(CalculateDropIndicatorBounds(item, position));
+ }
+}
+
+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();
+ }
+}
+
+} // namespace views
diff --git a/ui/views/controls/menu/submenu_view.h b/ui/views/controls/menu/submenu_view.h
new file mode 100644
index 0000000..693a538
--- /dev/null
+++ b/ui/views/controls/menu/submenu_view.h
@@ -0,0 +1,204 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_
+#define UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_
+#pragma once
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+#include "views/view.h"
+
+namespace views {
+
+class MenuHost;
+class MenuItemView;
+class MenuScrollViewContainer;
+
+// 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 NativeWidgetWin) 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 VIEWS_EXPORT SubmenuView : public View {
+ public:
+ // The submenu's class name.
+ static const char kViewClassName[];
+
+ // Creates a SubmenuView for the specified menu item.
+ explicit SubmenuView(MenuItemView* parent);
+ virtual ~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() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Override from View.
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // Painting.
+ virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE;
+
+ // Drag and drop methods. These are forwarded to the MenuController.
+ virtual bool GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
+ virtual bool AreDropTypesRequired() OVERRIDE;
+ virtual bool CanDrop(const OSExchangeData& data) OVERRIDE;
+ virtual void OnDragEntered(const DropTargetEvent& event) OVERRIDE;
+ virtual int OnDragUpdated(const DropTargetEvent& event) OVERRIDE;
+ virtual void OnDragExited() OVERRIDE;
+ virtual int OnPerformDrop(const DropTargetEvent& event) OVERRIDE;
+
+ // Scrolls on menu item boundaries.
+ virtual bool OnMouseWheel(const MouseWheelEvent& e) OVERRIDE;
+
+ // 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(Widget* parent,
+ const gfx::Rect& bounds,
+ bool do_capture);
+
+ // Resets the bounds of the submenu to |bounds|.
+ void Reposition(const gfx::Rect& bounds);
+
+ // 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();
+
+ // Overriden from View to prevent tab from doing anything.
+ virtual bool SkipDefaultKeyEventProcessing(const KeyEvent& e) OVERRIDE;
+
+ // Returns the parent menu item we're showing children for.
+ MenuItemView* GetMenuItem() const;
+
+ // 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();
+
+ // Invoked if the menu is prematurely destroyed. This can happen if the window
+ // closes while the menu is shown. If invoked the SubmenuView must drop all
+ // references to the MenuHost as the MenuHost is about to be deleted.
+ void MenuHostDestroyed();
+
+ // Max width of accelerators in child menu items. This doesn't include
+ // children's children, only direct children.
+ int max_accelerator_width() const { return max_accelerator_width_; }
+
+ // Minimum width of menu in pixels (default 0). This becomes the smallest
+ // width returned by GetPreferredSize().
+ void set_minimum_preferred_width(int minimum_preferred_width) {
+ minimum_preferred_width_ = minimum_preferred_width;
+ }
+
+ // Automatically resize menu if a subview's preferred size changes.
+ bool resize_open_menu() const { return resize_open_menu_; }
+ void set_resize_open_menu(bool resize_open_menu) {
+ resize_open_menu_ = resize_open_menu;
+ }
+
+ // Padding around the edges of the submenu.
+ static const int kSubmenuBorderSize;
+
+ protected:
+ // View override.
+ virtual std::string GetClassName() const OVERRIDE;
+
+ // View method. Overridden to schedule a paint. We do this so that when
+ // scrolling occurs, everything is repainted correctly.
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+
+ virtual void ChildPreferredSizeChanged(View* child) OVERRIDE;
+
+ private:
+ // Paints the drop indicator. This is only invoked if item is non-NULL and
+ // position is not DROP_NONE.
+ void PaintDropIndicator(gfx::Canvas* 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_;
+
+ // Widget subclass used to show the children. This is deleted when we invoke
+ // |DestroyMenuHost|, or |MenuHostDestroyed| is invoked back on us.
+ 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_;
+
+ // See description above getter.
+ int max_accelerator_width_;
+
+ // Minimum width returned in GetPreferredSize().
+ int minimum_preferred_width_;
+
+ // Reposition open menu when contained views change size.
+ bool resize_open_menu_;
+
+ DISALLOW_COPY_AND_ASSIGN(SubmenuView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_SUBMENU_VIEW_H_
diff --git a/ui/views/controls/menu/view_menu_delegate.h b/ui/views/controls/menu/view_menu_delegate.h
new file mode 100644
index 0000000..ef243e0
--- /dev/null
+++ b/ui/views/controls/menu/view_menu_delegate.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 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 UI_VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+#define UI_VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
+#pragma once
+
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Point;
+}
+
+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 gfx::Point& pt) = 0;
+
+ protected:
+ virtual ~ViewMenuDelegate() {}
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_CONTROLS_MENU_VIEW_MENU_DELEGATE_H_
diff --git a/ui/views/controls/scrollbar/base_scroll_bar.cc b/ui/views/controls/scrollbar/base_scroll_bar.cc
index 01194c1..3afb238 100644
--- a/ui/views/controls/scrollbar/base_scroll_bar.cc
+++ b/ui/views/controls/scrollbar/base_scroll_bar.cc
@@ -16,10 +16,10 @@
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/widget/widget.h"
-#include "views/controls/menu/menu_item_view.h"
-#include "views/controls/menu/menu_runner.h"
#include "views/controls/scroll_view.h"
#if defined(OS_LINUX)
diff --git a/ui/views/controls/scrollbar/base_scroll_bar.h b/ui/views/controls/scrollbar/base_scroll_bar.h
index 3624c1d..8dee9b9 100644
--- a/ui/views/controls/scrollbar/base_scroll_bar.h
+++ b/ui/views/controls/scrollbar/base_scroll_bar.h
@@ -7,9 +7,9 @@
#pragma once
#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
#include "views/context_menu_controller.h"
-#include "views/controls/menu/menu_delegate.h"
#include "views/repeat_controller.h"
namespace views {
diff --git a/ui/views/controls/scrollbar/bitmap_scroll_bar.cc b/ui/views/controls/scrollbar/bitmap_scroll_bar.cc
index a3c76e7..6aa44d5 100644
--- a/ui/views/controls/scrollbar/bitmap_scroll_bar.cc
+++ b/ui/views/controls/scrollbar/bitmap_scroll_bar.cc
@@ -17,9 +17,9 @@
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
+#include "ui/views/controls/menu/menu.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/widget/widget.h"
-#include "views/controls/menu/menu.h"
#include "views/controls/scroll_view.h"
#if defined(OS_LINUX)
diff --git a/ui/views/controls/textfield/native_textfield_views.cc b/ui/views/controls/textfield/native_textfield_views.cc
index 8cba8de..3e6a5f8 100644
--- a/ui/views/controls/textfield/native_textfield_views.cc
+++ b/ui/views/controls/textfield/native_textfield_views.cc
@@ -18,6 +18,9 @@
#include "ui/gfx/canvas.h"
#include "ui/gfx/insets.h"
#include "ui/gfx/render_text.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/controls/textfield/textfield_views_model.h"
@@ -27,9 +30,6 @@
#include "views/background.h"
#include "views/border.h"
#include "views/controls/focusable_border.h"
-#include "views/controls/menu/menu_item_view.h"
-#include "views/controls/menu/menu_model_adapter.h"
-#include "views/controls/menu/menu_runner.h"
#include "views/metrics.h"
#include "views/views_delegate.h"
diff --git a/ui/views/controls/textfield/native_textfield_win.cc b/ui/views/controls/textfield/native_textfield_win.cc
index 0741fd6..bbbdbd4 100644
--- a/ui/views/controls/textfield/native_textfield_win.cc
+++ b/ui/views/controls/textfield/native_textfield_win.cc
@@ -23,6 +23,8 @@
#include "ui/base/range/range.h"
#include "ui/base/win/mouse_wheel_util.h"
#include "ui/gfx/native_theme_win.h"
+#include "ui/views/controls/menu/menu_2.h"
+#include "ui/views/controls/menu/menu_win.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/controls/textfield/native_textfield_views.h"
#include "ui/views/controls/textfield/textfield.h"
@@ -30,8 +32,6 @@
#include "ui/views/focus/focus_manager.h"
#include "ui/views/widget/widget.h"
#include "views/controls/label.h"
-#include "views/controls/menu/menu_2.h"
-#include "views/controls/menu/menu_win.h"
#include "views/metrics.h"
#include "views/views_delegate.h"
diff --git a/ui/views/examples/menu_example.cc b/ui/views/examples/menu_example.cc
index fd20aee..f158ded 100644
--- a/ui/views/examples/menu_example.cc
+++ b/ui/views/examples/menu_example.cc
@@ -10,9 +10,9 @@
#include "ui/base/models/simple_menu_model.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/menu/menu_2.h"
+#include "ui/views/controls/menu/view_menu_delegate.h"
#include "ui/views/layout/fill_layout.h"
-#include "views/controls/menu/menu_2.h"
-#include "views/controls/menu/view_menu_delegate.h"
#include "views/view.h"
namespace {
diff --git a/ui/views/touchui/touch_selection_controller_impl.cc b/ui/views/touchui/touch_selection_controller_impl.cc
index b1ac3a0..1d64a0a 100644
--- a/ui/views/touchui/touch_selection_controller_impl.cc
+++ b/ui/views/touchui/touch_selection_controller_impl.cc
@@ -20,11 +20,11 @@
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/button/text_button.h"
+#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
#include "views/background.h"
#include "views/controls/label.h"
-#include "views/controls/menu/menu_config.h"
namespace {
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 4503720..8d8fad7 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -13,6 +13,7 @@
#include "ui/gfx/compositor/compositor.h"
#include "ui/gfx/compositor/layer.h"
#include "ui/gfx/screen.h"
+#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/focus/focus_manager_factory.h"
#include "ui/views/focus/view_storage.h"
@@ -24,7 +25,6 @@
#include "ui/views/widget/tooltip_manager.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/custom_frame_view.h"
-#include "views/controls/menu/menu_controller.h"
#include "views/views_delegate.h"
namespace {