summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/gtk/menu_bar_helper.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/gtk/menu_bar_helper.cc')
-rw-r--r--chrome/browser/ui/gtk/menu_bar_helper.cc183
1 files changed, 183 insertions, 0 deletions
diff --git a/chrome/browser/ui/gtk/menu_bar_helper.cc b/chrome/browser/ui/gtk/menu_bar_helper.cc
new file mode 100644
index 0000000..b2cba9d
--- /dev/null
+++ b/chrome/browser/ui/gtk/menu_bar_helper.cc
@@ -0,0 +1,183 @@
+// 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 "chrome/browser/gtk/menu_bar_helper.h"
+
+#include <algorithm>
+
+#include "app/gtk_signal_registrar.h"
+#include "base/logging.h"
+#include "chrome/browser/gtk/gtk_util.h"
+
+namespace {
+
+// Recursively find all the GtkMenus that are attached to menu item |child|
+// and add them to |data|, which is a vector of GtkWidgets.
+void PopulateSubmenus(GtkWidget* child, gpointer data) {
+ std::vector<GtkWidget*>* submenus =
+ static_cast<std::vector<GtkWidget*>*>(data);
+ GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(child));
+ if (submenu) {
+ submenus->push_back(submenu);
+ gtk_container_foreach(GTK_CONTAINER(submenu), PopulateSubmenus, submenus);
+ }
+}
+
+// Is the cursor over |menu| or one of its parent menus?
+bool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) {
+ if (motion->x >= 0 && motion->y >= 0 &&
+ motion->x < menu->allocation.width &&
+ motion->y < menu->allocation.height) {
+ return true;
+ }
+
+ while (menu) {
+ GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu));
+ if (!menu_item)
+ return false;
+ GtkWidget* parent = gtk_widget_get_parent(menu_item);
+
+ if (gtk_util::WidgetContainsCursor(parent))
+ return true;
+ menu = parent;
+ }
+
+ return false;
+}
+
+} // namespace
+
+MenuBarHelper::MenuBarHelper(Delegate* delegate)
+ : button_showing_menu_(NULL),
+ showing_menu_(NULL),
+ delegate_(delegate) {
+ DCHECK(delegate_);
+}
+
+MenuBarHelper::~MenuBarHelper() {
+}
+
+void MenuBarHelper::Add(GtkWidget* button) {
+ buttons_.push_back(button);
+}
+
+void MenuBarHelper::Remove(GtkWidget* button) {
+ std::vector<GtkWidget*>::iterator iter =
+ find(buttons_.begin(), buttons_.end(), button);
+ if (iter == buttons_.end()) {
+ NOTREACHED();
+ return;
+ }
+ buttons_.erase(iter);
+}
+
+void MenuBarHelper::Clear() {
+ buttons_.clear();
+}
+
+void MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) {
+ DCHECK(GTK_IS_MENU(menu));
+ button_showing_menu_ = button;
+ showing_menu_ = menu;
+
+ signal_handlers_.reset(new GtkSignalRegistrar());
+ signal_handlers_->Connect(menu, "destroy",
+ G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
+ signal_handlers_->Connect(menu, "hide",
+ G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
+ signal_handlers_->Connect(menu, "motion-notify-event",
+ G_CALLBACK(OnMenuMotionNotifyThunk), this);
+ signal_handlers_->Connect(menu, "move-current",
+ G_CALLBACK(OnMenuMoveCurrentThunk), this);
+ gtk_container_foreach(GTK_CONTAINER(menu), PopulateSubmenus, &submenus_);
+
+ for (size_t i = 0; i < submenus_.size(); ++i) {
+ signal_handlers_->Connect(submenus_[i], "motion-notify-event",
+ G_CALLBACK(OnMenuMotionNotifyThunk), this);
+ }
+}
+
+gboolean MenuBarHelper::OnMenuMotionNotify(GtkWidget* menu,
+ GdkEventMotion* motion) {
+ // Don't do anything if pointer is in the menu.
+ if (MotionIsOverMenu(menu, motion))
+ return FALSE;
+ if (buttons_.empty())
+ return FALSE;
+
+ gint x = 0;
+ gint y = 0;
+ GtkWidget* last_button = NULL;
+
+ for (size_t i = 0; i < buttons_.size(); ++i) {
+ GtkWidget* button = buttons_[i];
+ // Figure out coordinates relative to this button. Avoid using
+ // gtk_widget_get_pointer() unnecessarily.
+ if (i == 0) {
+ // We have to make this call because the menu is a popup window, so it
+ // doesn't share a toplevel with the buttons and we can't just use
+ // gtk_widget_translate_coordinates().
+ gtk_widget_get_pointer(buttons_[0], &x, &y);
+ } else {
+ gint last_x = x;
+ gint last_y = y;
+ if (!gtk_widget_translate_coordinates(
+ last_button, button, last_x, last_y, &x, &y)) {
+ // |button| may not be realized.
+ continue;
+ }
+ }
+
+ last_button = button;
+
+ if (x >= 0 && y >= 0 && x < button->allocation.width &&
+ y < button->allocation.height) {
+ if (button != button_showing_menu_)
+ delegate_->PopupForButton(button);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget* menu) {
+ DCHECK_EQ(showing_menu_, menu);
+
+ signal_handlers_.reset();
+ showing_menu_ = NULL;
+ button_showing_menu_ = NULL;
+ submenus_.clear();
+}
+
+void MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu,
+ GtkMenuDirectionType dir) {
+ // The menu directions are triggered by the arrow keys as follows
+ //
+ // PARENT left
+ // CHILD right
+ // NEXT down
+ // PREV up
+ //
+ // We only care about left and right. Note that for RTL, they are swapped.
+ switch (dir) {
+ case GTK_MENU_DIR_CHILD: {
+ GtkWidget* active_item = GTK_MENU_SHELL(menu)->active_menu_item;
+ // The move is going to open a submenu; don't override default behavior.
+ if (active_item && gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item)))
+ return;
+ // Fall through.
+ }
+ case GTK_MENU_DIR_PARENT: {
+ delegate_->PopupForButtonNextTo(button_showing_menu_, dir);
+ break;
+ }
+ default:
+ return;
+ }
+
+ // This signal doesn't have a return value; we have to manually stop its
+ // propagation.
+ g_signal_stop_emission_by_name(menu, "move-current");
+}