diff options
Diffstat (limited to 'chrome/browser/ui/gtk/menu_bar_helper.cc')
-rw-r--r-- | chrome/browser/ui/gtk/menu_bar_helper.cc | 183 |
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"); +} |