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