Disable TabDragController tests that fail with a real compositor.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / menu_bar_helper.cc
blob3240577b3eddddee8ce49cf717784f7a4f7c0c3a
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"
7 #include <algorithm>
9 #include "base/logging.h"
10 #include "chrome/browser/ui/gtk/gtk_util.h"
11 #include "ui/base/gtk/gtk_signal_registrar.h"
13 namespace {
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));
21 if (submenu) {
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) {
35 return true;
38 while (menu) {
39 GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu));
40 if (!menu_item)
41 return false;
42 GtkWidget* parent = gtk_widget_get_parent(menu_item);
44 if (gtk_util::WidgetContainsCursor(parent))
45 return true;
46 menu = parent;
49 return false;
52 } // namespace
54 MenuBarHelper::MenuBarHelper(Delegate* delegate)
55 : button_showing_menu_(NULL),
56 showing_menu_(NULL),
57 delegate_(delegate) {
58 DCHECK(delegate_);
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()) {
72 NOTREACHED();
73 return;
75 buttons_.erase(iter);
78 void MenuBarHelper::Clear() {
79 buttons_.clear();
82 void MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) {
83 DCHECK(GTK_IS_MENU(menu));
84 button_showing_menu_ = button;
85 showing_menu_ = menu;
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))
108 return FALSE;
109 if (buttons_.empty())
110 return FALSE;
112 gint x = 0;
113 gint y = 0;
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.
120 if (i == 0) {
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);
125 } else {
126 gint last_x = x;
127 gint last_y = y;
128 if (!gtk_widget_translate_coordinates(
129 last_button, button, last_x, last_y, &x, &y)) {
130 // |button| may not be realized.
131 continue;
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);
143 return TRUE;
147 return FALSE;
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;
156 submenus_.clear();
159 void MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu,
160 GtkMenuDirectionType dir) {
161 // The menu directions are triggered by the arrow keys as follows
163 // PARENT left
164 // CHILD right
165 // NEXT down
166 // PREV up
168 // We only care about left and right. Note that for RTL, they are swapped.
169 switch (dir) {
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)))
174 return;
175 // Fall through.
177 case GTK_MENU_DIR_PARENT: {
178 delegate_->PopupForButtonNextTo(button_showing_menu_, dir);
179 break;
181 default:
182 return;
185 // This signal doesn't have a return value; we have to manually stop its
186 // propagation.
187 g_signal_stop_emission_by_name(menu, "move-current");