172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be 3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file. 4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/menu_bar_helper.h" 6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <algorithm> 8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/logging.h" 1072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/gtk_util.h" 1172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/gtk/gtk_signal_registrar.h" 12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace { 14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Recursively find all the GtkMenus that are attached to menu item |child| 16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// and add them to |data|, which is a vector of GtkWidgets. 17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid PopulateSubmenus(GtkWidget* child, gpointer data) { 18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::vector<GtkWidget*>* submenus = 19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch static_cast<std::vector<GtkWidget*>*>(data); 20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(child)); 21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (submenu) { 22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch submenus->push_back(submenu); 23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gtk_container_foreach(GTK_CONTAINER(submenu), PopulateSubmenus, submenus); 24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Is the cursor over |menu| or one of its parent menus? 28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochbool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) { 29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (motion->x >= 0 && motion->y >= 0 && 30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch motion->x < menu->allocation.width && 31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch motion->y < menu->allocation.height) { 32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return true; 33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch while (menu) { 36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu)); 37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!menu_item) 38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* parent = gtk_widget_get_parent(menu_item); 40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (gtk_util::WidgetContainsCursor(parent)) 42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return true; 43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch menu = parent; 44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return false; 47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} // namespace 50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 51c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochMenuBarHelper::MenuBarHelper(Delegate* delegate) 52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch : button_showing_menu_(NULL), 53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch showing_menu_(NULL), 54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch delegate_(delegate) { 55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK(delegate_); 56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 58c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochMenuBarHelper::~MenuBarHelper() { 59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Add(GtkWidget* button) { 62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch buttons_.push_back(button); 63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Remove(GtkWidget* button) { 66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch std::vector<GtkWidget*>::iterator iter = 67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch find(buttons_.begin(), buttons_.end(), button); 68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (iter == buttons_.end()) { 69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch NOTREACHED(); 70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch buttons_.erase(iter); 73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::Clear() { 76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch buttons_.clear(); 77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) { 80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK(GTK_IS_MENU(menu)); 81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch button_showing_menu_ = button; 82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch showing_menu_ = menu; 83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 8472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen signal_handlers_.reset(new ui::GtkSignalRegistrar()); 85201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch signal_handlers_->Connect(menu, "destroy", 86201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this); 87201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch signal_handlers_->Connect(menu, "hide", 88201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this); 89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch signal_handlers_->Connect(menu, "motion-notify-event", 90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch G_CALLBACK(OnMenuMotionNotifyThunk), this); 91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch signal_handlers_->Connect(menu, "move-current", 92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch G_CALLBACK(OnMenuMoveCurrentThunk), this); 93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gtk_container_foreach(GTK_CONTAINER(menu), PopulateSubmenus, &submenus_); 94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (size_t i = 0; i < submenus_.size(); ++i) { 96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch signal_handlers_->Connect(submenus_[i], "motion-notify-event", 97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch G_CALLBACK(OnMenuMotionNotifyThunk), this); 98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgboolean MenuBarHelper::OnMenuMotionNotify(GtkWidget* menu, 102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GdkEventMotion* motion) { 103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Don't do anything if pointer is in the menu. 104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (MotionIsOverMenu(menu, motion)) 105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return FALSE; 106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (buttons_.empty()) 107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return FALSE; 108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gint x = 0; 110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gint y = 0; 111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* last_button = NULL; 112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch for (size_t i = 0; i < buttons_.size(); ++i) { 114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* button = buttons_[i]; 115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Figure out coordinates relative to this button. Avoid using 116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // gtk_widget_get_pointer() unnecessarily. 117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (i == 0) { 118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // We have to make this call because the menu is a popup window, so it 119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // doesn't share a toplevel with the buttons and we can't just use 120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // gtk_widget_translate_coordinates(). 121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gtk_widget_get_pointer(buttons_[0], &x, &y); 122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } else { 123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gint last_x = x; 124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch gint last_y = y; 125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (!gtk_widget_translate_coordinates( 126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_button, button, last_x, last_y, &x, &y)) { 1273345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick // |button| may not be realized. 1283345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick continue; 129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch last_button = button; 133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (x >= 0 && y >= 0 && x < button->allocation.width && 135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch y < button->allocation.height) { 136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (button != button_showing_menu_) 137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch delegate_->PopupForButton(button); 138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return TRUE; 139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return FALSE; 143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 145201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdochvoid MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget* menu) { 146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch DCHECK_EQ(showing_menu_, menu); 147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch signal_handlers_.reset(); 149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch showing_menu_ = NULL; 150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch button_showing_menu_ = NULL; 151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch submenus_.clear(); 152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu, 155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkMenuDirectionType dir) { 156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // The menu directions are triggered by the arrow keys as follows 157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // 158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // PARENT left 159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // CHILD right 160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // NEXT down 161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // PREV up 162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // 163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // We only care about left and right. Note that for RTL, they are swapped. 164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch switch (dir) { 165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch case GTK_MENU_DIR_CHILD: { 166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch GtkWidget* active_item = GTK_MENU_SHELL(menu)->active_menu_item; 167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // The move is going to open a submenu; don't override default behavior. 168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch if (active_item && gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item))) 169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // Fall through. 171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch case GTK_MENU_DIR_PARENT: { 173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch delegate_->PopupForButtonNextTo(button_showing_menu_, dir); 174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch break; 175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch default: 177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch return; 178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch } 179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch 180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // This signal doesn't have a return value; we have to manually stop its 181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch // propagation. 182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch g_signal_stop_emission_by_name(menu, "move-current"); 183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch} 184