15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/bookmarks/bookmark_menu_controller_gtk.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <gtk/gtk.h>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/string_util.h"
10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/bookmarks/bookmark_model.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/bookmarks/bookmark_model_factory.h"
130f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "chrome/browser/bookmarks/bookmark_stats.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/bookmarks/bookmark_utils.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser.h"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/event_utils.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/gtk_chrome_button.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/gtk_theme_service.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/gtk_util.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/gtk/menu_gtk.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/page_navigator.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/generated_resources.h"
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/theme_resources.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ui_resources.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/dragdrop/gtk_dnd_util.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/window_open_disposition.h"
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/gtk_util.h"
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::OpenURLParams;
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::PageNavigator;
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void SetImageMenuItem(GtkWidget* menu_item,
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      const BookmarkNode* node,
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      BookmarkModel* model) {
413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  GdkPixbuf* pixbuf = GetPixbufForNode(node, model, true);
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                gtk_image_new_from_pixbuf(pixbuf));
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_unref(pixbuf);
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const BookmarkNode* GetNodeFromMenuItem(GtkWidget* menu_item) {
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return static_cast<const BookmarkNode*>(
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      g_object_get_data(G_OBJECT(menu_item), "bookmark-node"));
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const BookmarkNode* GetParentNodeFromEmptyMenu(GtkWidget* menu) {
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return static_cast<const BookmarkNode*>(
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      g_object_get_data(G_OBJECT(menu), "parent-node"));
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void* AsVoid(const BookmarkNode* node) {
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return const_cast<BookmarkNode*>(node);
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// The context menu has been dismissed, restore the X and application grabs
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// to whichever menu last had them. (Assuming that menu is still showing.)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void OnContextMenuHide(GtkWidget* context_menu, GtkWidget* grab_menu) {
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_util::GrabAllInput(grab_menu);
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Match the ref we took when connecting this signal.
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_unref(grab_menu);
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)BookmarkMenuController::BookmarkMenuController(Browser* browser,
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                               PageNavigator* navigator,
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                               GtkWindow* window,
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                               const BookmarkNode* node,
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                               int start_child_index)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : browser_(browser),
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      page_navigator_(navigator),
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parent_window_(window),
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      model_(BookmarkModelFactory::GetForProfile(browser->profile())),
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      node_(node),
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      drag_icon_(NULL),
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ignore_button_release_(false),
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      triggering_widget_(NULL) {
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  menu_ = gtk_menu_new();
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_ref_sink(menu_);
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  BuildMenu(node, start_child_index, menu_);
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  signals_.Connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_show_all(menu_);
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)BookmarkMenuController::~BookmarkMenuController() {
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  model_->RemoveObserver(this);
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Make sure the hide handler runs.
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_hide(menu_);
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_destroy(menu_);
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_unref(menu_);
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::Popup(GtkWidget* widget, gint button_type,
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   guint32 timestamp) {
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  model_->AddObserver(this);
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  triggering_widget_ = widget;
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  signals_.Connect(triggering_widget_, "destroy",
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   G_CALLBACK(gtk_widget_destroyed), &triggering_widget_);
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget),
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                    GTK_STATE_ACTIVE);
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 &MenuGtk::WidgetMenuPositionFunc,
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 widget, button_type, timestamp);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::BookmarkModelChanged() {
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_menu_popdown(GTK_MENU(menu_));
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::BookmarkNodeFaviconChanged(
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    BookmarkModel* model, const BookmarkNode* node) {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::map<const BookmarkNode*, GtkWidget*>::iterator it =
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      node_to_menu_widget_map_.find(node);
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (it != node_to_menu_widget_map_.end())
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetImageMenuItem(it->second, node, model);
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void BookmarkMenuController::WillExecuteCommand(
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      int command_id,
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      const std::vector<const BookmarkNode*>& bookmarks) {
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_menu_popdown(GTK_MENU(menu_));
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::CloseMenu() {
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  context_menu_->Cancel();
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::NavigateToMenuItem(
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* menu_item,
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    WindowOpenDisposition disposition) {
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const BookmarkNode* node = GetNodeFromMenuItem(menu_item);
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(node);
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(page_navigator_);
1420f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)  RecordBookmarkLaunch(node, BOOKMARK_LAUNCH_LOCATION_BAR_SUBFOLDER);
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  page_navigator_->OpenURL(OpenURLParams(
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      node->url(), content::Referrer(), disposition,
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       int start_child_index,
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       GtkWidget* menu) {
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(parent->empty() || start_child_index < parent->child_count());
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  signals_.Connect(menu, "button-press-event",
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  signals_.Connect(menu, "button-release-event",
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   G_CALLBACK(OnMenuButtonPressedOrReleasedThunk), this);
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (int i = start_child_index; i < parent->child_count(); ++i) {
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const BookmarkNode* node = parent->GetChild(i);
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    GtkWidget* menu_item =
1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        gtk_image_menu_item_new_with_label(BuildMenuLabelFor(node).c_str());
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_object_set_data(G_OBJECT(menu_item), "bookmark-node", AsVoid(node));
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SetImageMenuItem(menu_item, node, model_);
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_util::SetAlwaysShowImage(menu_item);
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signals_.Connect(menu_item, "button-release-event",
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     G_CALLBACK(OnButtonReleasedThunk), this);
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (node->is_url()) {
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      signals_.Connect(menu_item, "activate",
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       G_CALLBACK(OnMenuItemActivatedThunk), this);
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else if (node->is_folder()) {
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GtkWidget* submenu = gtk_menu_new();
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      BuildMenu(node, 0, submenu);
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED();
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_drag_source_set(menu_item, GDK_BUTTON1_MASK, NULL, 0,
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_LINK));
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int target_mask = ui::CHROME_BOOKMARK_ITEM;
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (node->is_url())
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      target_mask |= ui::TEXT_URI_LIST | ui::NETSCAPE_URL;
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ui::SetSourceTargetListFromCodeMask(menu_item, target_mask);
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signals_.Connect(menu_item, "drag-begin",
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     G_CALLBACK(OnMenuItemDragBeginThunk), this);
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signals_.Connect(menu_item, "drag-end",
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     G_CALLBACK(OnMenuItemDragEndThunk), this);
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signals_.Connect(menu_item, "drag-data-get",
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     G_CALLBACK(OnMenuItemDragGetThunk), this);
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // It is important to connect to this signal after setting up the drag
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // source because we only want to stifle the menu's default handler and
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // not the handler that the drag source uses.
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (node->is_folder()) {
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      signals_.Connect(menu_item, "button-press-event",
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       G_CALLBACK(OnFolderButtonPressedThunk), this);
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    node_to_menu_widget_map_[node] = menu_item;
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (parent->empty()) {
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* empty_menu = gtk_menu_item_new_with_label(
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        l10n_util::GetStringUTF8(IDS_MENU_EMPTY_SUBMENU).c_str());
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_widget_set_sensitive(empty_menu, FALSE);
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_object_set_data(G_OBJECT(menu), "parent-node", AsVoid(parent));
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_menu_shell_append(GTK_MENU_SHELL(menu), empty_menu);
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gboolean BookmarkMenuController::OnMenuButtonPressedOrReleased(
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* sender,
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GdkEventButton* event) {
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Handle middle mouse downs and right mouse ups.
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!((event->button == 2 && event->type == GDK_BUTTON_RELEASE) ||
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (event->button == 3 && event->type == GDK_BUTTON_PRESS))) {
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return FALSE;
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ignore_button_release_ = false;
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GtkMenuShell* menu_shell = GTK_MENU_SHELL(sender);
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the cursor is outside our bounds, pass this event up to the parent.
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!gtk_util::WidgetContainsCursor(sender)) {
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (menu_shell->parent_menu_shell) {
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return OnMenuButtonPressedOrReleased(menu_shell->parent_menu_shell,
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           event);
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // We are the top level menu; we can propagate no further.
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return FALSE;
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // This will return NULL if we are not an empty menu.
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const BookmarkNode* parent = GetParentNodeFromEmptyMenu(sender);
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool is_empty_menu = !!parent;
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If there is no active menu item and we are not an empty menu, then do
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // nothing. This can happen if the user has canceled a context menu while
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the cursor is hovering over a bookmark menu. Doing nothing is not optimal
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // (the hovered item should be active), but it's a hopefully rare corner
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // case.
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GtkWidget* menu_item = menu_shell->active_menu_item;
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_empty_menu && !menu_item)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return TRUE;
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const BookmarkNode* node =
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      menu_item ? GetNodeFromMenuItem(menu_item) : NULL;
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (event->button == 2 && node && node->is_folder()) {
2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    chrome::OpenAll(parent_window_, page_navigator_, node, NEW_BACKGROUND_TAB,
2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    browser_->profile());
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_menu_popdown(GTK_MENU(menu_));
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return TRUE;
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (event->button == 3) {
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK_NE(is_empty_menu, !!node);
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!is_empty_menu)
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parent = node->parent();
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Show the right click menu and stop processing this button event.
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::vector<const BookmarkNode*> nodes;
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (node)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      nodes.push_back(node);
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    context_menu_controller_.reset(
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new BookmarkContextMenuController(
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parent_window_, this, browser_, browser_->profile(),
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            page_navigator_, parent, nodes));
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    context_menu_.reset(
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        new MenuGtk(NULL, context_menu_controller_->menu_model()));
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Our bookmark folder menu loses the grab to the context menu. When the
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // context menu is hidden, re-assert our grab.
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* grabbing_menu = gtk_grab_get_current();
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_object_ref(grabbing_menu);
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signals_.Connect(context_menu_->widget(), "hide",
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     G_CALLBACK(OnContextMenuHide), grabbing_menu);
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root),
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  event->time);
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return TRUE;
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return FALSE;
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gboolean BookmarkMenuController::OnButtonReleased(
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* sender,
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GdkEventButton* event) {
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ignore_button_release_) {
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Don't handle this message; it was a drag.
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ignore_button_release_ = false;
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return FALSE;
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Releasing either button 1 or 2 should trigger the bookmark.
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!gtk_menu_item_get_submenu(GTK_MENU_ITEM(sender))) {
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // The menu item is a link node.
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (event->button == 1 || event->button == 2) {
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      WindowOpenDisposition disposition =
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        event_utils::DispositionFromGdkState(event->state);
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NavigateToMenuItem(sender, disposition);
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // We need to manually dismiss the popup menu because we're overriding
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // button-release-event.
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      gtk_menu_popdown(GTK_MENU(menu_));
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return TRUE;
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // The menu item is a folder node.
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (event->button == 1) {
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Having overriden the normal handling, we need to manually activate
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // the item.
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      gtk_menu_shell_select_item(GTK_MENU_SHELL(sender->parent), sender);
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      g_signal_emit_by_name(sender->parent, "activate-current");
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return TRUE;
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return FALSE;
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gboolean BookmarkMenuController::OnFolderButtonPressed(
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    GtkWidget* sender, GdkEventButton* event) {
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The button press may start a drag; don't let the default handler run.
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (event->button == 1)
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return TRUE;
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return FALSE;
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::OnMenuHidden(GtkWidget* menu) {
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (triggering_widget_)
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(triggering_widget_));
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::OnMenuItemActivated(GtkWidget* menu_item) {
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  NavigateToMenuItem(menu_item, CURRENT_TAB);
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::OnMenuItemDragBegin(GtkWidget* menu_item,
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                 GdkDragContext* drag_context) {
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The parent menu item might be removed during the drag. Ref it so |button|
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // won't get destroyed.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_ref(menu_item->parent);
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Signal to any future OnButtonReleased calls that we're dragging instead of
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // pressing.
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ignore_button_release_ = true;
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  const BookmarkNode* node = BookmarkNodeForWidget(menu_item);
3513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  drag_icon_ = GetDragRepresentationForNode(
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      node, model_, GtkThemeService::GetFrom(browser_->profile()));
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gint x, y;
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_get_pointer(menu_item, &x, &y);
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_drag_set_icon_widget(drag_context, drag_icon_, x, y);
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Hide our node.
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_hide(menu_item);
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void BookmarkMenuController::OnMenuItemDragEnd(GtkWidget* menu_item,
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                               GdkDragContext* drag_context) {
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_show(menu_item);
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  g_object_unref(menu_item->parent);
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gtk_widget_destroy(drag_icon_);
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  drag_icon_ = NULL;
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3703551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)void BookmarkMenuController::OnMenuItemDragGet(GtkWidget* widget,
3713551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)                                               GdkDragContext* context,
3723551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)                                               GtkSelectionData* selection_data,
3733551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)                                               guint target_type,
3743551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)                                               guint time) {
3753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  const BookmarkNode* node = BookmarkNodeForWidget(widget);
3763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  WriteBookmarkToSelection(
3773551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)      node, selection_data, target_type, browser_->profile());
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
379