| 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");
|
| +}
|
|
|