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.
5 #include "chrome/browser/ui/gtk/menu_bar_helper.h"
9 #include "base/logging.h"
10 #include "chrome/browser/ui/gtk/gtk_util.h"
11 #include "ui/base/gtk/gtk_signal_registrar.h"
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.
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
));
22 submenus
->push_back(submenu
);
23 gtk_container_foreach(GTK_CONTAINER(submenu
), PopulateSubmenus
, submenus
);
27 // Is the cursor over |menu| or one of its parent menus?
28 bool MotionIsOverMenu(GtkWidget
* menu
, GdkEventMotion
* motion
) {
29 GtkAllocation allocation
;
30 gtk_widget_get_allocation(menu
, &allocation
);
32 if (motion
->x
>= 0 && motion
->y
>= 0 &&
33 motion
->x
< allocation
.width
&&
34 motion
->y
< allocation
.height
) {
39 GtkWidget
* menu_item
= gtk_menu_get_attach_widget(GTK_MENU(menu
));
42 GtkWidget
* parent
= gtk_widget_get_parent(menu_item
);
44 if (gtk_util::WidgetContainsCursor(parent
))
54 MenuBarHelper::MenuBarHelper(Delegate
* delegate
)
55 : button_showing_menu_(NULL
),
61 MenuBarHelper::~MenuBarHelper() {
64 void MenuBarHelper::Add(GtkWidget
* button
) {
65 buttons_
.push_back(button
);
68 void MenuBarHelper::Remove(GtkWidget
* button
) {
69 std::vector
<GtkWidget
*>::iterator iter
=
70 find(buttons_
.begin(), buttons_
.end(), button
);
71 if (iter
== buttons_
.end()) {
78 void MenuBarHelper::Clear() {
82 void MenuBarHelper::MenuStartedShowing(GtkWidget
* button
, GtkWidget
* menu
) {
83 DCHECK(GTK_IS_MENU(menu
));
84 button_showing_menu_
= button
;
87 signal_handlers_
.reset(new ui::GtkSignalRegistrar());
88 signal_handlers_
->Connect(menu
, "destroy",
89 G_CALLBACK(OnMenuHiddenOrDestroyedThunk
), this);
90 signal_handlers_
->Connect(menu
, "hide",
91 G_CALLBACK(OnMenuHiddenOrDestroyedThunk
), this);
92 signal_handlers_
->Connect(menu
, "motion-notify-event",
93 G_CALLBACK(OnMenuMotionNotifyThunk
), this);
94 signal_handlers_
->Connect(menu
, "move-current",
95 G_CALLBACK(OnMenuMoveCurrentThunk
), this);
96 gtk_container_foreach(GTK_CONTAINER(menu
), PopulateSubmenus
, &submenus_
);
98 for (size_t i
= 0; i
< submenus_
.size(); ++i
) {
99 signal_handlers_
->Connect(submenus_
[i
], "motion-notify-event",
100 G_CALLBACK(OnMenuMotionNotifyThunk
), this);
104 gboolean
MenuBarHelper::OnMenuMotionNotify(GtkWidget
* menu
,
105 GdkEventMotion
* motion
) {
106 // Don't do anything if pointer is in the menu.
107 if (MotionIsOverMenu(menu
, motion
))
109 if (buttons_
.empty())
114 GtkWidget
* last_button
= NULL
;
116 for (size_t i
= 0; i
< buttons_
.size(); ++i
) {
117 GtkWidget
* button
= buttons_
[i
];
118 // Figure out coordinates relative to this button. Avoid using
119 // gtk_widget_get_pointer() unnecessarily.
121 // We have to make this call because the menu is a popup window, so it
122 // doesn't share a toplevel with the buttons and we can't just use
123 // gtk_widget_translate_coordinates().
124 gtk_widget_get_pointer(buttons_
[0], &x
, &y
);
128 if (!gtk_widget_translate_coordinates(
129 last_button
, button
, last_x
, last_y
, &x
, &y
)) {
130 // |button| may not be realized.
135 last_button
= button
;
137 GtkAllocation allocation
;
138 gtk_widget_get_allocation(button
, &allocation
);
140 if (x
>= 0 && y
>= 0 && x
< allocation
.width
&& y
< allocation
.height
) {
141 if (button
!= button_showing_menu_
)
142 delegate_
->PopupForButton(button
);
150 void MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget
* menu
) {
151 DCHECK_EQ(showing_menu_
, menu
);
153 signal_handlers_
.reset();
154 showing_menu_
= NULL
;
155 button_showing_menu_
= NULL
;
159 void MenuBarHelper::OnMenuMoveCurrent(GtkWidget
* menu
,
160 GtkMenuDirectionType dir
) {
161 // The menu directions are triggered by the arrow keys as follows
168 // We only care about left and right. Note that for RTL, they are swapped.
170 case GTK_MENU_DIR_CHILD
: {
171 GtkWidget
* active_item
= GTK_MENU_SHELL(menu
)->active_menu_item
;
172 // The move is going to open a submenu; don't override default behavior.
173 if (active_item
&& gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item
)))
177 case GTK_MENU_DIR_PARENT
: {
178 delegate_
->PopupForButtonNextTo(button_showing_menu_
, dir
);
185 // This signal doesn't have a return value; we have to manually stop its
187 g_signal_stop_emission_by_name(menu
, "move-current");