diff options
Diffstat (limited to 'chrome/browser/ui/gtk')
-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 |
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|. |