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