172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file.
4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
5dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h"
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include <gtk/gtk.h>
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "base/string_util.h"
103345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick#include "base/utf_string_conversions.h"
11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/bookmarks/bookmark_model.h"
12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "chrome/browser/bookmarks/bookmark_utils.h"
1321d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen#include "chrome/browser/profiles/profile.h"
14dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
1572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/gtk_chrome_button.h"
16ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "chrome/browser/ui/gtk/gtk_theme_service.h"
1772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/gtk_util.h"
1872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "chrome/browser/ui/gtk/menu_gtk.h"
19dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "content/browser/tab_contents/page_navigator.h"
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "grit/app_resources.h"
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "grit/generated_resources.h"
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "grit/theme_resources.h"
2372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/dragdrop/gtk_dnd_util.h"
2472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/base/l10n/l10n_util.h"
2572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen#include "ui/gfx/gtk_util.h"
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch#include "webkit/glue/window_open_disposition.h"
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochnamespace {
29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// TODO(estade): It might be a good idea to vary this by locale.
31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst int kMaxChars = 50;
32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid SetImageMenuItem(GtkWidget* menu_item,
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                      const BookmarkNode* node,
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                      BookmarkModel* model) {
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GdkPixbuf* pixbuf = bookmark_utils::GetPixbufForNode(node, model, true);
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                gtk_image_new_from_pixbuf(pixbuf));
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_object_unref(pixbuf);
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst BookmarkNode* GetNodeFromMenuItem(GtkWidget* menu_item) {
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return static_cast<const BookmarkNode*>(
44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      g_object_get_data(G_OBJECT(menu_item), "bookmark-node"));
45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochconst BookmarkNode* GetParentNodeFromEmptyMenu(GtkWidget* menu) {
48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return static_cast<const BookmarkNode*>(
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      g_object_get_data(G_OBJECT(menu), "parent-node"));
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid* AsVoid(const BookmarkNode* node) {
53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return const_cast<BookmarkNode*>(node);
54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// The context menu has been dismissed, restore the X and application grabs
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// to whichever menu last had them. (Assuming that menu is still showing.)
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid OnContextMenuHide(GtkWidget* context_menu, GtkWidget* grab_menu) {
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_util::GrabAllInput(grab_menu);
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Match the ref we took when connecting this signal.
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_object_unref(grab_menu);
63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}  // namespace
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
67c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochBookmarkMenuController::BookmarkMenuController(Browser* browser,
68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               Profile* profile,
69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               PageNavigator* navigator,
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               GtkWindow* window,
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               const BookmarkNode* node,
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               int start_child_index)
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    : browser_(browser),
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      profile_(profile),
75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      page_navigator_(navigator),
76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      parent_window_(window),
77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      model_(profile->GetBookmarkModel()),
78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      node_(node),
79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      drag_icon_(NULL),
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      ignore_button_release_(false),
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      triggering_widget_(NULL) {
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  menu_ = gtk_menu_new();
834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  g_object_ref_sink(menu_);
84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  BuildMenu(node, start_child_index, menu_);
854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  signals_.Connect(menu_, "hide",
86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                   G_CALLBACK(OnMenuHiddenThunk), this);
87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_widget_show_all(menu_);
88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen MurdochBookmarkMenuController::~BookmarkMenuController() {
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  profile_->GetBookmarkModel()->RemoveObserver(this);
92201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  // Make sure the hide handler runs.
93201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch  gtk_widget_hide(menu_);
944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  gtk_widget_destroy(menu_);
954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  g_object_unref(menu_);
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::Popup(GtkWidget* widget, gint button_type,
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                   guint32 timestamp) {
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  profile_->GetBookmarkModel()->AddObserver(this);
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  triggering_widget_ = widget;
1034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  signals_.Connect(triggering_widget_, "destroy",
1044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch                   G_CALLBACK(gtk_widget_destroyed), &triggering_widget_);
105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget),
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                    GTK_STATE_ACTIVE);
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                 &MenuGtk::WidgetMenuPositionFunc,
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                 widget, button_type, timestamp);
110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::BookmarkModelChanged() {
113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_menu_popdown(GTK_MENU(menu_));
114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenvoid BookmarkMenuController::BookmarkNodeFaviconLoaded(
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    BookmarkModel* model, const BookmarkNode* node) {
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  std::map<const BookmarkNode*, GtkWidget*>::iterator it =
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      node_to_menu_widget_map_.find(node);
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (it != node_to_menu_widget_map_.end())
121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    SetImageMenuItem(it->second, node, model);
122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::WillExecuteCommand() {
125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_menu_popdown(GTK_MENU(menu_));
126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::CloseMenu() {
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  context_menu_->Cancel();
130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::NavigateToMenuItem(
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* menu_item,
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    WindowOpenDisposition disposition) {
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const BookmarkNode* node = GetNodeFromMenuItem(menu_item);
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(node);
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  DCHECK(page_navigator_);
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  page_navigator_->OpenURL(
139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      node->GetURL(), GURL(), disposition, PageTransition::AUTO_BOOKMARK);
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                       int start_child_index,
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                       GtkWidget* menu) {
145ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  DCHECK(!parent->child_count() ||
146ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen         start_child_index < parent->child_count());
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
1484a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  signals_.Connect(menu, "button-press-event",
149513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch                   G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
1504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  signals_.Connect(menu, "button-release-event",
151513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch                   G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
153ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  for (int i = start_child_index; i < parent->child_count(); ++i) {
154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    const BookmarkNode* node = parent->GetChild(i);
155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // This breaks on word boundaries. Ideally we would break on character
157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // boundaries.
15821d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen    string16 elided_name = l10n_util::TruncateString(node->GetTitle(),
15921d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen                                                     kMaxChars);
160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* menu_item =
16121d179b334e59e9a3bfcaed4c4430bef1bc5759dKristian Monsen        gtk_image_menu_item_new_with_label(UTF16ToUTF8(elided_name).c_str());
162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    g_object_set_data(G_OBJECT(menu_item), "bookmark-node", AsVoid(node));
163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    SetImageMenuItem(menu_item, node, profile_->GetBookmarkModel());
164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_util::SetAlwaysShowImage(menu_item);
165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
1664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    signals_.Connect(menu_item, "button-release-event",
167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                     G_CALLBACK(OnButtonReleasedThunk), this);
168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (node->is_url()) {
1694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      signals_.Connect(menu_item, "activate",
170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                       G_CALLBACK(OnMenuItemActivatedThunk), this);
171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else if (node->is_folder()) {
172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      GtkWidget* submenu = gtk_menu_new();
173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      BuildMenu(node, 0, submenu);
174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else {
176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      NOTREACHED();
177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_drag_source_set(menu_item, GDK_BUTTON1_MASK, NULL, 0,
180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_LINK));
18172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    int target_mask = ui::CHROME_BOOKMARK_ITEM;
182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (node->is_url())
18372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      target_mask |= ui::TEXT_URI_LIST | ui::NETSCAPE_URL;
18472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    ui::SetSourceTargetListFromCodeMask(menu_item, target_mask);
1854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    signals_.Connect(menu_item, "drag-begin",
186c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                     G_CALLBACK(OnMenuItemDragBeginThunk), this);
1874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    signals_.Connect(menu_item, "drag-end",
188c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                     G_CALLBACK(OnMenuItemDragEndThunk), this);
1894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    signals_.Connect(menu_item, "drag-data-get",
190c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                     G_CALLBACK(OnMenuItemDragGetThunk), this);
191c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
192c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // It is important to connect to this signal after setting up the drag
193c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // source because we only want to stifle the menu's default handler and
194c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // not the handler that the drag source uses.
195c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (node->is_folder()) {
1964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      signals_.Connect(menu_item, "button-press-event",
197c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                       G_CALLBACK(OnFolderButtonPressedThunk), this);
198c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
199c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
200c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
201c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    node_to_menu_widget_map_[node] = menu_item;
202c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
204ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  if (parent->child_count() == 0) {
205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* empty_menu = gtk_menu_item_new_with_label(
206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        l10n_util::GetStringUTF8(IDS_MENU_EMPTY_SUBMENU).c_str());
207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_widget_set_sensitive(empty_menu, FALSE);
208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    g_object_set_data(G_OBJECT(menu), "parent-node", AsVoid(parent));
209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_menu_shell_append(GTK_MENU_SHELL(menu), empty_menu);
210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
213513209b27ff55e2841eac0e4120199c23acce758Ben Murdochgboolean BookmarkMenuController::OnMenuButtonPressedOrReleased(
214c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* sender,
215c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GdkEventButton* event) {
216513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  // Handle middle mouse downs and right mouse ups.
217513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  if (!((event->button == 2 && event->type == GDK_BUTTON_RELEASE) ||
218513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      (event->button == 3 && event->type == GDK_BUTTON_PRESS))) {
219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return FALSE;
220513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  }
221c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
222c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  ignore_button_release_ = false;
223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkMenuShell* menu_shell = GTK_MENU_SHELL(sender);
224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // If the cursor is outside our bounds, pass this event up to the parent.
225c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!gtk_util::WidgetContainsCursor(sender)) {
226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (menu_shell->parent_menu_shell) {
227513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch      return OnMenuButtonPressedOrReleased(menu_shell->parent_menu_shell,
228513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch                                           event);
229c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    } else {
230c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // We are the top level menu; we can propagate no further.
231c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return FALSE;
232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // This will return NULL if we are not an empty menu.
236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const BookmarkNode* parent = GetParentNodeFromEmptyMenu(sender);
237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  bool is_empty_menu = !!parent;
238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // If there is no active menu item and we are not an empty menu, then do
239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // nothing. This can happen if the user has canceled a context menu while
240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // the cursor is hovering over a bookmark menu. Doing nothing is not optimal
241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // (the hovered item should be active), but it's a hopefully rare corner
242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // case.
243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  GtkWidget* menu_item = menu_shell->active_menu_item;
244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!is_empty_menu && !menu_item)
245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return TRUE;
246c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const BookmarkNode* node =
247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      menu_item ? GetNodeFromMenuItem(menu_item) : NULL;
248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
249513209b27ff55e2841eac0e4120199c23acce758Ben Murdoch  if (event->button == 2 && node && node->is_folder()) {
250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    bookmark_utils::OpenAll(parent_window_,
251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            profile_, page_navigator_,
252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                            node, NEW_BACKGROUND_TAB);
253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_menu_popdown(GTK_MENU(menu_));
254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return TRUE;
255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  } else if (event->button == 3) {
256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    DCHECK_NE(is_empty_menu, !!node);
257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!is_empty_menu)
258ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      parent = node->parent();
259c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Show the right click menu and stop processing this button event.
261c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    std::vector<const BookmarkNode*> nodes;
262c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (node)
263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      nodes.push_back(node);
264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    context_menu_controller_.reset(
265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        new BookmarkContextMenuController(
266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            parent_window_, this, profile_,
267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            page_navigator_, parent, nodes));
268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    context_menu_.reset(
269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        new MenuGtk(NULL, context_menu_controller_->menu_model()));
270c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
271c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Our bookmark folder menu loses the grab to the context menu. When the
272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // context menu is hidden, re-assert our grab.
273c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* grabbing_menu = gtk_grab_get_current();
274c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    g_object_ref(grabbing_menu);
2754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    signals_.Connect(context_menu_->widget(), "hide",
276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                     G_CALLBACK(OnContextMenuHide), grabbing_menu);
277c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
27872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root),
27972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                  event->time);
280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return TRUE;
281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
283c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return FALSE;
284c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgboolean BookmarkMenuController::OnButtonReleased(
287c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* sender,
288c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GdkEventButton* event) {
289c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (ignore_button_release_) {
290c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // Don't handle this message; it was a drag.
291c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    ignore_button_release_ = false;
292c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return FALSE;
293c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
294c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
295c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Releasing either button 1 or 2 should trigger the bookmark.
296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (!gtk_menu_item_get_submenu(GTK_MENU_ITEM(sender))) {
297c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // The menu item is a link node.
298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (event->button == 1 || event->button == 2) {
299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      WindowOpenDisposition disposition =
300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          event_utils::DispositionFromEventFlags(event->state);
301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      NavigateToMenuItem(sender, disposition);
302c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
303c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // We need to manually dismiss the popup menu because we're overriding
304c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // button-release-event.
305c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gtk_menu_popdown(GTK_MENU(menu_));
306c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  } else {
309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    // The menu item is a folder node.
310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (event->button == 1) {
311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Having overriden the normal handling, we need to manually activate
312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // the item.
313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      gtk_menu_shell_select_item(GTK_MENU_SHELL(sender->parent), sender);
314c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      g_signal_emit_by_name(sender->parent, "activate-current");
315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return TRUE;
316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return FALSE;
320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
321c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochgboolean BookmarkMenuController::OnFolderButtonPressed(
323c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* sender, GdkEventButton* event) {
324c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // The button press may start a drag; don't let the default handler run.
325c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (event->button == 1)
326c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return TRUE;
327c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return FALSE;
328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
329c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
330c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::OnMenuHidden(GtkWidget* menu) {
331c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  if (triggering_widget_)
332c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(triggering_widget_));
333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::OnMenuItemActivated(GtkWidget* menu_item) {
336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  NavigateToMenuItem(menu_item, CURRENT_TAB);
337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::OnMenuItemDragBegin(GtkWidget* menu_item,
340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                                 GdkDragContext* drag_context) {
341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // The parent menu item might be removed during the drag. Ref it so |button|
342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // won't get destroyed.
343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_object_ref(menu_item->parent);
344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Signal to any future OnButtonReleased calls that we're dragging instead of
346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // pressing.
347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  ignore_button_release_ = true;
348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(menu_item);
350c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  drag_icon_ = bookmark_utils::GetDragRepresentationForNode(
351ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      node, model_, GtkThemeService::GetFrom(profile_));
352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gint x, y;
353c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_widget_get_pointer(menu_item, &x, &y);
354c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_drag_set_icon_widget(drag_context, drag_icon_, x, y);
355c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
356c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Hide our node.
357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_widget_hide(menu_item);
358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::OnMenuItemDragEnd(GtkWidget* menu_item,
361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                               GdkDragContext* drag_context) {
362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_widget_show(menu_item);
363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  g_object_unref(menu_item->parent);
364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  gtk_widget_destroy(drag_icon_);
366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  drag_icon_ = NULL;
367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochvoid BookmarkMenuController::OnMenuItemDragGet(
370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkWidget* widget, GdkDragContext* context,
371c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    GtkSelectionData* selection_data,
372c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    guint target_type, guint time) {
373c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(widget);
374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  bookmark_utils::WriteBookmarkToSelection(node, selection_data, target_type,
375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                           profile_);
376c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch}
377