Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / menu_bar_helper.cc
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.
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?
28 bool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) {
29   GtkAllocation allocation;
30   gtk_widget_get_allocation(menu, &allocation);
31
32   if (motion->x >= 0 && motion->y >= 0 &&
33       motion->x < allocation.width &&
34       motion->y < allocation.height) {
35     return true;
36   }
37
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);
43
44     if (gtk_util::WidgetContainsCursor(parent))
45       return true;
46     menu = parent;
47   }
48
49   return false;
50 }
51
52 }  // namespace
53
54 MenuBarHelper::MenuBarHelper(Delegate* delegate)
55     : button_showing_menu_(NULL),
56       showing_menu_(NULL),
57       delegate_(delegate) {
58   DCHECK(delegate_);
59 }
60
61 MenuBarHelper::~MenuBarHelper() {
62 }
63
64 void MenuBarHelper::Add(GtkWidget* button) {
65   buttons_.push_back(button);
66 }
67
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;
74   }
75   buttons_.erase(iter);
76 }
77
78 void MenuBarHelper::Clear() {
79   buttons_.clear();
80 }
81
82 void MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) {
83   DCHECK(GTK_IS_MENU(menu));
84   button_showing_menu_ = button;
85   showing_menu_ = menu;
86
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_);
97
98   for (size_t i = 0; i < submenus_.size(); ++i) {
99     signal_handlers_->Connect(submenus_[i], "motion-notify-event",
100                               G_CALLBACK(OnMenuMotionNotifyThunk), this);
101   }
102 }
103
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;
111
112   gint x = 0;
113   gint y = 0;
114   GtkWidget* last_button = NULL;
115
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;
132       }
133     }
134
135     last_button = button;
136
137     GtkAllocation allocation;
138     gtk_widget_get_allocation(button, &allocation);
139
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;
144     }
145   }
146
147   return FALSE;
148 }
149
150 void MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget* menu) {
151   DCHECK_EQ(showing_menu_, menu);
152
153   signal_handlers_.reset();
154   showing_menu_ = NULL;
155   button_showing_menu_ = NULL;
156   submenus_.clear();
157 }
158
159 void MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu,
160                                       GtkMenuDirectionType dir) {
161   // The menu directions are triggered by the arrow keys as follows
162   //
163   //   PARENT   left
164   //   CHILD    right
165   //   NEXT     down
166   //   PREV     up
167   //
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.
176     }
177     case GTK_MENU_DIR_PARENT: {
178       delegate_->PopupForButtonNextTo(button_showing_menu_, dir);
179       break;
180     }
181     default:
182       return;
183   }
184
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");
188 }