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.
5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h"
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"
33 using content::OpenURLParams
;
34 using content::PageNavigator
;
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
);
47 const BookmarkNode
* GetNodeFromMenuItem(GtkWidget
* menu_item
) {
48 return static_cast<const BookmarkNode
*>(
49 g_object_get_data(G_OBJECT(menu_item
), "bookmark-node"));
52 const BookmarkNode
* GetParentNodeFromEmptyMenu(GtkWidget
* menu
) {
53 return static_cast<const BookmarkNode
*>(
54 g_object_get_data(G_OBJECT(menu
), "parent-node"));
57 void* AsVoid(const BookmarkNode
* node
) {
58 return const_cast<BookmarkNode
*>(node
);
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
);
66 // Match the ref we took when connecting this signal.
67 g_object_unref(grab_menu
);
72 BookmarkMenuController::BookmarkMenuController(Browser
* browser
,
73 PageNavigator
* navigator
,
75 const BookmarkNode
* node
,
76 int start_child_index
)
78 page_navigator_(navigator
),
79 parent_window_(window
),
80 model_(BookmarkModelFactory::GetForProfile(browser
->profile())),
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_
);
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_
);
100 void BookmarkMenuController::Popup(GtkWidget
* widget
, gint button_type
,
102 model_
->AddObserver(this);
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
),
109 gtk_menu_popup(GTK_MENU(menu_
), NULL
, NULL
,
110 &MenuGtk::WidgetMenuPositionFunc
,
111 widget
, button_type
, timestamp
);
114 void BookmarkMenuController::BookmarkModelChanged() {
115 gtk_menu_popdown(GTK_MENU(menu_
));
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
);
126 void BookmarkMenuController::WillExecuteCommand(
128 const std::vector
<const BookmarkNode
*>& bookmarks
) {
129 gtk_menu_popdown(GTK_MENU(menu_
));
132 void BookmarkMenuController::CloseMenu() {
133 context_menu_
->Cancel();
136 void BookmarkMenuController::NavigateToMenuItem(
137 GtkWidget
* menu_item
,
138 WindowOpenDisposition disposition
) {
139 const BookmarkNode
* node
= GetNodeFromMenuItem(menu_item
);
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));
148 void BookmarkMenuController::BuildMenu(const BookmarkNode
* parent
,
149 int start_child_index
,
151 DCHECK(parent
->empty() || start_child_index
< parent
->child_count());
153 signals_
.Connect(menu
, "button-press-event",
154 G_CALLBACK(OnMenuButtonPressedOrReleasedThunk
), this);
155 signals_
.Connect(menu
, "button-release-event",
156 G_CALLBACK(OnMenuButtonPressedOrReleasedThunk
), this);
158 for (int i
= start_child_index
; i
< parent
->child_count(); ++i
) {
159 const BookmarkNode
* node
= parent
->GetChild(i
);
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
);
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
);
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
;
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);
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);
201 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
202 node_to_menu_widget_map_
[node
] = menu_item
;
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
);
214 gboolean
BookmarkMenuController::OnMenuButtonPressedOrReleased(
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
))) {
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
,
231 // We are the top level menu; we can propagate no further.
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
244 GtkWidget
* menu_item
= menu_shell
->active_menu_item
;
245 if (!is_empty_menu
&& !menu_item
)
247 const BookmarkNode
* node
=
248 menu_item
? GetNodeFromMenuItem(menu_item
) : NULL
;
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_
));
255 } else if (event
->button
== 3) {
256 DCHECK_NE(is_empty_menu
, !!node
);
258 parent
= node
->parent();
260 // Show the right click menu and stop processing this button event.
261 std::vector
<const BookmarkNode
*> nodes
;
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
));
269 new MenuGtk(NULL
, context_menu_controller_
->menu_model()));
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
);
278 context_menu_
->PopupAsContext(gfx::Point(event
->x_root
, event
->y_root
),
286 gboolean
BookmarkMenuController::OnButtonReleased(
288 GdkEventButton
* event
) {
289 if (ignore_button_release_
) {
290 // Don't handle this message; it was a drag.
291 ignore_button_release_
= false;
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
);
302 NavigateToMenuItem(sender
, disposition
);
304 // We need to manually dismiss the popup menu because we're overriding
305 // button-release-event.
306 gtk_menu_popdown(GTK_MENU(menu_
));
310 // The menu item is a folder node.
311 if (event
->button
== 1) {
312 // Having overriden the normal handling, we need to manually activate
314 gtk_menu_shell_select_item(GTK_MENU_SHELL(sender
->parent
), sender
);
315 g_signal_emit_by_name(sender
->parent
, "activate-current");
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)
331 void BookmarkMenuController::OnMenuHidden(GtkWidget
* menu
) {
332 if (triggering_widget_
)
333 gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(triggering_widget_
));
336 void BookmarkMenuController::OnMenuItemActivated(GtkWidget
* menu_item
) {
337 NavigateToMenuItem(menu_item
, CURRENT_TAB
);
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
);
346 // Signal to any future OnButtonReleased calls that we're dragging instead of
348 ignore_button_release_
= true;
350 const BookmarkNode
* node
= BookmarkNodeForWidget(menu_item
);
351 drag_icon_
= GetDragRepresentationForNode(
352 node
, model_
, GtkThemeService::GetFrom(browser_
->profile()));
354 gtk_widget_get_pointer(menu_item
, &x
, &y
);
355 gtk_drag_set_icon_widget(drag_context
, drag_icon_
, x
, y
);
358 gtk_widget_hide(menu_item
);
361 void BookmarkMenuController::OnMenuItemDragEnd(GtkWidget
* menu_item
,
362 GdkDragContext
* drag_context
) {
363 gtk_widget_show(menu_item
);
364 g_object_unref(menu_item
->parent
);
366 gtk_widget_destroy(drag_icon_
);
370 void BookmarkMenuController::OnMenuItemDragGet(GtkWidget
* widget
,
371 GdkDragContext
* context
,
372 GtkSelectionData
* selection_data
,
375 const BookmarkNode
* node
= BookmarkNodeForWidget(widget
);
376 WriteBookmarkToSelection(
377 node
, selection_data
, target_type
, browser_
->profile());