summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/bookmark_menu_controller_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/bookmark_menu_controller_gtk.cc')
-rw-r--r--chrome/browser/gtk/bookmark_menu_controller_gtk.cc363
1 files changed, 363 insertions, 0 deletions
diff --git a/chrome/browser/gtk/bookmark_menu_controller_gtk.cc b/chrome/browser/gtk/bookmark_menu_controller_gtk.cc
new file mode 100644
index 0000000..e9395f2
--- /dev/null
+++ b/chrome/browser/gtk/bookmark_menu_controller_gtk.cc
@@ -0,0 +1,363 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/bookmark_menu_controller_gtk.h"
+
+#include <gtk/gtk.h>
+
+#include "app/gtk_dnd_util.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/string_util.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/bookmarks/bookmark_utils.h"
+#include "chrome/browser/gtk/bookmark_utils_gtk.h"
+#include "chrome/browser/gtk/gtk_chrome_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/gtk/menu_gtk.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents/page_navigator.h"
+#include "gfx/gtk_util.h"
+#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "webkit/glue/window_open_disposition.h"
+
+namespace {
+
+// TODO(estade): It might be a good idea to vary this by locale.
+const int kMaxChars = 50;
+
+void SetImageMenuItem(GtkWidget* menu_item,
+ const BookmarkNode* node,
+ BookmarkModel* model) {
+ GdkPixbuf* pixbuf = bookmark_utils::GetPixbufForNode(node, model, true);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+}
+
+const BookmarkNode* GetNodeFromMenuItem(GtkWidget* menu_item) {
+ return static_cast<const BookmarkNode*>(
+ g_object_get_data(G_OBJECT(menu_item), "bookmark-node"));
+}
+
+const BookmarkNode* GetParentNodeFromEmptyMenu(GtkWidget* menu) {
+ return static_cast<const BookmarkNode*>(
+ g_object_get_data(G_OBJECT(menu), "parent-node"));
+}
+
+void* AsVoid(const BookmarkNode* node) {
+ return const_cast<BookmarkNode*>(node);
+}
+
+// The context menu has been dismissed, restore the X and application grabs
+// to whichever menu last had them. (Assuming that menu is still showing.)
+void OnContextMenuHide(GtkWidget* context_menu, GtkWidget* grab_menu) {
+ gtk_util::GrabAllInput(grab_menu);
+
+ // Match the ref we took when connecting this signal.
+ g_object_unref(grab_menu);
+}
+
+} // namespace
+
+BookmarkMenuController::BookmarkMenuController(Browser* browser,
+ Profile* profile,
+ PageNavigator* navigator,
+ GtkWindow* window,
+ const BookmarkNode* node,
+ int start_child_index)
+ : browser_(browser),
+ profile_(profile),
+ page_navigator_(navigator),
+ parent_window_(window),
+ model_(profile->GetBookmarkModel()),
+ node_(node),
+ drag_icon_(NULL),
+ ignore_button_release_(false),
+ triggering_widget_(NULL) {
+ menu_ = gtk_menu_new();
+ BuildMenu(node, start_child_index, menu_);
+ g_signal_connect(menu_, "hide",
+ G_CALLBACK(OnMenuHiddenThunk), this);
+ gtk_widget_show_all(menu_);
+}
+
+BookmarkMenuController::~BookmarkMenuController() {
+ profile_->GetBookmarkModel()->RemoveObserver(this);
+ gtk_menu_popdown(GTK_MENU(menu_));
+}
+
+void BookmarkMenuController::Popup(GtkWidget* widget, gint button_type,
+ guint32 timestamp) {
+ profile_->GetBookmarkModel()->AddObserver(this);
+
+ triggering_widget_ = widget;
+ gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget),
+ GTK_STATE_ACTIVE);
+ gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
+ &MenuGtk::WidgetMenuPositionFunc,
+ widget, button_type, timestamp);
+}
+
+void BookmarkMenuController::BookmarkModelChanged() {
+ gtk_menu_popdown(GTK_MENU(menu_));
+}
+
+void BookmarkMenuController::BookmarkNodeFavIconLoaded(
+ BookmarkModel* model, const BookmarkNode* node) {
+ std::map<const BookmarkNode*, GtkWidget*>::iterator it =
+ node_to_menu_widget_map_.find(node);
+ if (it != node_to_menu_widget_map_.end())
+ SetImageMenuItem(it->second, node, model);
+}
+
+void BookmarkMenuController::WillExecuteCommand() {
+ gtk_menu_popdown(GTK_MENU(menu_));
+}
+
+void BookmarkMenuController::CloseMenu() {
+ context_menu_->Cancel();
+}
+
+void BookmarkMenuController::NavigateToMenuItem(
+ GtkWidget* menu_item,
+ WindowOpenDisposition disposition) {
+ const BookmarkNode* node = GetNodeFromMenuItem(menu_item);
+ DCHECK(node);
+ DCHECK(page_navigator_);
+ page_navigator_->OpenURL(
+ node->GetURL(), GURL(), disposition, PageTransition::AUTO_BOOKMARK);
+}
+
+void BookmarkMenuController::BuildMenu(const BookmarkNode* parent,
+ int start_child_index,
+ GtkWidget* menu) {
+ DCHECK(!parent->GetChildCount() ||
+ start_child_index < parent->GetChildCount());
+
+ g_signal_connect(menu, "button-press-event",
+ G_CALLBACK(OnButtonPressedThunk), this);
+
+ for (int i = start_child_index; i < parent->GetChildCount(); ++i) {
+ const BookmarkNode* node = parent->GetChild(i);
+
+ // This breaks on word boundaries. Ideally we would break on character
+ // boundaries.
+ std::wstring elided_name =
+ l10n_util::TruncateString(node->GetTitle(), kMaxChars);
+ GtkWidget* menu_item =
+ gtk_image_menu_item_new_with_label(WideToUTF8(elided_name).c_str());
+ g_object_set_data(G_OBJECT(menu_item), "bookmark-node", AsVoid(node));
+ SetImageMenuItem(menu_item, node, profile_->GetBookmarkModel());
+ gtk_util::SetAlwaysShowImage(menu_item);
+
+ g_signal_connect(menu_item, "button-release-event",
+ G_CALLBACK(OnButtonReleasedThunk), this);
+ if (node->is_url()) {
+ g_signal_connect(menu_item, "activate",
+ G_CALLBACK(OnMenuItemActivatedThunk), this);
+ } else if (node->is_folder()) {
+ GtkWidget* submenu = gtk_menu_new();
+ BuildMenu(node, 0, submenu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+ } else {
+ NOTREACHED();
+ }
+
+ gtk_drag_source_set(menu_item, GDK_BUTTON1_MASK, NULL, 0,
+ static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_LINK));
+ int target_mask = gtk_dnd_util::CHROME_BOOKMARK_ITEM;
+ if (node->is_url())
+ target_mask |= gtk_dnd_util::TEXT_URI_LIST | gtk_dnd_util::NETSCAPE_URL;
+ gtk_dnd_util::SetSourceTargetListFromCodeMask(menu_item, target_mask);
+ g_signal_connect(menu_item, "drag-begin",
+ G_CALLBACK(OnMenuItemDragBeginThunk), this);
+ g_signal_connect(menu_item, "drag-end",
+ G_CALLBACK(OnMenuItemDragEndThunk), this);
+ g_signal_connect(menu_item, "drag-data-get",
+ G_CALLBACK(OnMenuItemDragGetThunk), this);
+
+ // It is important to connect to this signal after setting up the drag
+ // source because we only want to stifle the menu's default handler and
+ // not the handler that the drag source uses.
+ if (node->is_folder()) {
+ g_signal_connect(menu_item, "button-press-event",
+ G_CALLBACK(OnFolderButtonPressedThunk), this);
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
+ node_to_menu_widget_map_[node] = menu_item;
+ }
+
+ if (parent->GetChildCount() == 0) {
+ GtkWidget* empty_menu = gtk_menu_item_new_with_label(
+ l10n_util::GetStringUTF8(IDS_MENU_EMPTY_SUBMENU).c_str());
+ gtk_widget_set_sensitive(empty_menu, FALSE);
+ g_object_set_data(G_OBJECT(menu), "parent-node", AsVoid(parent));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), empty_menu);
+ }
+}
+
+gboolean BookmarkMenuController::OnButtonPressed(
+ GtkWidget* sender,
+ GdkEventButton* event) {
+ if (event->button == 1)
+ return FALSE;
+
+ ignore_button_release_ = false;
+ GtkMenuShell* menu_shell = GTK_MENU_SHELL(sender);
+ // If the cursor is outside our bounds, pass this event up to the parent.
+ if (!gtk_util::WidgetContainsCursor(sender)) {
+ if (menu_shell->parent_menu_shell) {
+ return OnButtonPressed(menu_shell->parent_menu_shell, event);
+ } else {
+ // We are the top level menu; we can propagate no further.
+ return FALSE;
+ }
+ }
+
+ // This will return NULL if we are not an empty menu.
+ const BookmarkNode* parent = GetParentNodeFromEmptyMenu(sender);
+ bool is_empty_menu = !!parent;
+ // If there is no active menu item and we are not an empty menu, then do
+ // nothing. This can happen if the user has canceled a context menu while
+ // the cursor is hovering over a bookmark menu. Doing nothing is not optimal
+ // (the hovered item should be active), but it's a hopefully rare corner
+ // case.
+ GtkWidget* menu_item = menu_shell->active_menu_item;
+ if (!is_empty_menu && !menu_item)
+ return TRUE;
+ const BookmarkNode* node =
+ menu_item ? GetNodeFromMenuItem(menu_item) : NULL;
+
+ if (event->button == 2 && node) {
+ bookmark_utils::OpenAll(parent_window_,
+ profile_, page_navigator_,
+ node, NEW_BACKGROUND_TAB);
+ gtk_menu_popdown(GTK_MENU(menu_));
+ return TRUE;
+ } else if (event->button == 3) {
+ DCHECK_NE(is_empty_menu, !!node);
+ if (!is_empty_menu)
+ parent = node->GetParent();
+
+ // Show the right click menu and stop processing this button event.
+ std::vector<const BookmarkNode*> nodes;
+ if (node)
+ nodes.push_back(node);
+ context_menu_controller_.reset(
+ new BookmarkContextMenuController(
+ parent_window_, this, profile_,
+ page_navigator_, parent, nodes));
+ context_menu_.reset(
+ new MenuGtk(NULL, context_menu_controller_->menu_model()));
+
+ // Our bookmark folder menu loses the grab to the context menu. When the
+ // context menu is hidden, re-assert our grab.
+ GtkWidget* grabbing_menu = gtk_grab_get_current();
+ g_object_ref(grabbing_menu);
+ g_signal_connect(context_menu_->widget(), "hide",
+ G_CALLBACK(OnContextMenuHide), grabbing_menu);
+
+ context_menu_->PopupAsContext(event->time);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean BookmarkMenuController::OnButtonReleased(
+ GtkWidget* sender,
+ GdkEventButton* event) {
+ if (ignore_button_release_) {
+ // Don't handle this message; it was a drag.
+ ignore_button_release_ = false;
+ return FALSE;
+ }
+
+ // Releasing either button 1 or 2 should trigger the bookmark.
+ if (!gtk_menu_item_get_submenu(GTK_MENU_ITEM(sender))) {
+ // The menu item is a link node.
+ if (event->button == 1 || event->button == 2) {
+ WindowOpenDisposition disposition =
+ event_utils::DispositionFromEventFlags(event->state);
+ NavigateToMenuItem(sender, disposition);
+
+ // We need to manually dismiss the popup menu because we're overriding
+ // button-release-event.
+ gtk_menu_popdown(GTK_MENU(menu_));
+ return TRUE;
+ }
+ } else {
+ // The menu item is a folder node.
+ if (event->button == 1) {
+ // Having overriden the normal handling, we need to manually activate
+ // the item.
+ gtk_menu_shell_select_item(GTK_MENU_SHELL(sender->parent), sender);
+ g_signal_emit_by_name(sender->parent, "activate-current");
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+gboolean BookmarkMenuController::OnFolderButtonPressed(
+ GtkWidget* sender, GdkEventButton* event) {
+ // The button press may start a drag; don't let the default handler run.
+ if (event->button == 1)
+ return TRUE;
+ return FALSE;
+}
+
+void BookmarkMenuController::OnMenuHidden(GtkWidget* menu) {
+ if (triggering_widget_)
+ gtk_chrome_button_unset_paint_state(GTK_CHROME_BUTTON(triggering_widget_));
+}
+
+void BookmarkMenuController::OnMenuItemActivated(GtkWidget* menu_item) {
+ NavigateToMenuItem(menu_item, CURRENT_TAB);
+}
+
+void BookmarkMenuController::OnMenuItemDragBegin(GtkWidget* menu_item,
+ GdkDragContext* drag_context) {
+ // The parent menu item might be removed during the drag. Ref it so |button|
+ // won't get destroyed.
+ g_object_ref(menu_item->parent);
+
+ // Signal to any future OnButtonReleased calls that we're dragging instead of
+ // pressing.
+ ignore_button_release_ = true;
+
+ const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(menu_item);
+ drag_icon_ = bookmark_utils::GetDragRepresentationForNode(
+ node, model_, GtkThemeProvider::GetFrom(profile_));
+ gint x, y;
+ gtk_widget_get_pointer(menu_item, &x, &y);
+ gtk_drag_set_icon_widget(drag_context, drag_icon_, x, y);
+
+ // Hide our node.
+ gtk_widget_hide(menu_item);
+}
+
+void BookmarkMenuController::OnMenuItemDragEnd(GtkWidget* menu_item,
+ GdkDragContext* drag_context) {
+ gtk_widget_show(menu_item);
+ g_object_unref(menu_item->parent);
+
+ gtk_widget_destroy(drag_icon_);
+ drag_icon_ = NULL;
+}
+
+void BookmarkMenuController::OnMenuItemDragGet(
+ GtkWidget* widget, GdkDragContext* context,
+ GtkSelectionData* selection_data,
+ guint target_type, guint time) {
+ const BookmarkNode* node = bookmark_utils::BookmarkNodeForWidget(widget);
+ bookmark_utils::WriteBookmarkToSelection(node, selection_data, target_type,
+ profile_);
+}