diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-10 21:35:52 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-10 21:35:52 +0000 |
commit | 300647558310e1d8a33129e1446318e0baea48b8 (patch) | |
tree | 85fc937d9bf2257ecd166c265399f993cae1d543 | |
parent | eaaa13e1f3a1df738f059d0cd59266b6c45e819c (diff) | |
download | chromium_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.cc | 260 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_bookmark_menu.h | 125 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_menu_bar.cc | 15 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_menu_bar.h | 5 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
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', |