summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/gtk
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/gtk')
-rw-r--r--chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc31
-rw-r--r--chrome/browser/ui/gtk/browser_window_gtk.cc2
-rw-r--r--chrome/browser/ui/gtk/global_history_menu.cc462
-rw-r--r--chrome/browser/ui/gtk/global_history_menu.h111
-rw-r--r--chrome/browser/ui/gtk/global_menu_bar.cc144
-rw-r--r--chrome/browser/ui/gtk/global_menu_bar.h40
-rw-r--r--chrome/browser/ui/gtk/gtk_util.cc33
-rw-r--r--chrome/browser/ui/gtk/gtk_util.h4
8 files changed, 749 insertions, 78 deletions
diff --git a/chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc b/chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc
index 961e7f1..07d4a4a 100644
--- a/chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc
+++ b/chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc
@@ -34,10 +34,6 @@ const int kBitsInAByte = 8;
// Maximum number of characters on a bookmark button.
const size_t kMaxCharsOnAButton = 15;
-// Max size of each component of the button tooltips.
-const size_t kMaxTooltipTitleLength = 100;
-const size_t kMaxTooltipURLLength = 400;
-
// Padding between the chrome button highlight border and the contents (favicon,
// text).
const int kButtonPaddingTop = 0;
@@ -245,32 +241,7 @@ std::string BuildTooltipFor(const BookmarkNode* node) {
if (node->is_folder())
return std::string();
- const std::string& url = node->GetURL().possibly_invalid_spec();
- const std::string& title = UTF16ToUTF8(node->GetTitle());
-
- std::string truncated_url = UTF16ToUTF8(l10n_util::TruncateString(
- UTF8ToUTF16(url), kMaxTooltipURLLength));
- gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(),
- truncated_url.size());
- std::string escaped_url(escaped_url_cstr);
- g_free(escaped_url_cstr);
-
- std::string tooltip;
- if (url == title || title.empty()) {
- return escaped_url;
- } else {
- std::string truncated_title = UTF16ToUTF8(l10n_util::TruncateString(
- node->GetTitle(), kMaxTooltipTitleLength));
- gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(),
- truncated_title.size());
- std::string escaped_title(escaped_title_cstr);
- g_free(escaped_title_cstr);
-
- if (!escaped_url.empty())
- return std::string("<b>") + escaped_title + "</b>\n" + escaped_url;
- else
- return std::string("<b>") + escaped_title + "</b>";
- }
+ return gtk_util::BuildTooltipTitleFor(node->GetTitle(), node->GetURL());
}
const BookmarkNode* BookmarkNodeForWidget(GtkWidget* widget) {
diff --git a/chrome/browser/ui/gtk/browser_window_gtk.cc b/chrome/browser/ui/gtk/browser_window_gtk.cc
index 865bb5f..c744616 100644
--- a/chrome/browser/ui/gtk/browser_window_gtk.cc
+++ b/chrome/browser/ui/gtk/browser_window_gtk.cc
@@ -1594,7 +1594,7 @@ void BrowserWindowGtk::InitWidgets() {
// GtkMenuBar it sees into the global menu bar. (It doesn't seem to check the
// visibility of the GtkMenuBar, so we can just permanently hide it.)
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGlobalGnomeMenu)) {
- global_menu_bar_.reset(new GlobalMenuBar(browser_.get(), this));
+ global_menu_bar_.reset(new GlobalMenuBar(browser_.get()));
gtk_container_add(GTK_CONTAINER(window_vbox_), global_menu_bar_->widget());
}
diff --git a/chrome/browser/ui/gtk/global_history_menu.cc b/chrome/browser/ui/gtk/global_history_menu.cc
new file mode 100644
index 0000000..83c861c
--- /dev/null
+++ b/chrome/browser/ui/gtk/global_history_menu.cc
@@ -0,0 +1,462 @@
+// 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_history_menu.h"
+
+#include <gtk/gtk.h>
+
+#include "base/stl_util-inl.h"
+#include "base/utf_string_conversions.h"
+#include "base/string_number_conversions.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_tab_restore_service_delegate.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 "chrome/browser/ui/gtk/owned_widget_gtk.h"
+#include "chrome/common/url_constants.h"
+#include "content/common/notification_service.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/gtk_util.h"
+
+namespace {
+
+// The number of recently closed items to get.
+const unsigned int kRecentlyClosedCount = 10;
+
+// Menus more than this many chars long will get trimmed.
+const int kMaximumMenuWidthInChars = 50;
+
+} // namespace
+
+struct GlobalHistoryMenu::ClearMenuClosure {
+ GtkWidget* container;
+ GlobalHistoryMenu* menu_bar;
+ int tag;
+};
+
+struct GlobalHistoryMenu::GetIndexClosure {
+ bool found;
+ int current_index;
+ int tag;
+};
+
+class GlobalHistoryMenu::HistoryItem {
+ public:
+ HistoryItem()
+ : icon_requested(false),
+ menu_item(NULL),
+ session_id(0) {}
+
+ // The title for the menu item.
+ string16 title;
+ // The URL that will be navigated to if the user selects this item.
+ GURL url;
+
+ // If the icon is being requested from the FaviconService, |icon_requested|
+ // will be true and |icon_handle| will be non-NULL. If this is false, then
+ // |icon_handle| will be NULL.
+ bool icon_requested;
+ // The Handle given to us by the FaviconService for the icon fetch request.
+ FaviconService::Handle icon_handle;
+
+ // The icon as a GtkImage for inclusion in a GtkImageMenuItem.
+ OwnedWidgetGtk icon_image;
+
+ // A pointer to the menu_item. This is a weak reference in the GTK+ version
+ // because the GtkMenu must sink the reference.
+ GtkWidget* menu_item;
+
+ // This ID is unique for a browser session and can be passed to the
+ // TabRestoreService to re-open the closed window or tab that this
+ // references. A non-0 session ID indicates that this is an entry can be
+ // restored that way. Otherwise, the URL will be used to open the item and
+ // this ID will be 0.
+ SessionID::id_type session_id;
+
+ // If the HistoryItem is a window, this will be the vector of tabs. Note
+ // that this is a list of weak references. The |menu_item_map_| is the owner
+ // of all items. If it is not a window, then the entry is a single page and
+ // the vector will be empty.
+ std::vector<HistoryItem*> tabs;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HistoryItem);
+};
+
+GlobalHistoryMenu::GlobalHistoryMenu(Browser* browser)
+ : browser_(browser),
+ profile_(browser_->profile()),
+ default_favicon_(NULL),
+ tab_restore_service_(NULL) {
+}
+
+GlobalHistoryMenu::~GlobalHistoryMenu() {
+ if (tab_restore_service_)
+ tab_restore_service_->RemoveObserver(this);
+
+ STLDeleteContainerPairSecondPointers(menu_item_history_map_.begin(),
+ menu_item_history_map_.end());
+ menu_item_history_map_.clear();
+}
+
+void GlobalHistoryMenu::Init(GtkWidget* history_menu) {
+ history_menu_ = history_menu;
+
+ default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
+
+ if (profile_) {
+ tab_restore_service_ = profile_->GetTabRestoreService();
+ if (tab_restore_service_) {
+ tab_restore_service_->LoadTabsFromLastSession();
+ tab_restore_service_->AddObserver(this);
+
+ // If LoadTabsFromLastSession doesn't load tabs, it won't call
+ // TabRestoreServiceChanged(). This ensures that all new windows after
+ // the first one will have their menus populated correctly.
+ TabRestoreServiceChanged(tab_restore_service_);
+ }
+
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ Source<Profile>(profile_));
+ }
+}
+
+GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForMenuItem(
+ GtkWidget* menu_item) {
+ MenuItemToHistoryMap::iterator it = menu_item_history_map_.find(menu_item);
+ return it != menu_item_history_map_.end() ? it->second : NULL;
+}
+
+bool GlobalHistoryMenu::HasValidHistoryItemForTab(
+ const TabRestoreService::Tab& entry) {
+ if (entry.navigations.empty())
+ return false;
+
+ const TabNavigation& current_navigation =
+ entry.navigations.at(entry.current_navigation_index);
+ if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
+ return false;
+
+ return true;
+}
+
+GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForTab(
+ const TabRestoreService::Tab& entry) {
+ if (!HasValidHistoryItemForTab(entry))
+ return NULL;
+
+ const TabNavigation& current_navigation =
+ entry.navigations.at(entry.current_navigation_index);
+ HistoryItem* item = new HistoryItem();
+ item->title = current_navigation.title();
+ item->url = current_navigation.virtual_url();
+ item->session_id = entry.id;
+
+ // Tab navigations don't come with icons, so we always have to request them.
+ GetFaviconForHistoryItem(item);
+
+ return item;
+}
+
+GtkWidget* GlobalHistoryMenu::AddHistoryItemToMenu(HistoryItem* item,
+ GtkWidget* menu,
+ int tag,
+ int index) {
+ string16 title = item->title;
+ std::string url_string = item->url.possibly_invalid_spec();
+
+ if (title.empty())
+ title = UTF8ToUTF16(url_string);
+ ui::ElideString(title, kMaximumMenuWidthInChars, &title);
+
+ GtkWidget* menu_item = gtk_image_menu_item_new_with_label(
+ UTF16ToUTF8(title).c_str());
+ gtk_util::SetAlwaysShowImage(menu_item);
+
+ item->menu_item = menu_item;
+ gtk_widget_show(menu_item);
+ g_object_set_data(G_OBJECT(menu_item), "type-tag", GINT_TO_POINTER(tag));
+ g_signal_connect(menu_item, "activate",
+ G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
+ if (item->icon_image.get()) {
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ item->icon_image.get());
+ } else if (!item->tabs.size()) {
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
+ gtk_image_new_from_pixbuf(default_favicon_));
+ }
+
+ std::string tooltip = gtk_util::BuildTooltipTitleFor(item->title, item->url);
+ gtk_widget_set_tooltip_markup(menu_item, tooltip.c_str());
+
+ menu_item_history_map_.insert(std::make_pair(menu_item, item));
+ gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, index);
+
+ return menu_item;
+}
+
+void GlobalHistoryMenu::GetFaviconForHistoryItem(HistoryItem* item) {
+ FaviconService* service =
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
+ FaviconService::Handle handle = service->GetFaviconForURL(
+ item->url,
+ history::FAVICON,
+ &favicon_consumer_,
+ NewCallback(this, &GlobalHistoryMenu::GotFaviconData));
+ favicon_consumer_.SetClientData(service, handle, item);
+ item->icon_handle = handle;
+ item->icon_requested = true;
+}
+
+void GlobalHistoryMenu::GotFaviconData(FaviconService::Handle handle,
+ history::FaviconData favicon) {
+ HistoryItem* item =
+ favicon_consumer_.GetClientData(
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
+ DCHECK(item);
+ item->icon_requested = false;
+ item->icon_handle = NULL;
+
+ SkBitmap icon;
+ if (favicon.is_valid() &&
+ gfx::PNGCodec::Decode(favicon.image_data->front(),
+ favicon.image_data->size(), &icon)) {
+ GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
+ if (pixbuf) {
+ item->icon_image.Own(gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+
+ if (item->menu_item) {
+ gtk_image_menu_item_set_image(
+ GTK_IMAGE_MENU_ITEM(item->menu_item),
+ item->icon_image.get());
+ }
+ }
+ }
+}
+
+void GlobalHistoryMenu::CancelFaviconRequest(HistoryItem* item) {
+ DCHECK(item);
+ if (item->icon_requested) {
+ FaviconService* service =
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
+ service->CancelRequest(item->icon_handle);
+ item->icon_requested = false;
+ item->icon_handle = NULL;
+ }
+}
+
+int GlobalHistoryMenu::GetIndexOfMenuItemWithTag(GtkWidget* menu, int tag_id) {
+ GetIndexClosure closure;
+ closure.found = false;
+ closure.current_index = 0;
+ closure.tag = tag_id;
+
+ gtk_container_foreach(
+ GTK_CONTAINER(menu),
+ reinterpret_cast<void (*)(GtkWidget*, void*)>(GetIndexCallback),
+ &closure);
+
+ return closure.current_index;
+}
+
+void GlobalHistoryMenu::ClearMenuSection(GtkWidget* menu, int tag) {
+ ClearMenuClosure closure;
+ closure.container = menu;
+ closure.menu_bar = this;
+ closure.tag = tag;
+
+ gtk_container_foreach(
+ GTK_CONTAINER(menu),
+ reinterpret_cast<void (*)(GtkWidget*, void*)>(ClearMenuCallback),
+ &closure);
+}
+
+// static
+void GlobalHistoryMenu::GetIndexCallback(GtkWidget* menu_item,
+ GetIndexClosure* closure) {
+ int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
+ if (tag == closure->tag)
+ closure->found = true;
+
+ if (!closure->found)
+ closure->current_index++;
+}
+
+// static
+void GlobalHistoryMenu::ClearMenuCallback(GtkWidget* menu_item,
+ ClearMenuClosure* closure) {
+ DCHECK_NE(closure->tag, 0);
+
+ int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
+ if (closure->tag == tag) {
+ HistoryItem* item = closure->menu_bar->HistoryItemForMenuItem(menu_item);
+
+ if (item) {
+ closure->menu_bar->CancelFaviconRequest(item);
+ closure->menu_bar->menu_item_history_map_.erase(menu_item);
+ delete item;
+ }
+
+ GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
+ if (submenu)
+ closure->menu_bar->ClearMenuSection(submenu, closure->tag);
+
+ gtk_container_remove(GTK_CONTAINER(closure->container), menu_item);
+ }
+}
+
+void GlobalHistoryMenu::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type.value == NotificationType::BROWSER_THEME_CHANGED);
+
+ // Keeping track of which menu items have the default icon is going an
+ // error-prone pain, so instead just store the new default favicon and
+ // we'll update on the next menu change event.
+ default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
+}
+
+void GlobalHistoryMenu::TabRestoreServiceChanged(TabRestoreService* service) {
+ const TabRestoreService::Entries& entries = service->entries();
+
+ ClearMenuSection(history_menu_, GlobalMenuBar::TAG_RECENTLY_CLOSED);
+
+ // We'll get the index the "Recently Closed" header. (This can vary depending
+ // on the number of "Most Visited" items.
+ int index = GetIndexOfMenuItemWithTag(
+ history_menu_,
+ GlobalMenuBar::TAG_RECENTLY_CLOSED_HEADER) + 1;
+
+ unsigned int added_count = 0;
+ for (TabRestoreService::Entries::const_iterator it = entries.begin();
+ it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
+ TabRestoreService::Entry* entry = *it;
+
+ if (entry->type == TabRestoreService::WINDOW) {
+ TabRestoreService::Window* entry_win =
+ static_cast<TabRestoreService::Window*>(entry);
+ std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
+ if (tabs.empty())
+ continue;
+
+ // Check that this window has valid content. Sometimes it is possible for
+ // there to not be any subitems for a given window; if that is the case,
+ // do not add the entry to the main menu.
+ int valid_tab_count = 0;
+ std::vector<TabRestoreService::Tab>::const_iterator it;
+ for (it = tabs.begin(); it != tabs.end(); ++it) {
+ if (HasValidHistoryItemForTab(*it))
+ valid_tab_count++;
+ }
+ if (valid_tab_count == 0)
+ continue;
+
+ // Create the item for the parent/window. Do not set the title yet
+ // because the actual number of items that are in the menu will not be
+ // known until things like the NTP are filtered out, which is done when
+ // the tab items are actually created.
+ HistoryItem* item = new HistoryItem();
+ item->session_id = entry_win->id;
+
+ GtkWidget* submenu = gtk_menu_new();
+
+ GtkWidget* restore_item = gtk_menu_item_new_with_label(
+ l10n_util::GetStringUTF8(
+ IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str());
+ g_object_set_data(G_OBJECT(restore_item), "type-tag",
+ GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
+ g_signal_connect(restore_item, "activate",
+ G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
+ gtk_widget_show(restore_item);
+
+ // The mac version of this code allows the user to click on the parent
+ // menu item to have the same effect as clicking the restore window
+ // submenu item. GTK+ helpfully activates a menu item when it shows a
+ // submenu so toss that feature out.
+ menu_item_history_map_.insert(std::make_pair(restore_item, item));
+ gtk_menu_shell_append(GTK_MENU_SHELL(submenu), restore_item);
+
+ GtkWidget* separator = gtk_separator_menu_item_new();
+ gtk_widget_show(separator);
+ gtk_menu_shell_append(GTK_MENU_SHELL(submenu), separator);
+
+ // Loop over the window's tabs and add them to the submenu.
+ int subindex = 2;
+ for (it = tabs.begin(); it != tabs.end(); ++it) {
+ TabRestoreService::Tab tab = *it;
+ HistoryItem* tab_item = HistoryItemForTab(tab);
+ if (tab_item) {
+ item->tabs.push_back(tab_item);
+ AddHistoryItemToMenu(tab_item,
+ submenu,
+ GlobalMenuBar::TAG_RECENTLY_CLOSED,
+ subindex++);
+ }
+ }
+
+ // Now that the number of tabs that has been added is known, set the
+ // title of the parent menu item.
+ std::string title =
+ (item->tabs.size() == 1) ?
+ l10n_util::GetStringUTF8(
+ IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
+ l10n_util::GetStringFUTF8(
+ IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
+ base::IntToString16(item->tabs.size()));
+
+ // Create the menu item parent. Unlike mac, it's can't be activated.
+ GtkWidget* parent_item = gtk_image_menu_item_new_with_label(
+ title.c_str());
+ gtk_widget_show(parent_item);
+ g_object_set_data(G_OBJECT(parent_item), "type-tag",
+ GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), submenu);
+
+ gtk_menu_shell_insert(GTK_MENU_SHELL(history_menu_), parent_item,
+ index++);
+ ++added_count;
+ } else if (entry->type == TabRestoreService::TAB) {
+ TabRestoreService::Tab* tab =
+ static_cast<TabRestoreService::Tab*>(entry);
+ HistoryItem* item = HistoryItemForTab(*tab);
+ if (item) {
+ AddHistoryItemToMenu(item,
+ history_menu_,
+ GlobalMenuBar::TAG_RECENTLY_CLOSED,
+ index++);
+ ++added_count;
+ }
+ }
+ }
+}
+
+void GlobalHistoryMenu::TabRestoreServiceDestroyed(
+ TabRestoreService* service) {
+ tab_restore_service_ = NULL;
+}
+
+void GlobalHistoryMenu::OnRecentlyClosedItemActivated(GtkWidget* sender) {
+ WindowOpenDisposition disposition =
+ gtk_util::DispositionForCurrentButtonPressEvent();
+ HistoryItem* item = HistoryItemForMenuItem(sender);
+
+ // If this item can be restored using TabRestoreService, do so. Otherwise,
+ // just load the URL.
+ TabRestoreService* service = browser_->profile()->GetTabRestoreService();
+ if (item->session_id && service) {
+ service->RestoreEntryById(browser_->tab_restore_service_delegate(),
+ item->session_id, false);
+ } else {
+ DCHECK(item->url.is_valid());
+ browser_->OpenURL(item->url, GURL(), disposition,
+ PageTransition::AUTO_BOOKMARK);
+ }
+}
diff --git a/chrome/browser/ui/gtk/global_history_menu.h b/chrome/browser/ui/gtk/global_history_menu.h
new file mode 100644
index 0000000..fff3c99
--- /dev/null
+++ b/chrome/browser/ui/gtk/global_history_menu.h
@@ -0,0 +1,111 @@
+// 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_HISTORY_MENU_H_
+#define CHROME_BROWSER_UI_GTK_GLOBAL_HISTORY_MENU_H_
+
+#include <map>
+
+#include "content/browser/cancelable_request.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/sessions/tab_restore_service.h"
+#include "chrome/browser/sessions/tab_restore_service_observer.h"
+#include "content/common/notification_observer.h"
+#include "content/common/notification_registrar.h"
+#include "ui/base/gtk/gtk_signal.h"
+
+class Browser;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+// Controls the History menu.
+class GlobalHistoryMenu : public NotificationObserver,
+ public TabRestoreServiceObserver {
+ public:
+ explicit GlobalHistoryMenu(Browser* browser);
+ virtual ~GlobalHistoryMenu();
+
+ // Takes the history menu we need to modify based on the tab restore/most
+ // visited state.
+ void Init(GtkWidget* history_menu);
+
+ private:
+ class HistoryItem;
+ struct ClearMenuClosure;
+ struct GetIndexClosure;
+
+ typedef std::map<GtkWidget*, HistoryItem*> MenuItemToHistoryMap;
+
+ // Returns the currently existing HistoryItem associated with
+ // |menu_item|. Can return NULL.
+ HistoryItem* HistoryItemForMenuItem(GtkWidget* menu_item);
+
+ // Returns whether there's a valid HistoryItem representation of |entry|.
+ bool HasValidHistoryItemForTab(const TabRestoreService::Tab& entry);
+
+ // Creates a HistoryItem from the data in |entry|.
+ HistoryItem* HistoryItemForTab(const TabRestoreService::Tab& entry);
+
+ // Creates a menu item form |item| and inserts it in |menu| at |index|.
+ GtkWidget* AddHistoryItemToMenu(HistoryItem* item,
+ GtkWidget* menu,
+ int tag,
+ int index);
+
+ // Requests a FavIcon; we'll receive the data in the future through the
+ // GotFaviconData() callback.
+ void GetFaviconForHistoryItem(HistoryItem* item);
+
+ // Callback for GetFaviconForHistoryItem().
+ void GotFaviconData(FaviconService::Handle handle,
+ history::FaviconData favicon);
+
+ // Cancels an outstanding favicon request.
+ void CancelFaviconRequest(HistoryItem* item);
+
+ // Find the first index of the item in |menu| with the tag |tag_id|.
+ int GetIndexOfMenuItemWithTag(GtkWidget* menu, int tag_id);
+
+ // This will remove all menu items in |menu| with |tag| as their tag. This
+ // clears state about HistoryItems* that we keep to prevent that data from
+ // going stale. That's why this method recurses into its child menus.
+ void ClearMenuSection(GtkWidget* menu, int tag);
+
+ // Implementation detail of GetIndexOfMenuItemWithTag.
+ static void GetIndexCallback(GtkWidget* widget, GetIndexClosure* closure);
+
+ // Implementation detail of ClearMenuSection.
+ static void ClearMenuCallback(GtkWidget* widget, ClearMenuClosure* closure);
+
+ // NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // For TabRestoreServiceObserver
+ virtual void TabRestoreServiceChanged(TabRestoreService* service);
+ virtual void TabRestoreServiceDestroyed(TabRestoreService* service);
+
+ CHROMEGTK_CALLBACK_0(GlobalHistoryMenu, void, OnRecentlyClosedItemActivated);
+
+ Browser* browser_;
+ Profile* profile_;
+
+ NotificationRegistrar registrar_;
+
+ GdkPixbuf* default_favicon_;
+
+ // The history menu. We keep this since we need to rewrite parts of it
+ // periodically.
+ GtkWidget* history_menu_;
+
+ TabRestoreService* tab_restore_service_; // weak
+
+ // A mapping from GtkMenuItems to HistoryItems that maintain data.
+ MenuItemToHistoryMap menu_item_history_map_;
+
+ // Maps HistoryItems to favicon request Handles.
+ CancelableRequestConsumerTSimple<HistoryItem*> favicon_consumer_;
+};
+
+#endif // CHROME_BROWSER_UI_GTK_GLOBAL_HISTORY_MENU_H_
diff --git a/chrome/browser/ui/gtk/global_menu_bar.cc b/chrome/browser/ui/gtk/global_menu_bar.cc
index 6442e41..368faaa 100644
--- a/chrome/browser/ui/gtk/global_menu_bar.cc
+++ b/chrome/browser/ui/gtk/global_menu_bar.cc
@@ -11,6 +11,8 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/gtk/accelerators_gtk.h"
+#include "chrome/browser/ui/gtk/gtk_util.h"
+#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_service.h"
#include "grit/generated_resources.h"
@@ -20,12 +22,14 @@
struct GlobalMenuBarCommand {
int str_id;
int command;
+ int tag;
};
namespace {
const int MENU_SEPARATOR =-1;
const int MENU_END = -2;
+const int MENU_DISABLED_LABEL = -3;
GlobalMenuBarCommand file_menu[] = {
{ IDS_NEW_TAB, IDC_NEW_TAB },
@@ -99,6 +103,47 @@ GlobalMenuBarCommand view_menu[] = {
{ MENU_END, MENU_END }
};
+GlobalMenuBarCommand history_menu[] = {
+ { IDS_HISTORY_HOME_LINUX, IDC_HOME },
+ { IDS_HISTORY_BACK_LINUX, IDC_BACK },
+ { IDS_HISTORY_FORWARD_LINUX, IDC_FORWARD },
+
+ { MENU_SEPARATOR, MENU_SEPARATOR },
+
+ { IDS_HISTORY_VISITED_LINUX, MENU_DISABLED_LABEL,
+ GlobalMenuBar::TAG_MOST_VISITED_HEADER },
+
+ { MENU_SEPARATOR, MENU_SEPARATOR },
+
+ { IDS_HISTORY_CLOSED_LINUX, MENU_DISABLED_LABEL,
+ GlobalMenuBar::TAG_RECENTLY_CLOSED_HEADER },
+
+ { MENU_SEPARATOR, MENU_SEPARATOR },
+
+ { IDS_SHOWFULLHISTORY_LINK, IDC_SHOW_HISTORY },
+
+ { MENU_END, MENU_END }
+};
+
+GlobalMenuBarCommand bookmark_menu[] = {
+ { IDS_BOOKMARK_MANAGER, IDC_SHOW_BOOKMARK_MANAGER },
+ { 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 }
+};
+
GlobalMenuBarCommand tools_menu[] = {
{ IDS_SHOW_DOWNLOADS, IDC_SHOW_DOWNLOADS },
{ IDS_SHOW_HISTORY, IDC_SHOW_HISTORY },
@@ -126,28 +171,31 @@ GlobalMenuBarCommand help_menu[] = {
} // namespace
-GlobalMenuBar::GlobalMenuBar(Browser* browser,
- BrowserWindowGtk* window)
+GlobalMenuBar::GlobalMenuBar(Browser* browser)
: browser_(browser),
- browser_window_(window),
+ profile_(browser_->profile()),
menu_bar_(gtk_menu_bar_new()),
+ history_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
// instead remain in our widget hierarchy simply to be noticed by third party
// components.
- gtk_widget_set_no_show_all(menu_bar_, TRUE);
+ gtk_widget_set_no_show_all(menu_bar_.get(), TRUE);
// Set a nice name so it shows up in gtkparasite and others.
- gtk_widget_set_name(menu_bar_, "chrome-hidden-global-menubar");
+ gtk_widget_set_name(menu_bar_.get(), "chrome-hidden-global-menubar");
BuildGtkMenuFrom(IDS_FILE_MENU_LINUX, &id_to_menu_item_, file_menu);
BuildGtkMenuFrom(IDS_EDIT_MENU_LINUX, &id_to_menu_item_, edit_menu);
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);
BuildGtkMenuFrom(IDS_TOOLS_MENU_LINUX, &id_to_menu_item_, tools_menu);
BuildGtkMenuFrom(IDS_HELP_MENU_LINUX, &id_to_menu_item_, help_menu);
- for (IDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
+ for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
it != id_to_menu_item_.end(); ++it) {
// Get the starting enabled state.
gtk_widget_set_sensitive(
@@ -179,7 +227,7 @@ GlobalMenuBar::GlobalMenuBar(Browser* browser,
}
GlobalMenuBar::~GlobalMenuBar() {
- for (IDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
+ for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
it != id_to_menu_item_.end(); ++it) {
browser_->command_updater()->RemoveCommandObserver(it->first, this);
}
@@ -187,32 +235,15 @@ GlobalMenuBar::~GlobalMenuBar() {
g_object_unref(dummy_accel_group_);
}
-void GlobalMenuBar::BuildGtkMenuFrom(int menu_str_id,
- std::map<int, GtkWidget*>* id_to_menu_item,
- GlobalMenuBarCommand* commands) {
+GtkWidget* GlobalMenuBar::BuildGtkMenuFrom(
+ int menu_str_id,
+ std::map<int, GtkWidget*>* id_to_menu_item,
+ GlobalMenuBarCommand* commands) {
GtkWidget* menu = gtk_menu_new();
for (int i = 0; commands[i].str_id != MENU_END; ++i) {
- GtkWidget* menu_item = NULL;
- if (commands[i].str_id == MENU_SEPARATOR) {
- menu_item = gtk_separator_menu_item_new();
- } else {
- int command_id = commands[i].command;
- std::string label =
- gfx::ConvertAcceleratorsFromWindowsStyle(
- l10n_util::GetStringUTF8(commands[i].str_id));
-
- if (command_id == IDC_SHOW_BOOKMARK_BAR)
- menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
- else
- menu_item = gtk_menu_item_new_with_mnemonic(label.c_str());
-
- id_to_menu_item->insert(std::make_pair(command_id, menu_item));
- g_object_set_data(G_OBJECT(menu_item), "command-id",
- GINT_TO_POINTER(command_id));
- g_signal_connect(menu_item, "activate",
- G_CALLBACK(OnItemActivatedThunk), this);
- }
- gtk_widget_show(menu_item);
+ GtkWidget* menu_item = BuildMenuItem(
+ commands[i].str_id, commands[i].command, commands[i].tag,
+ id_to_menu_item, menu);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
}
@@ -223,12 +254,51 @@ void GlobalMenuBar::BuildGtkMenuFrom(int menu_str_id,
l10n_util::GetStringUTF8(menu_str_id)).c_str());
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu);
gtk_widget_show(menu_item);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar_.get()), menu_item);
+
+ return menu;
+}
- gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar_), menu_item);
+GtkWidget* GlobalMenuBar::BuildMenuItem(
+ int string_id,
+ int command_id,
+ int tag_id,
+ std::map<int, GtkWidget*>* id_to_menu_item,
+ GtkWidget* menu_to_add_to) {
+ GtkWidget* menu_item = NULL;
+ if (string_id == MENU_SEPARATOR) {
+ menu_item = gtk_separator_menu_item_new();
+ } else {
+ std::string label =
+ gfx::ConvertAcceleratorsFromWindowsStyle(
+ l10n_util::GetStringUTF8(string_id));
+
+ if (command_id == IDC_SHOW_BOOKMARK_BAR)
+ menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
+ else
+ menu_item = gtk_menu_item_new_with_mnemonic(label.c_str());
+
+ if (tag_id) {
+ g_object_set_data(G_OBJECT(menu_item), "type-tag",
+ GINT_TO_POINTER(tag_id));
+ }
+
+ if (command_id == MENU_DISABLED_LABEL) {
+ gtk_widget_set_sensitive(menu_item, FALSE);
+ } else {
+ id_to_menu_item->insert(std::make_pair(command_id, menu_item));
+ g_object_set_data(G_OBJECT(menu_item), "command-id",
+ GINT_TO_POINTER(command_id));
+ g_signal_connect(menu_item, "activate",
+ G_CALLBACK(OnItemActivatedThunk), this);
+ }
+ }
+ gtk_widget_show(menu_item);
+ return menu_item;
}
void GlobalMenuBar::EnabledStateChangedForCommand(int id, bool enabled) {
- IDMenuItemMap::iterator it = id_to_menu_item_.find(id);
+ CommandIDMenuItemMap::iterator it = id_to_menu_item_.find(id);
if (it != id_to_menu_item_.end())
gtk_widget_set_sensitive(it->second, enabled);
}
@@ -238,13 +308,15 @@ void GlobalMenuBar::Observe(NotificationType type,
const NotificationDetails& details) {
DCHECK(type.value == NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED);
- IDMenuItemMap::iterator it = id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR);
+ CommandIDMenuItemMap::iterator it =
+ id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR);
if (it != id_to_menu_item_.end()) {
PrefService* prefs = browser_->profile()->GetPrefs();
block_activation_ = true;
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(it->second),
- prefs->GetBoolean(prefs::kShowBookmarkBar));
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(it->second),
+ prefs->GetBoolean(prefs::kShowBookmarkBar));
block_activation_ = false;
}
}
diff --git a/chrome/browser/ui/gtk/global_menu_bar.h b/chrome/browser/ui/gtk/global_menu_bar.h
index 5ff88e7..0497a64 100644
--- a/chrome/browser/ui/gtk/global_menu_bar.h
+++ b/chrome/browser/ui/gtk/global_menu_bar.h
@@ -8,12 +8,13 @@
#include <map>
#include "chrome/browser/command_updater.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"
#include "content/common/notification_registrar.h"
#include "ui/base/gtk/gtk_signal.h"
class Browser;
-class BrowserWindowGtk;
struct GlobalMenuBarCommand;
typedef struct _GtkAccelGroup GtkAccelGroup;
@@ -30,18 +31,31 @@ typedef struct _GtkWidget GtkWidget;
class GlobalMenuBar : public CommandUpdater::CommandObserver,
public NotificationObserver {
public:
- GlobalMenuBar(Browser* browser, BrowserWindowGtk* window);
+ static const int TAG_NORMAL = 0;
+ static const int TAG_MOST_VISITED = 1;
+ static const int TAG_RECENTLY_CLOSED = 2;
+ static const int TAG_MOST_VISITED_HEADER = 3;
+ static const int TAG_RECENTLY_CLOSED_HEADER = 4;
+
+ explicit GlobalMenuBar(Browser* browser);
virtual ~GlobalMenuBar();
- GtkWidget* widget() { return menu_bar_; }
+ GtkWidget* widget() { return menu_bar_.get(); }
private:
- typedef std::map<int, GtkWidget*> IDMenuItemMap;
+ typedef std::map<int, GtkWidget*> CommandIDMenuItemMap;
// Helper function that builds the data.
- void BuildGtkMenuFrom(int menu_str_id,
- std::map<int, GtkWidget*>* id_to_menu_item,
- GlobalMenuBarCommand* commands);
+ GtkWidget* BuildGtkMenuFrom(int menu_str_id,
+ std::map<int, GtkWidget*>* id_to_menu_item,
+ GlobalMenuBarCommand* commands);
+
+ // Builds an individual menu item.
+ GtkWidget* BuildMenuItem(int string_id,
+ int command_id,
+ int tag_id,
+ std::map<int, GtkWidget*>* id_to_menu_item,
+ GtkWidget* menu_to_add_to);
// CommandUpdater::CommandObserver:
virtual void EnabledStateChangedForCommand(int id, bool enabled);
@@ -54,12 +68,16 @@ class GlobalMenuBar : public CommandUpdater::CommandObserver,
CHROMEGTK_CALLBACK_0(GlobalMenuBar, void, OnItemActivated);
Browser* browser_;
- BrowserWindowGtk* browser_window_;
+ Profile* profile_;
NotificationRegistrar registrar_;
// Our menu bar widget.
- GtkWidget* menu_bar_;
+ OwnedWidgetGtk menu_bar_;
+
+ // Listens to the TabRestoreService and the HistoryService and keeps the
+ // history menu fresh.
+ GlobalHistoryMenu history_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
@@ -67,8 +85,8 @@ class GlobalMenuBar : public CommandUpdater::CommandObserver,
GtkAccelGroup* dummy_accel_group_;
// A mapping from command ids to GtkMenuItem objects. We use this to update
- // the enable state since we are a .
- IDMenuItemMap id_to_menu_item_;
+ // the command enable state.
+ CommandIDMenuItemMap id_to_menu_item_;
// gtk_check_menu_item_set_active() will call the "activate" signal. We need
// to block this activation while we change the checked state.
diff --git a/chrome/browser/ui/gtk/gtk_util.cc b/chrome/browser/ui/gtk/gtk_util.cc
index 69fcbe5..547caaa 100644
--- a/chrome/browser/ui/gtk/gtk_util.cc
+++ b/chrome/browser/ui/gtk/gtk_util.cc
@@ -65,6 +65,10 @@ static const char* kIconName = "chromium-browser";
const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";
+// Max size of each component of the button tooltips.
+const size_t kMaxTooltipTitleLength = 100;
+const size_t kMaxTooltipURLLength = 400;
+
// Callback used in RemoveAllChildren.
void RemoveWidget(GtkWidget* widget, gpointer container) {
gtk_container_remove(GTK_CONTAINER(container), widget);
@@ -770,6 +774,35 @@ GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) {
return point;
}
+std::string BuildTooltipTitleFor(string16 title, GURL url) {
+ const std::string& url_str = url.possibly_invalid_spec();
+ const std::string& title_str = UTF16ToUTF8(title);
+
+ std::string truncated_url = UTF16ToUTF8(l10n_util::TruncateString(
+ UTF8ToUTF16(url_str), kMaxTooltipURLLength));
+ gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(),
+ truncated_url.size());
+ std::string escaped_url(escaped_url_cstr);
+ g_free(escaped_url_cstr);
+
+ std::string tooltip;
+ if (url_str == title_str || title.empty()) {
+ return escaped_url;
+ } else {
+ std::string truncated_title = UTF16ToUTF8(l10n_util::TruncateString(
+ title, kMaxTooltipTitleLength));
+ gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(),
+ truncated_title.size());
+ std::string escaped_title(escaped_title_cstr);
+ g_free(escaped_title_cstr);
+
+ if (!escaped_url.empty())
+ return std::string("<b>") + escaped_title + "</b>\n" + escaped_url;
+ else
+ return std::string("<b>") + escaped_title + "</b>";
+ }
+}
+
void DrawTextEntryBackground(GtkWidget* offscreen_entry,
GtkWidget* widget_to_draw_on,
GdkRectangle* dirty_rec,
diff --git a/chrome/browser/ui/gtk/gtk_util.h b/chrome/browser/ui/gtk/gtk_util.h
index 74552ec..cad7d26 100644
--- a/chrome/browser/ui/gtk/gtk_util.h
+++ b/chrome/browser/ui/gtk/gtk_util.h
@@ -238,6 +238,10 @@ gfx::Point ClientPoint(GtkWidget* widget);
// shapes.
GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr);
+// Creates a tooltip string to be passed to gtk_widget_set_tooltip_markup from
+// the title and URL.
+std::string BuildTooltipTitleFor(string16 title, GURL url);
+
// Draws a GTK text entry with the style parameters of GtkEntry
// |offscreen_entry| onto |widget_to_draw_on| in the rectangle |rec|. Drawing
// is only done in the clip rectangle |dirty_rec|.