Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / bookmarks / bookmark_menu_controller_gtk.cc
1 // Copyright (c) 2012 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/bookmarks/bookmark_menu_controller_gtk.h"
6
7 #include <gtk/gtk.h>
8
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/bookmarks/bookmark_model.h"
12 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
13 #include "chrome/browser/bookmarks/bookmark_stats.h"
14 #include "chrome/browser/bookmarks/bookmark_utils.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
19 #include "chrome/browser/ui/gtk/event_utils.h"
20 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
21 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
22 #include "chrome/browser/ui/gtk/gtk_util.h"
23 #include "chrome/browser/ui/gtk/menu_gtk.h"
24 #include "content/public/browser/page_navigator.h"
25 #include "grit/generated_resources.h"
26 #include "grit/theme_resources.h"
27 #include "grit/ui_resources.h"
28 #include "ui/base/dragdrop/gtk_dnd_util.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/window_open_disposition.h"
31 #include "ui/gfx/gtk_util.h"
32
33 using content::OpenURLParams;
34 using content::PageNavigator;
35
36 namespace {
37
38 void SetImageMenuItem(GtkWidget* menu_item,
39                       const BookmarkNode* node,
40                       BookmarkModel* model) {
41   GdkPixbuf* pixbuf = GetPixbufForNode(node, model, true);
42   gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
43                                 gtk_image_new_from_pixbuf(pixbuf));
44   g_object_unref(pixbuf);
45 }
46
47 const BookmarkNode* GetNodeFromMenuItem(GtkWidget* menu_item) {
48   return static_cast<const BookmarkNode*>(
49       g_object_get_data(G_OBJECT(menu_item), "bookmark-node"));
50 }
51
52 const BookmarkNode* GetParentNodeFromEmptyMenu(GtkWidget* menu) {
53   return static_cast<const BookmarkNode*>(
54       g_object_get_data(G_OBJECT(menu), "parent-node"));
55 }
56
57 void* AsVoid(const BookmarkNode* node) {
58   return const_cast<BookmarkNode*>(node);
59 }
60
61 // The context menu has been dismissed, restore the X and application grabs
62 // to whichever menu last had them. (Assuming that menu is still showing.)
63 void OnContextMenuHide(GtkWidget* context_menu, GtkWidget* grab_menu) {
64   gtk_util::GrabAllInput(grab_menu);
65
66   // Match the ref we took when connecting this signal.
67   g_object_unref(grab_menu);
68 }
69
70 }  // namespace
71
72 BookmarkMenuController::BookmarkMenuController(Browser* browser,
73                                                PageNavigator* navigator,
74                                                GtkWindow* window,
75                                                const BookmarkNode* node,
76                                                int start_child_index)
77     : browser_(browser),
78       page_navigator_(navigator),
79       parent_window_(window),
80       model_(BookmarkModelFactory::GetForProfile(browser->profile())),
81       node_(node),
82       drag_icon_(NULL),
83       ignore_button_release_(false),
84       triggering_widget_(NULL) {
85   menu_ = gtk_menu_new();
86   g_object_ref_sink(menu_);
87   BuildMenu(node, start_child_index, menu_);
88   signals_.Connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
89   gtk_widget_show_all(menu_);
90 }
91
92 BookmarkMenuController::~BookmarkMenuController() {
93   model_->RemoveObserver(this);
94   // Make sure the hide handler runs.
95   gtk_widget_hide(menu_);
96   gtk_widget_destroy(menu_);
97   g_object_unref(menu_);
98 }
99
100 void BookmarkMenuController::Popup(GtkWidget* widget, gint button_type,
101                                    guint32 timestamp) {
102   model_->AddObserver(this);
103
104   triggering_widget_ = widget;
105   signals_.Connect(triggering_widget_, "destroy",
106                    G_CALLBACK(gtk_widget_destroyed), &triggering_widget_);
107   gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget),
108                                     GTK_STATE_ACTIVE);
109   gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
110                  &MenuGtk::WidgetMenuPositionFunc,
111                  widget, button_type, timestamp);
112 }
113
114 void BookmarkMenuController::BookmarkModelChanged() {
115   gtk_menu_popdown(GTK_MENU(menu_));
116 }
117
118 void BookmarkMenuController::BookmarkNodeFaviconChanged(
119     BookmarkModel* model, const BookmarkNode* node) {
120   std::map<const BookmarkNode*, GtkWidget*>::iterator it =
121       node_to_menu_widget_map_.find(node);
122   if (it != node_to_menu_widget_map_.end())
123     SetImageMenuItem(it->second, node, model);
124 }
125
126 void BookmarkMenuController::WillExecuteCommand(
127       int command_id,
128       const std::vector<const BookmarkNode*>& bookmarks) {
129   gtk_menu_popdown(GTK_MENU(menu_));
130 }
131
132 void BookmarkMenuController::CloseMenu() {
133   context_menu_->Cancel();
134 }
135
136 void BookmarkMenuController::NavigateToMenuItem(
137     GtkWidget* menu_item,
138     WindowOpenDisposition disposition) {
139   const BookmarkNode* node = GetNodeFromMenuItem(menu_item);
140   DCHECK(node);
141   DCHECK(page_navigator_);
142   RecordBookmarkLaunch(node, BOOKMARK_LAUNCH_LOCATION_BAR_SUBFOLDER);
143   page_navigator_->OpenURL(OpenURLParams(
144       node->url(), content::Referrer(), disposition,
145       content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
146 }
147
148 void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
149                                        int start_child_index,
150                                        GtkWidget* menu) {
151   DCHECK(parent->empty() || start_child_index < parent->child_count());
152
153   signals_.Connect(menu, "button-press-event",
154                    G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
155   signals_.Connect(menu, "button-release-event",
156                    G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
157
158   for (int i = start_child_index; i < parent->child_count(); ++i) {
159     const BookmarkNode* node = parent->GetChild(i);
160
161     GtkWidget* menu_item =
162         gtk_image_menu_item_new_with_label(BuildMenuLabelFor(node).c_str());
163     g_object_set_data(G_OBJECT(menu_item), "bookmark-node", AsVoid(node));
164     SetImageMenuItem(menu_item, node, model_);
165     gtk_util::SetAlwaysShowImage(menu_item);
166
167     signals_.Connect(menu_item, "button-release-event",
168                      G_CALLBACK(OnButtonReleasedThunk), this);
169     if (node->is_url()) {
170       signals_.Connect(menu_item, "activate",
171                        G_CALLBACK(OnMenuItemActivatedThunk), this);
172     } else if (node->is_folder()) {
173       GtkWidget* submenu = gtk_menu_new();
174       BuildMenu(node, 0, submenu);
175       gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
176     } else {
177       NOTREACHED();
178     }
179
180     gtk_drag_source_set(menu_item, GDK_BUTTON1_MASK, NULL, 0,
181         static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_LINK));
182     int target_mask = ui::CHROME_BOOKMARK_ITEM;
183     if (node->is_url())
184       target_mask |= ui::TEXT_URI_LIST | ui::NETSCAPE_URL;
185     ui::SetSourceTargetListFromCodeMask(menu_item, target_mask);
186     signals_.Connect(menu_item, "drag-begin",
187                      G_CALLBACK(OnMenuItemDragBeginThunk), this);
188     signals_.Connect(menu_item, "drag-end",
189                      G_CALLBACK(OnMenuItemDragEndThunk), this);
190     signals_.Connect(menu_item, "drag-data-get",
191                      G_CALLBACK(OnMenuItemDragGetThunk), this);
192
193     // It is important to connect to this signal after setting up the drag
194     // source because we only want to stifle the menu's default handler and
195     // not the handler that the drag source uses.
196     if (node->is_folder()) {
197       signals_.Connect(menu_item, "button-press-event",
198                        G_CALLBACK(OnFolderButtonPressedThunk), this);
199     }
200
201     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
202     node_to_menu_widget_map_[node] = menu_item;
203   }
204
205   if (parent->empty()) {
206     GtkWidget* empty_menu = gtk_menu_item_new_with_label(
207         l10n_util::GetStringUTF8(IDS_MENU_EMPTY_SUBMENU).c_str());
208     gtk_widget_set_sensitive(empty_menu, FALSE);
209     g_object_set_data(G_OBJECT(menu), "parent-node", AsVoid(parent));
210     gtk_menu_shell_append(GTK_MENU_SHELL(menu), empty_menu);
211   }
212 }
213
214 gboolean BookmarkMenuController::OnMenuButtonPressedOrReleased(
215     GtkWidget* sender,
216     GdkEventButton* event) {
217   // Handle middle mouse downs and right mouse ups.
218   if (!((event->button == 2 && event->type == GDK_BUTTON_RELEASE) ||
219       (event->button == 3 && event->type == GDK_BUTTON_PRESS))) {
220     return FALSE;
221   }
222
223   ignore_button_release_ = false;
224   GtkMenuShell* menu_shell = GTK_MENU_SHELL(sender);
225   // If the cursor is outside our bounds, pass this event up to the parent.
226   if (!gtk_util::WidgetContainsCursor(sender)) {
227     if (menu_shell->parent_menu_shell) {
228       return OnMenuButtonPressedOrReleased(menu_shell->parent_menu_shell,
229                                            event);
230     } else {
231       // We are the top level menu; we can propagate no further.
232       return FALSE;
233     }
234   }
235
236   // This will return NULL if we are not an empty menu.
237   const BookmarkNode* parent = GetParentNodeFromEmptyMenu(sender);
238   bool is_empty_menu = !!parent;
239   // If there is no active menu item and we are not an empty menu, then do
240   // nothing. This can happen if the user has canceled a context menu while
241   // the cursor is hovering over a bookmark menu. Doing nothing is not optimal
242   // (the hovered item should be active), but it's a hopefully rare corner
243   // case.
244   GtkWidget* menu_item = menu_shell->active_menu_item;
245   if (!is_empty_menu && !menu_item)
246     return TRUE;
247   const BookmarkNode* node =
248       menu_item ? GetNodeFromMenuItem(menu_item) : NULL;
249
250   if (event->button == 2 && node && node->is_folder()) {
251     chrome::OpenAll(parent_window_, page_navigator_, node, NEW_BACKGROUND_TAB,
252                     browser_->profile());
253     gtk_menu_popdown(GTK_MENU(menu_));
254     return TRUE;
255   } else if (event->button == 3) {
256     DCHECK_NE(is_empty_menu, !!node);
257     if (!is_empty_menu)
258       parent = node->parent();
259
260     // Show the right click menu and stop processing this button event.
261     std::vector<const BookmarkNode*> nodes;
262     if (node)
263       nodes.push_back(node);
264     context_menu_controller_.reset(
265         new BookmarkContextMenuController(
266             parent_window_, this, browser_, browser_->profile(),
267             page_navigator_, parent, nodes));
268     context_menu_.reset(
269         new MenuGtk(NULL, context_menu_controller_->menu_model()));
270
271     // Our bookmark folder menu loses the grab to the context menu. When the
272     // context menu is hidden, re-assert our grab.
273     GtkWidget* grabbing_menu = gtk_grab_get_current();
274     g_object_ref(grabbing_menu);
275     signals_.Connect(context_menu_->widget(), "hide",
276                      G_CALLBACK(OnContextMenuHide), grabbing_menu);
277
278     context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root),
279                                   event->time);
280     return TRUE;
281   }
282
283   return FALSE;
284 }
285
286 gboolean BookmarkMenuController::OnButtonReleased(
287     GtkWidget* sender,
288     GdkEventButton* event) {
289   if (ignore_button_release_) {
290     // Don't handle this message; it was a drag.
291     ignore_button_release_ = false;
292     return FALSE;
293   }
294
295   // Releasing either button 1 or 2 should trigger the bookmark.
296   if (!gtk_menu_item_get_submenu(GTK_MENU_ITEM(sender))) {
297     // The menu item is a link node.
298     if (event->button == 1 || event->button == 2) {
299       WindowOpenDisposition disposition =
300         event_utils::DispositionFromGdkState(event->state);
301
302       NavigateToMenuItem(sender, disposition);
303
304       // We need to manually dismiss the popup menu because we're overriding
305       // button-release-event.
306       gtk_menu_popdown(GTK_MENU(menu_));
307       return TRUE;
308     }
309   } else {
310     // The menu item is a folder node.
311     if (event->button == 1) {
312       // Having overriden the normal handling, we need to manually activate
313       // the item.
314       gtk_menu_shell_select_item(GTK_MENU_SHELL(sender->parent), sender);
315       g_signal_emit_by_name(sender->parent, "activate-current");
316       return TRUE;
317     }
318   }
319
320   return FALSE;
321 }
322
323 gboolean BookmarkMenuController::OnFolderButtonPressed(
324     GtkWidget* sender, GdkEventButton* event) {
325   // The button press may start a drag; don't let the default handler run.
326   if (event->button == 1)
327     return TRUE;
328   return FALSE;
329 }
330
331 void BookmarkMenuController::OnMenuHidden(GtkWidget* menu) {
332   if (triggering_widget_)
333     gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(triggering_widget_));
334 }
335
336 void BookmarkMenuController::OnMenuItemActivated(GtkWidget* menu_item) {
337   NavigateToMenuItem(menu_item, CURRENT_TAB);
338 }
339
340 void BookmarkMenuController::OnMenuItemDragBegin(GtkWidget* menu_item,
341                                                  GdkDragContext* drag_context) {
342   // The parent menu item might be removed during the drag. Ref it so |button|
343   // won't get destroyed.
344   g_object_ref(menu_item->parent);
345
346   // Signal to any future OnButtonReleased calls that we're dragging instead of
347   // pressing.
348   ignore_button_release_ = true;
349
350   const BookmarkNode* node = BookmarkNodeForWidget(menu_item);
351   drag_icon_ = GetDragRepresentationForNode(
352       node, model_, GtkThemeService::GetFrom(browser_->profile()));
353   gint x, y;
354   gtk_widget_get_pointer(menu_item, &x, &y);
355   gtk_drag_set_icon_widget(drag_context, drag_icon_, x, y);
356
357   // Hide our node.
358   gtk_widget_hide(menu_item);
359 }
360
361 void BookmarkMenuController::OnMenuItemDragEnd(GtkWidget* menu_item,
362                                                GdkDragContext* drag_context) {
363   gtk_widget_show(menu_item);
364   g_object_unref(menu_item->parent);
365
366   gtk_widget_destroy(drag_icon_);
367   drag_icon_ = NULL;
368 }
369
370 void BookmarkMenuController::OnMenuItemDragGet(GtkWidget* widget,
371                                                GdkDragContext* context,
372                                                GtkSelectionData* selection_data,
373                                                guint target_type,
374                                                guint time) {
375   const BookmarkNode* node = BookmarkNodeForWidget(widget);
376   WriteBookmarkToSelection(
377       node, selection_data, target_type, browser_->profile());
378 }