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