summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-10 21:35:52 +0000
committererg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-10 21:35:52 +0000
commit300647558310e1d8a33129e1446318e0baea48b8 (patch)
tree85fc937d9bf2257ecd166c265399f993cae1d543
parenteaaa13e1f3a1df738f059d0cd59266b6c45e819c (diff)
downloadchromium_src-300647558310e1d8a33129e1446318e0baea48b8.zip
chromium_src-300647558310e1d8a33129e1446318e0baea48b8.tar.gz
chromium_src-300647558310e1d8a33129e1446318e0baea48b8.tar.bz2
GTK: Implement the global bookmarks menu.
BUG=30213 TEST=The bookmarks menu under Unity is populated with the user's bookmarks. Review URL: http://codereview.chromium.org/6980011 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@84860 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/ui/gtk/global_bookmark_menu.cc260
-rw-r--r--chrome/browser/ui/gtk/global_bookmark_menu.h125
-rw-r--r--chrome/browser/ui/gtk/global_menu_bar.cc15
-rw-r--r--chrome/browser/ui/gtk/global_menu_bar.h5
-rw-r--r--chrome/chrome_browser.gypi2
5 files changed, 395 insertions, 12 deletions
diff --git a/chrome/browser/ui/gtk/global_bookmark_menu.cc b/chrome/browser/ui/gtk/global_bookmark_menu.cc
new file mode 100644
index 0000000..c06fc1b
--- /dev/null
+++ b/chrome/browser/ui/gtk/global_bookmark_menu.cc
@@ -0,0 +1,260 @@
+// Copyright (c) 2011 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/ui/gtk/global_bookmark_menu.h"
+
+#include <gtk/gtk.h>
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/gtk/global_bookmark_menu.h"
+#include "chrome/browser/ui/gtk/global_menu_bar.h"
+#include "chrome/browser/ui/gtk/gtk_theme_service.h"
+#include "chrome/browser/ui/gtk/gtk_util.h"
+#include "content/common/notification_service.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/gtk_util.h"
+
+namespace {
+
+const int kMaxChars = 50;
+
+} // namespace
+
+GlobalBookmarkMenu::GlobalBookmarkMenu(Browser* browser)
+ : browser_(browser),
+ profile_(browser->profile()),
+ default_favicon_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
+ DCHECK(profile_);
+
+ default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
+ default_folder_ = GtkThemeService::GetFolderIcon(true);
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ Source<Profile>(profile_));
+}
+
+GlobalBookmarkMenu::~GlobalBookmarkMenu() {
+ profile_->GetBookmarkModel()->RemoveObserver(this);
+}
+
+void GlobalBookmarkMenu::Init(GtkWidget* bookmark_menu) {
+ bookmark_menu_ = bookmark_menu;
+
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ model->AddObserver(this);
+ if (model->IsLoaded())
+ Loaded(model);
+}
+
+void GlobalBookmarkMenu::RebuildMenuInFuture() {
+ method_factory_.RevokeAll();
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ method_factory_.NewRunnableMethod(&GlobalBookmarkMenu::RebuildMenu));
+}
+
+void GlobalBookmarkMenu::RebuildMenu() {
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ DCHECK(model);
+ DCHECK(model->IsLoaded());
+
+ ClearBookmarkMenu();
+
+ const BookmarkNode* bar_node = model->GetBookmarkBarNode();
+ if (bar_node->child_count()) {
+ AddBookmarkMenuItem(bookmark_menu_, gtk_separator_menu_item_new());
+ AddNodeToMenu(bar_node, bookmark_menu_);
+ }
+
+ // Only display the other bookmarks folder in the menu if it has items in it.
+ const BookmarkNode* other_node = model->other_node();
+ if (other_node->child_count()) {
+ GtkWidget* submenu = gtk_menu_new();
+ AddNodeToMenu(other_node, submenu);
+
+ AddBookmarkMenuItem(bookmark_menu_, gtk_separator_menu_item_new());
+
+ GtkWidget* menu_item = gtk_image_menu_item_new_with_label(
+ l10n_util::GetStringUTF8(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME).c_str());
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
+ gtk_image_menu_item_set_image(
+ GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(default_folder_));
+
+ AddBookmarkMenuItem(bookmark_menu_, menu_item);
+ }
+}
+
+void GlobalBookmarkMenu::AddBookmarkMenuItem(GtkWidget* menu,
+ GtkWidget* menu_item) {
+ g_object_set_data(G_OBJECT(menu_item), "type-tag",
+ GINT_TO_POINTER(GlobalMenuBar::TAG_BOOKMARK_CLEARABLE));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
+ gtk_widget_show(menu_item);
+}
+
+void GlobalBookmarkMenu::AddNodeToMenu(const BookmarkNode* node,
+ GtkWidget* menu) {
+ int child_count = node->child_count();
+ if (!child_count) {
+ GtkWidget* item = gtk_menu_item_new_with_label(
+ l10n_util::GetStringUTF8(IDS_MENU_EMPTY_SUBMENU).c_str());
+ gtk_widget_set_sensitive(item, FALSE);
+ AddBookmarkMenuItem(menu, item);
+ } else {
+ for (int i = 0; i < child_count; i++) {
+ const BookmarkNode* child = node->GetChild(i);
+ GtkWidget* item = gtk_image_menu_item_new();
+ ConfigureMenuItem(child, item);
+ bookmark_nodes_[child] = item;
+
+ if (child->is_folder()) {
+ GtkWidget* submenu = gtk_menu_new();
+ AddNodeToMenu(child, submenu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
+ } else {
+ g_object_set_data(G_OBJECT(item), "bookmark-node",
+ const_cast<BookmarkNode*>(child));
+ g_signal_connect(item, "activate",
+ G_CALLBACK(OnBookmarkItemActivatedThunk), this);
+ }
+
+ AddBookmarkMenuItem(menu, item);
+ }
+ }
+}
+
+void GlobalBookmarkMenu::ConfigureMenuItem(const BookmarkNode* node,
+ GtkWidget* menu_item) {
+ string16 elided_name = l10n_util::TruncateString(node->GetTitle(), kMaxChars);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menu_item),
+ UTF16ToUTF8(elided_name).c_str());
+
+ if (node->is_url()) {
+ std::string tooltip = gtk_util::BuildTooltipTitleFor(node->GetTitle(),
+ node->GetURL());
+ gtk_widget_set_tooltip_markup(menu_item, tooltip.c_str());
+ }
+
+ const SkBitmap& bitmap = profile_->GetBookmarkModel()->GetFavicon(node);
+ if (!bitmap.isNull()) {
+ GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&bitmap);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ } else {
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(
+ node->is_url() ? default_favicon_ :
+ default_folder_));
+ }
+}
+
+GtkWidget* GlobalBookmarkMenu::MenuItemForNode(const BookmarkNode* node) {
+ if (!node)
+ return NULL;
+ std::map<const BookmarkNode*, GtkWidget*>::iterator it =
+ bookmark_nodes_.find(node);
+ if (it == bookmark_nodes_.end())
+ return NULL;
+ return it->second;
+}
+
+void GlobalBookmarkMenu::ClearBookmarkMenu() {
+ bookmark_nodes_.clear();
+
+ gtk_container_foreach(GTK_CONTAINER(bookmark_menu_),
+ &ClearBookmarkItemCallback,
+ NULL);
+}
+
+// static
+void GlobalBookmarkMenu::ClearBookmarkItemCallback(GtkWidget* menu_item,
+ void* unused) {
+ int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
+ if (tag == GlobalMenuBar::TAG_BOOKMARK_CLEARABLE)
+ gtk_widget_destroy(menu_item);
+}
+
+void GlobalBookmarkMenu::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type.value == NotificationType::BROWSER_THEME_CHANGED);
+
+ // Change the icon and invalidate the menu.
+ default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
+ default_folder_ = GtkThemeService::GetFolderIcon(true);
+ RebuildMenuInFuture();
+}
+
+void GlobalBookmarkMenu::Loaded(BookmarkModel* model) {
+ // If we have a Loaded() event, then we need to build the menu immediately
+ // for the first time.
+ RebuildMenu();
+}
+
+void GlobalBookmarkMenu::BookmarkModelBeingDeleted(BookmarkModel* model) {
+ ClearBookmarkMenu();
+}
+
+void GlobalBookmarkMenu::BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) {
+ RebuildMenuInFuture();
+}
+
+void GlobalBookmarkMenu::BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ RebuildMenuInFuture();
+}
+
+void GlobalBookmarkMenu::BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node) {
+ GtkWidget* item = MenuItemForNode(node);
+ if (item) {
+ gtk_widget_destroy(item);
+ bookmark_nodes_.erase(node);
+ }
+}
+
+void GlobalBookmarkMenu::BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) {
+ GtkWidget* item = MenuItemForNode(node);
+ if (item)
+ ConfigureMenuItem(node, item);
+}
+
+void GlobalBookmarkMenu::BookmarkNodeFaviconLoaded(BookmarkModel* model,
+ const BookmarkNode* node) {
+ GtkWidget* item = MenuItemForNode(node);
+ if (item)
+ ConfigureMenuItem(node, item);
+}
+
+void GlobalBookmarkMenu::BookmarkNodeChildrenReordered(
+ BookmarkModel* model,
+ const BookmarkNode* node) {
+ RebuildMenuInFuture();
+}
+
+void GlobalBookmarkMenu::OnBookmarkItemActivated(GtkWidget* menu_item) {
+ // The actual mouse event that generated this activated event was in a
+ // different process. Go with something default.
+ const BookmarkNode* node = static_cast<const BookmarkNode*>(
+ g_object_get_data(G_OBJECT(menu_item), "bookmark-node"));
+
+ browser_->OpenURL(node->GetURL(), GURL(), NEW_FOREGROUND_TAB,
+ PageTransition::AUTO_BOOKMARK);
+}
+
diff --git a/chrome/browser/ui/gtk/global_bookmark_menu.h b/chrome/browser/ui/gtk/global_bookmark_menu.h
new file mode 100644
index 0000000..aff940e
--- /dev/null
+++ b/chrome/browser/ui/gtk/global_bookmark_menu.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_BROWSER_UI_GTK_GLOBAL_BOOKMARK_MENU_H_
+#define CHROME_BROWSER_UI_GTK_GLOBAL_BOOKMARK_MENU_H_
+
+#include <map>
+
+#include "base/task.h"
+#include "chrome/browser/bookmarks/bookmark_model_observer.h"
+#include "content/common/notification_observer.h"
+#include "content/common/notification_registrar.h"
+#include "ui/base/gtk/gtk_signal.h"
+
+class Browser;
+class Profile;
+
+typedef struct _GdkPixbuf GdkPixbuf;
+typedef struct _GtkWidget GtkWidget;
+
+// Manages the global bookmarks menu.
+//
+// There are a few subtleties here: we can't rely on accurate event
+// dispositions being sent back, right click menus on menu items are placed
+// relative to the main chrome window instead of the global menu bar, and we
+// need to update the menu in the background (instead of building it on showing
+// and not updating it if the model changes). I'm not even thinking about
+// making these draggable since these items aren't displayed in our process.
+class GlobalBookmarkMenu : public NotificationObserver,
+ public BookmarkModelObserver {
+ public:
+ explicit GlobalBookmarkMenu(Browser* browser);
+ virtual ~GlobalBookmarkMenu();
+
+ // Takes the bookmark menu we need to modify based on bookmark state.
+ void Init(GtkWidget* bookmark_menu);
+
+ private:
+ // Schedules the menu to be rebuilt. The mac version sets a boolean and
+ // rebuilds the menu during their pre-show callback. We don't have anything
+ // like that: by the time we get a "show" signal from GTK+, the menu has
+ // already been displayed and it will take multiple dbus calls to add the
+ // menu items.
+ //
+ // Since the bookmark model works by sending us BookmarkNodeEvent
+ // notifications one by one, we use a timer to batch up calls.
+ void RebuildMenuInFuture();
+
+ // Rebuilds the menu now. Called on initial Load() and from
+ // RebuildMenuInFuture().
+ void RebuildMenu();
+
+ // Adds |item| to |menu| and marks it as a dynamic item.
+ void AddBookmarkMenuItem(GtkWidget* menu, GtkWidget* menu_item);
+
+ // Adds an menu item representing |node| to |menu|.
+ void AddNodeToMenu(const BookmarkNode* node, GtkWidget* menu);
+
+ // This configures a GtkWidget with all the data from a BookmarkNode. This is
+ // used to update existing menu items, as well as to configure newly created
+ // ones, like in AddNodeToMenu().
+ void ConfigureMenuItem(const BookmarkNode* node, GtkWidget* menu_item);
+
+ // Returns the GtkMenuItem for |node|.
+ GtkWidget* MenuItemForNode(const BookmarkNode* node);
+
+ // Removes all bookmark entries from the bookmark menu in anticipation that
+ // we're about to do a rebuild.
+ void ClearBookmarkMenu();
+
+ // Callback used in ClearBookmarkMenu().
+ static void ClearBookmarkItemCallback(GtkWidget* menu_item,
+ void* unused);
+
+ // NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // BookmarkModelObserver:
+ virtual void Loaded(BookmarkModel* model);
+ virtual void BookmarkModelBeingDeleted(BookmarkModel* model);
+ virtual void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index);
+ virtual void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index);
+ virtual void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node);
+ virtual void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node);
+ virtual void BookmarkNodeFaviconLoaded(BookmarkModel* model,
+ const BookmarkNode* node);
+ virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node);
+
+ CHROMEGTK_CALLBACK_0(GlobalBookmarkMenu, void, OnBookmarkItemActivated);
+
+ Browser* browser_;
+ Profile* profile_;
+
+ NotificationRegistrar registrar_;
+
+ GdkPixbuf* default_favicon_;
+ GdkPixbuf* default_folder_;
+
+ GtkWidget* bookmark_menu_;
+
+ // We use this factory to create callback tasks for ThreadWatcher object. We
+ // use this during ping-pong messaging between WatchDog thread and watched
+ // thread.
+ ScopedRunnableMethodFactory<GlobalBookmarkMenu> method_factory_;
+
+ // In order to appropriately update items in the bookmark menu, without
+ // forcing a rebuild, map the model's nodes to menu items.
+ std::map<const BookmarkNode*, GtkWidget*> bookmark_nodes_;
+};
+
+#endif // CHROME_BROWSER_UI_GTK_GLOBAL_BOOKMARK_MENU_H_
diff --git a/chrome/browser/ui/gtk/global_menu_bar.cc b/chrome/browser/ui/gtk/global_menu_bar.cc
index de70b8a..6e6f9fd 100644
--- a/chrome/browser/ui/gtk/global_menu_bar.cc
+++ b/chrome/browser/ui/gtk/global_menu_bar.cc
@@ -130,17 +130,6 @@ GlobalMenuBarCommand bookmark_menu[] = {
{ IDS_BOOKMARK_CURRENT_PAGE_LINUX, IDC_BOOKMARK_PAGE },
{ IDS_BOOKMARK_ALL_TABS_LINUX, IDC_BOOKMARK_ALL_TABS },
- { MENU_SEPARATOR, MENU_SEPARATOR },
- // TODO(erg): Real implementation of bookmark bar bookmarks!
- { MENU_SEPARATOR, MENU_SEPARATOR },
-
- { IDS_BOOMARK_BAR_OPEN_ALL, IDC_BOOKMARK_BAR_OPEN_ALL },
- { IDS_BOOMARK_BAR_OPEN_ALL_NEW_WINDOW, IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW },
- { IDS_BOOMARK_BAR_OPEN_ALL_INCOGNITO, IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO },
-
- { MENU_SEPARATOR, MENU_SEPARATOR },
- // TODO(erg): "Other bookmarks" bookmarks
-
{ MENU_END, MENU_END }
};
@@ -176,6 +165,7 @@ GlobalMenuBar::GlobalMenuBar(Browser* browser)
profile_(browser_->profile()),
menu_bar_(gtk_menu_bar_new()),
history_menu_(browser_),
+ bookmark_menu_(browser_),
dummy_accel_group_(gtk_accel_group_new()),
block_activation_(false) {
// The global menu bar should never actually be shown in the app; it should
@@ -191,7 +181,8 @@ GlobalMenuBar::GlobalMenuBar(Browser* browser)
BuildGtkMenuFrom(IDS_VIEW_MENU_LINUX, &id_to_menu_item_, view_menu);
history_menu_.Init(BuildGtkMenuFrom(IDS_HISTORY_MENU_LINUX, &id_to_menu_item_,
history_menu));
- BuildGtkMenuFrom(IDS_BOOKMARKS_MENU_LINUX, &id_to_menu_item_, bookmark_menu);
+ bookmark_menu_.Init(BuildGtkMenuFrom(IDS_BOOKMARKS_MENU_LINUX,
+ &id_to_menu_item_, bookmark_menu));
BuildGtkMenuFrom(IDS_TOOLS_MENU_LINUX, &id_to_menu_item_, tools_menu);
BuildGtkMenuFrom(IDS_HELP_MENU_LINUX, &id_to_menu_item_, help_menu);
diff --git a/chrome/browser/ui/gtk/global_menu_bar.h b/chrome/browser/ui/gtk/global_menu_bar.h
index 0497a64..1364e51 100644
--- a/chrome/browser/ui/gtk/global_menu_bar.h
+++ b/chrome/browser/ui/gtk/global_menu_bar.h
@@ -8,6 +8,7 @@
#include <map>
#include "chrome/browser/command_updater.h"
+#include "chrome/browser/ui/gtk/global_bookmark_menu.h"
#include "chrome/browser/ui/gtk/global_history_menu.h"
#include "chrome/browser/ui/gtk/owned_widget_gtk.h"
#include "content/common/notification_observer.h"
@@ -36,6 +37,7 @@ class GlobalMenuBar : public CommandUpdater::CommandObserver,
static const int TAG_RECENTLY_CLOSED = 2;
static const int TAG_MOST_VISITED_HEADER = 3;
static const int TAG_RECENTLY_CLOSED_HEADER = 4;
+ static const int TAG_BOOKMARK_CLEARABLE = 5;
explicit GlobalMenuBar(Browser* browser);
virtual ~GlobalMenuBar();
@@ -79,6 +81,9 @@ class GlobalMenuBar : public CommandUpdater::CommandObserver,
// history menu fresh.
GlobalHistoryMenu history_menu_;
+ // Listens to the bookmark model and updates the menu.
+ GlobalBookmarkMenu bookmark_menu_;
+
// For some menu items, we want to show the accelerator, but not actually
// explicitly handle it. To this end we connect those menu items' accelerators
// to this group, but don't attach this group to any top level window.
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 26895f9..513059e 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2654,6 +2654,8 @@
'browser/ui/gtk/fullscreen_exit_bubble_gtk.h',
'browser/ui/gtk/gconf_titlebar_listener.cc',
'browser/ui/gtk/gconf_titlebar_listener.h',
+ 'browser/ui/gtk/global_bookmark_menu.cc',
+ 'browser/ui/gtk/global_bookmark_menu.h',
'browser/ui/gtk/global_history_menu.cc',
'browser/ui/gtk/global_history_menu.h',
'browser/ui/gtk/global_menu_bar.cc',