Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Unified Diff: chrome/browser/ui/gtk/global_history_menu.cc

Issue 6840068: GTK: Add Recently Closed tabs to the History menu in the global menu bar. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove current most visited section. will redo with TopSites in a different patch. Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/ui/gtk/global_history_menu.h ('k') | chrome/browser/ui/gtk/global_menu_bar.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/ui/gtk/global_history_menu.cc
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 0000000000000000000000000000000000000000..83c861c3d3cffaa69083d6032abd10e23b405def
--- /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);
+ }
+}
« no previous file with comments | « chrome/browser/ui/gtk/global_history_menu.h ('k') | chrome/browser/ui/gtk/global_menu_bar.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698