summaryrefslogtreecommitdiffstats
path: root/views/controls/menu
diff options
context:
space:
mode:
Diffstat (limited to 'views/controls/menu')
-rw-r--r--views/controls/menu/menu_2.cc12
-rw-r--r--views/controls/menu/menu_2.h18
-rw-r--r--views/controls/menu/menu_wrapper.h32
-rw-r--r--views/controls/menu/native_menu_gtk.cc58
-rw-r--r--views/controls/menu/native_menu_gtk.h20
-rw-r--r--views/controls/menu/native_menu_win.cc94
-rw-r--r--views/controls/menu/native_menu_win.h38
7 files changed, 255 insertions, 17 deletions
diff --git a/views/controls/menu/menu_2.cc b/views/controls/menu/menu_2.cc
index 2228b72..1c2bc29 100644
--- a/views/controls/menu/menu_2.cc
+++ b/views/controls/menu/menu_2.cc
@@ -42,4 +42,16 @@ 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);
+}
+
} // namespace
diff --git a/views/controls/menu/menu_2.h b/views/controls/menu/menu_2.h
index 77ff3d0..4b3843a6 100644
--- a/views/controls/menu/menu_2.h
+++ b/views/controls/menu/menu_2.h
@@ -1,6 +1,6 @@
-// Copyright (c) 2010 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.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
#ifndef VIEWS_CONTROLS_MENU_MENU_2_H_
#define VIEWS_CONTROLS_MENU_MENU_2_H_
@@ -61,6 +61,17 @@ class Menu2 {
// 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.
menus::MenuModel* model() const { return model_; }
@@ -78,4 +89,3 @@ class Menu2 {
} // namespace views
#endif // VIEWS_CONTROLS_MENU_MENU_2_H_
-
diff --git a/views/controls/menu/menu_wrapper.h b/views/controls/menu/menu_wrapper.h
index b985387..e569ac4 100644
--- a/views/controls/menu/menu_wrapper.h
+++ b/views/controls/menu/menu_wrapper.h
@@ -1,6 +1,6 @@
-// Copyright (c) 2009 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.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
#ifndef VIEWS_CONTROLS_MENU_MENU_WRAPPER_H_
#define VIEWS_CONTROLS_MENU_MENU_WRAPPER_H_
@@ -15,9 +15,24 @@ namespace views {
class Menu2;
+// An interface for clients that want a notification when a menu is opened.
+class MenuListener {
+ public:
+ // This will be called after the menu has actually opened.
+ virtual void OnMenuOpened() = 0;
+};
+
// An interface that wraps an object that implements a menu.
class 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.
@@ -37,6 +52,17 @@ class MenuWrapper {
// 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;
+
// Creates the appropriate instance of this wrapper for the current platform.
static MenuWrapper* CreateWrapper(Menu2* menu);
};
diff --git a/views/controls/menu/native_menu_gtk.cc b/views/controls/menu/native_menu_gtk.cc
index a910a76..012d2a8 100644
--- a/views/controls/menu/native_menu_gtk.cc
+++ b/views/controls/menu/native_menu_gtk.cc
@@ -76,7 +76,8 @@ NativeMenuGtk::NativeMenuGtk(Menu2* menu)
activated_menu_(NULL),
activated_index_(-1),
activate_factory_(this),
- host_menu_(menu) {
+ host_menu_(menu),
+ menu_action_(MENU_ACTION_NONE) {
}
NativeMenuGtk::~NativeMenuGtk() {
@@ -93,6 +94,7 @@ NativeMenuGtk::~NativeMenuGtk() {
void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) {
activated_menu_ = NULL;
activated_index_ = -1;
+ menu_action_ = MENU_ACTION_NONE;
UpdateStates();
Position position = { point, static_cast<Menu2::Alignment>(alignment) };
@@ -102,15 +104,25 @@ void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) {
DCHECK(!menu_shown_);
menu_shown_ = true;
+
+ for (unsigned int i = 0; i < listeners_.size(); ++i) {
+ listeners_[i]->OnMenuOpened();
+ }
+
// Listen for "hide" signal so that we know when to return from the blocking
// RunMenuAt call.
- gint handle_id =
+ gint hide_handle_id =
g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHidden), this);
+ gint move_handle_id =
+ g_signal_connect(menu_, "move-current", G_CALLBACK(OnMenuMoveCurrent),
+ this);
+
// Block until menu is no longer shown by running a nested message loop.
MessageLoopForUI::current()->Run(NULL);
- g_signal_handler_disconnect(G_OBJECT(menu_), handle_id);
+ g_signal_handler_disconnect(G_OBJECT(menu_), hide_handle_id);
+ g_signal_handler_disconnect(G_OBJECT(menu_), move_handle_id);
menu_shown_ = false;
if (activated_menu_) {
@@ -174,6 +186,25 @@ gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const {
return menu_;
}
+NativeMenuGtk::MenuAction NativeMenuGtk::GetMenuAction() const {
+ return menu_action_;
+}
+
+void NativeMenuGtk::AddMenuListener(MenuListener* listener) {
+ listeners_.push_back(listener);
+}
+
+void NativeMenuGtk::RemoveMenuListener(MenuListener* listener) {
+ for (std::vector<MenuListener*>::iterator iter = listeners_.begin();
+ iter != listeners_.end();
+ ++iter) {
+ if (*iter == listener) {
+ listeners_.erase(iter);
+ return;
+ }
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// NativeMenuGtk, private:
@@ -188,6 +219,26 @@ void NativeMenuGtk::OnMenuHidden(GtkWidget* widget, NativeMenuGtk* menu) {
MessageLoop::current()->Quit();
}
+// static
+void NativeMenuGtk::OnMenuMoveCurrent(GtkMenu* menu_widget,
+ GtkMenuDirectionType focus_direction,
+ NativeMenuGtk* menu) {
+ 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) {
+ menu->GetAncestor()->menu_action_ = MENU_ACTION_NEXT;
+ gtk_menu_popdown(menu_widget);
+ } else if (focus_direction == GTK_MENU_DIR_PARENT && parent == NULL) {
+ menu->GetAncestor()->menu_action_ = MENU_ACTION_PREVIOUS;
+ gtk_menu_popdown(menu_widget);
+ }
+}
+
void NativeMenuGtk::AddSeparatorAt(int index) {
GtkWidget* separator = gtk_separator_menu_item_new();
gtk_widget_show(separator);
@@ -375,6 +426,7 @@ void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) {
NativeMenuGtk* ancestor = GetAncestor();
ancestor->activated_menu_ = this;
activated_index_ = position;
+ ancestor->menu_action_ = MENU_ACTION_SELECTED;
}
}
diff --git a/views/controls/menu/native_menu_gtk.h b/views/controls/menu/native_menu_gtk.h
index 2c69ba3..812fbad 100644
--- a/views/controls/menu/native_menu_gtk.h
+++ b/views/controls/menu/native_menu_gtk.h
@@ -1,12 +1,14 @@
-// Copyright (c) 2009 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.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
#ifndef VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_
#define VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_
#include <gtk/gtk.h>
+#include <vector>
+
#include "base/task.h"
#include "views/controls/menu/menu_wrapper.h"
@@ -36,9 +38,15 @@ class NativeMenuGtk : public MenuWrapper {
virtual void Rebuild();
virtual void UpdateStates();
virtual gfx::NativeMenu GetNativeMenu() const;
+ virtual MenuAction GetMenuAction() const;
+ virtual void AddMenuListener(MenuListener* listener);
+ virtual void RemoveMenuListener(MenuListener* listener);
private:
static void OnMenuHidden(GtkWidget* widget, NativeMenuGtk* menu);
+ static void OnMenuMoveCurrent(GtkMenu* widget,
+ GtkMenuDirectionType focus_direction,
+ NativeMenuGtk* menu);
void AddSeparatorAt(int index);
GtkWidget* AddMenuItemAt(int index, GtkRadioMenuItem* radio_group,
@@ -111,6 +119,12 @@ class NativeMenuGtk : public MenuWrapper {
Menu2* host_menu_;
gulong destroy_handler_id_;
+ // The action that took place during the call to RunMenuAt.
+ MenuAction menu_action_;
+
+ // Vector of listeners to receive callbacks when the menu opens.
+ std::vector<MenuListener*> listeners_;
+
DISALLOW_COPY_AND_ASSIGN(NativeMenuGtk);
};
diff --git a/views/controls/menu/native_menu_win.cc b/views/controls/menu/native_menu_win.cc
index 3470e27..0881ab5 100644
--- a/views/controls/menu/native_menu_win.cc
+++ b/views/controls/menu/native_menu_win.cc
@@ -306,7 +306,8 @@ NativeMenuWin::NativeMenuWin(menus::MenuModel* model, HWND system_menu_for)
owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL) &&
!system_menu_for),
system_menu_for_(system_menu_for),
- first_item_index_(0) {
+ first_item_index_(0),
+ menu_action_(MENU_ACTION_NONE) {
}
NativeMenuWin::~NativeMenuWin() {
@@ -322,10 +323,28 @@ void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) {
UpdateStates();
UINT flags = TPM_LEFTBUTTON | 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();
TrackPopupMenuEx(menu_, flags, point.x(), point.y(), host_window_->hwnd(),
NULL);
+
+ UnhookWindowsHookEx(hhook);
+ open_native_menu_win_ = NULL;
}
void NativeMenuWin::CancelMenu() {
@@ -370,9 +389,82 @@ gfx::NativeMenu NativeMenuWin::GetNativeMenu() const {
return menu_;
}
+NativeMenuWin::MenuAction NativeMenuWin::GetMenuAction() const {
+ return menu_action_;
+}
+
+void NativeMenuWin::AddMenuListener(MenuListener* listener) {
+ listeners_.push_back(listener);
+}
+
+void NativeMenuWin::RemoveMenuListener(MenuListener* listener) {
+ for (std::vector<MenuListener*>::iterator iter = listeners_.begin();
+ iter != listeners_.end();
+ ++iter) {
+ if (*iter == listener) {
+ listeners_.erase(iter);
+ return;
+ }
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWin, private:
+// static
+NativeMenuWin* NativeMenuWin::open_native_menu_win_ = NULL;
+
+// static
+bool NativeMenuWin::GetHighlightedMenuItemInfo(
+ HMENU menu, bool* has_parent, bool* has_submenu) {
+ 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, has_parent, has_submenu))
+ *has_parent = true;
+ else
+ *has_submenu = true;
+ }
+ 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_;
+ // 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 (unsigned int i = 0; i < this_ptr->listeners_.size(); ++i) {
+ this_ptr->listeners_[i]->OnMenuOpened();
+ }
+ this_ptr->listeners_called_ = true;
+ }
+
+ MSG* msg = reinterpret_cast<MSG*>(l_param);
+ if (msg->message == WM_KEYDOWN) {
+ bool has_parent = false;
+ bool has_submenu = false;
+ GetHighlightedMenuItemInfo(this_ptr->menu_, &has_parent, &has_submenu);
+ if (msg->wParam == VK_LEFT && !has_parent) {
+ this_ptr->menu_action_ = MENU_ACTION_PREVIOUS;
+ ::EndMenu();
+ } else if (msg->wParam == VK_RIGHT && !has_parent && !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);
diff --git a/views/controls/menu/native_menu_win.h b/views/controls/menu/native_menu_win.h
index ba0144e..1138a46 100644
--- a/views/controls/menu/native_menu_win.h
+++ b/views/controls/menu/native_menu_win.h
@@ -1,6 +1,6 @@
-// Copyright (c) 2009 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.
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
#ifndef VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_
#define VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_
@@ -29,6 +29,9 @@ class NativeMenuWin : public MenuWrapper {
virtual void Rebuild();
virtual void UpdateStates();
virtual gfx::NativeMenu GetNativeMenu() const;
+ virtual MenuAction GetMenuAction() const;
+ virtual void AddMenuListener(MenuListener* listener);
+ virtual void RemoveMenuListener(MenuListener* listener);
private:
// IMPORTANT: Note about indices.
@@ -80,6 +83,20 @@ class NativeMenuWin : public MenuWrapper {
// Creates the host window that receives notifications from the menu.
void CreateHostWindow();
+ // Given a menu that's currently popped-up, find the currently
+ // highlighted item and return whether or not that item has a parent
+ // (i.e. it's in a submenu), and whether or not that item leads to a
+ // submenu. Returns true if a highlighted item was found. This
+ // method is called to determine if the right and left arrow keys
+ // should be used to switch between menus, or to open and close
+ // submenus.
+ static bool GetHighlightedMenuItemInfo(
+ HMENU menu, bool* has_parent, bool* has_submenu);
+
+ // 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.
menus::MenuModel* model_;
@@ -106,6 +123,21 @@ class NativeMenuWin : public MenuWrapper {
// 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_;
+
+ // Vector of listeners to receive callbacks when the menu opens.
+ std::vector<MenuListener*> listeners_;
+
+ // Keep track of whether the listeners have already been called at least
+ // once.
+ bool listeners_called_;
+
+ // 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);
};