Index: chrome/browser/gtk/menu_bar_helper.cc |
=================================================================== |
--- chrome/browser/gtk/menu_bar_helper.cc (revision 0) |
+++ chrome/browser/gtk/menu_bar_helper.cc (revision 0) |
@@ -0,0 +1,181 @@ |
+// 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. |
+ |
+#include "chrome/browser/gtk/menu_bar_helper.h" |
+ |
+#include "base/logging.h" |
+#include "chrome/common/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; |
+ g_signal_connect(menu, "motion-notify-event", |
+ G_CALLBACK(OnMenuMotionNotifyThunk), this); |
+ g_signal_connect(menu, "hide", |
+ G_CALLBACK(OnMenuHiddenThunk), this); |
+ g_signal_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) { |
+ g_signal_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)) { |
+ NOTREACHED(); |
+ return FALSE; |
+ } |
+ } |
+ |
+ 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::OnMenuHidden(GtkWidget* menu) { |
+ DCHECK_EQ(showing_menu_, menu); |
+ int matched = g_signal_handlers_disconnect_matched(showing_menu_, |
+ G_SIGNAL_MATCH_DATA, 0, NULL, NULL, NULL, this); |
+ DCHECK_EQ(3, matched); |
+ |
+ for (size_t i = 0; i < submenus_.size(); ++i) { |
+ g_signal_handlers_disconnect_by_func(submenus_[i], |
+ reinterpret_cast<gpointer>(OnMenuMotionNotifyThunk), this); |
+ } |
+ 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"); |
+} |