diff options
-rw-r--r-- | chrome/app/generated_resources.grd | 28 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.cc | 31 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/browser_window_gtk.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_history_menu.cc | 462 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_history_menu.h | 111 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_menu_bar.cc | 144 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/global_menu_bar.h | 40 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/gtk_util.cc | 33 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/gtk_util.h | 4 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
10 files changed, 778 insertions, 79 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index ce78bfd..f9a495b 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -9373,7 +9373,6 @@ Keep your key file in a safe place. You will need it to create new versions of y additional ones with different formatting. --> <!-- Menubar Menu Titles --> - <!-- NOTE: Some of these exist in context menus with Title Case support, but we use different IDs in case we need slightly different strings in some language due to the different context. --> <message name="IDS_FILE_MENU_LINUX" desc="The menu title of the Linux file menu."> &File </message> @@ -9430,6 +9429,33 @@ Keep your key file in a safe place. You will need it to create new versions of y Zoom &Out </message> + <!-- History menu --> + <message name="IDS_HISTORY_HOME_LINUX" desc="The Linux menu item for home in the history menu."> + Home + </message> + <message name="IDS_HISTORY_BACK_LINUX" desc="The Linux menu item for back in the history menu."> + &Back + </message> + <message name="IDS_HISTORY_FORWARD_LINUX" desc="The Linux menu item for forward in the history menu."> + &Forward + </message> + <message name="IDS_HISTORY_VISITED_LINUX" desc="The Linux menu item for the header of most visited items in the history menu."> + Most Visited + </message> + <message name="IDS_HISTORY_CLOSED_LINUX" desc="The Linux menu item for the header of recently closed items in the history menu."> + Recently Closed + </message> + <message name="IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX" desc="The Linux menu item for restoring all the tabs of a recently closed window."> + Restore All Tabs + </message> + + <!-- Bookmarks menu --> + <message name="IDS_BOOKMARK_CURRENT_PAGE_LINUX" desc="The Linux menu item for bookmarking the current page in the bookmark menu."> + Bookmark This Page... + </message> + <message name="IDS_BOOKMARK_ALL_TABS_LINUX" desc="The Linux menu item for bookmarking all tabs in the current window."> + Bookmark All Tabs... + </message> </if> <!-- File chooser dialog default titles (only used on Linux) --> 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|. diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index bf36718..2a0029f 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2640,6 +2640,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_history_menu.cc', + 'browser/ui/gtk/global_history_menu.h', 'browser/ui/gtk/global_menu_bar.cc', 'browser/ui/gtk/global_menu_bar.h', 'browser/ui/gtk/gtk_chrome_button.cc', |