| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/ui/gtk/global_history_menu.h" | |
| 6 | |
| 7 #include <gtk/gtk.h> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/bind_helpers.h" | |
| 11 #include "base/memory/weak_ptr.h" | |
| 12 #include "base/stl_util.h" | |
| 13 #include "base/strings/string_number_conversions.h" | |
| 14 #include "base/strings/utf_string_conversions.h" | |
| 15 #include "chrome/browser/chrome_notification_types.h" | |
| 16 #include "chrome/browser/history/top_sites.h" | |
| 17 #include "chrome/browser/profiles/profile.h" | |
| 18 #include "chrome/browser/sessions/tab_restore_service.h" | |
| 19 #include "chrome/browser/sessions/tab_restore_service_factory.h" | |
| 20 #include "chrome/browser/themes/theme_service_factory.h" | |
| 21 #include "chrome/browser/ui/browser.h" | |
| 22 #include "chrome/browser/ui/browser_tab_restore_service_delegate.h" | |
| 23 #include "chrome/browser/ui/gtk/event_utils.h" | |
| 24 #include "chrome/browser/ui/gtk/global_menu_bar.h" | |
| 25 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
| 26 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 27 #include "chrome/common/url_constants.h" | |
| 28 #include "content/public/browser/notification_source.h" | |
| 29 #include "grit/generated_resources.h" | |
| 30 #include "ui/base/gtk/owned_widget_gtk.h" | |
| 31 #include "ui/base/l10n/l10n_util.h" | |
| 32 #include "ui/gfx/codec/png_codec.h" | |
| 33 #include "ui/gfx/gtk_util.h" | |
| 34 #include "ui/gfx/text_elider.h" | |
| 35 | |
| 36 using content::OpenURLParams; | |
| 37 | |
| 38 namespace { | |
| 39 | |
| 40 // The maximum number of most visited items to display. | |
| 41 const unsigned int kMostVisitedCount = 8; | |
| 42 | |
| 43 // The number of recently closed items to get. | |
| 44 const unsigned int kRecentlyClosedCount = 8; | |
| 45 | |
| 46 // Menus more than this many chars long will get trimmed. | |
| 47 const int kMaximumMenuWidthInChars = 50; | |
| 48 | |
| 49 } // namespace | |
| 50 | |
| 51 struct GlobalHistoryMenu::ClearMenuClosure { | |
| 52 GtkWidget* container; | |
| 53 GlobalHistoryMenu* menu_bar; | |
| 54 int tag; | |
| 55 }; | |
| 56 | |
| 57 struct GlobalHistoryMenu::GetIndexClosure { | |
| 58 bool found; | |
| 59 int current_index; | |
| 60 int tag; | |
| 61 }; | |
| 62 | |
| 63 class GlobalHistoryMenu::HistoryItem { | |
| 64 public: | |
| 65 HistoryItem() | |
| 66 : menu_item(NULL), | |
| 67 session_id(0) {} | |
| 68 | |
| 69 // The title for the menu item. | |
| 70 base::string16 title; | |
| 71 // The URL that will be navigated to if the user selects this item. | |
| 72 GURL url; | |
| 73 | |
| 74 // A pointer to the menu_item. This is a weak reference in the GTK+ version | |
| 75 // because the GtkMenu must sink the reference. | |
| 76 GtkWidget* menu_item; | |
| 77 | |
| 78 // This ID is unique for a browser session and can be passed to the | |
| 79 // TabRestoreService to re-open the closed window or tab that this | |
| 80 // references. A non-0 session ID indicates that this is an entry can be | |
| 81 // restored that way. Otherwise, the URL will be used to open the item and | |
| 82 // this ID will be 0. | |
| 83 SessionID::id_type session_id; | |
| 84 | |
| 85 // If the HistoryItem is a window, this will be the vector of tabs. Note | |
| 86 // that this is a list of weak references. The |menu_item_map_| is the owner | |
| 87 // of all items. If it is not a window, then the entry is a single page and | |
| 88 // the vector will be empty. | |
| 89 std::vector<HistoryItem*> tabs; | |
| 90 | |
| 91 private: | |
| 92 DISALLOW_COPY_AND_ASSIGN(HistoryItem); | |
| 93 }; | |
| 94 | |
| 95 GlobalHistoryMenu::GlobalHistoryMenu(Browser* browser) | |
| 96 : browser_(browser), | |
| 97 profile_(browser_->profile()), | |
| 98 history_menu_(NULL), | |
| 99 top_sites_(NULL), | |
| 100 tab_restore_service_(NULL), | |
| 101 weak_ptr_factory_(this) { | |
| 102 } | |
| 103 | |
| 104 GlobalHistoryMenu::~GlobalHistoryMenu() { | |
| 105 if (tab_restore_service_) | |
| 106 tab_restore_service_->RemoveObserver(this); | |
| 107 | |
| 108 STLDeleteContainerPairSecondPointers(menu_item_history_map_.begin(), | |
| 109 menu_item_history_map_.end()); | |
| 110 menu_item_history_map_.clear(); | |
| 111 | |
| 112 if (history_menu_) { | |
| 113 gtk_widget_destroy(history_menu_); | |
| 114 g_object_unref(history_menu_); | |
| 115 } | |
| 116 } | |
| 117 | |
| 118 void GlobalHistoryMenu::Init(GtkWidget* history_menu, | |
| 119 GtkWidget* history_menu_item) { | |
| 120 history_menu_ = history_menu; | |
| 121 g_object_ref_sink(history_menu_); | |
| 122 | |
| 123 // We have to connect to |history_menu_item|'s "activate" signal instead of | |
| 124 // |history_menu|'s "show" signal because we are not supposed to modify the | |
| 125 // menu during "show" | |
| 126 g_signal_connect(history_menu_item, "activate", | |
| 127 G_CALLBACK(OnMenuActivateThunk), this); | |
| 128 | |
| 129 if (profile_) { | |
| 130 top_sites_ = profile_->GetTopSites(); | |
| 131 if (top_sites_) { | |
| 132 GetTopSitesData(); | |
| 133 | |
| 134 // Register for notification when TopSites changes so that we can update | |
| 135 // ourself. | |
| 136 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, | |
| 137 content::Source<history::TopSites>(top_sites_)); | |
| 138 } | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 void GlobalHistoryMenu::GetTopSitesData() { | |
| 143 DCHECK(top_sites_); | |
| 144 | |
| 145 top_sites_->GetMostVisitedURLs( | |
| 146 base::Bind(&GlobalHistoryMenu::OnTopSitesReceived, | |
| 147 weak_ptr_factory_.GetWeakPtr()), false); | |
| 148 } | |
| 149 | |
| 150 void GlobalHistoryMenu::OnTopSitesReceived( | |
| 151 const history::MostVisitedURLList& visited_list) { | |
| 152 ClearMenuSection(history_menu_, GlobalMenuBar::TAG_MOST_VISITED); | |
| 153 | |
| 154 int index = GetIndexOfMenuItemWithTag( | |
| 155 history_menu_, | |
| 156 GlobalMenuBar::TAG_MOST_VISITED_HEADER) + 1; | |
| 157 | |
| 158 for (size_t i = 0; i < visited_list.size() && i < kMostVisitedCount; ++i) { | |
| 159 const history::MostVisitedURL& visited = visited_list[i]; | |
| 160 if (visited.url.spec().empty()) | |
| 161 break; // This is the signal that there are no more real visited sites. | |
| 162 | |
| 163 HistoryItem* item = new HistoryItem(); | |
| 164 item->title = visited.title; | |
| 165 item->url = visited.url; | |
| 166 | |
| 167 AddHistoryItemToMenu(item, | |
| 168 history_menu_, | |
| 169 GlobalMenuBar::TAG_MOST_VISITED, | |
| 170 index++); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForMenuItem( | |
| 175 GtkWidget* menu_item) { | |
| 176 MenuItemToHistoryMap::iterator it = menu_item_history_map_.find(menu_item); | |
| 177 return it != menu_item_history_map_.end() ? it->second : NULL; | |
| 178 } | |
| 179 | |
| 180 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForTab( | |
| 181 const TabRestoreService::Tab& entry) { | |
| 182 const sessions::SerializedNavigationEntry& current_navigation = | |
| 183 entry.navigations.at(entry.current_navigation_index); | |
| 184 HistoryItem* item = new HistoryItem(); | |
| 185 item->title = current_navigation.title(); | |
| 186 item->url = current_navigation.virtual_url(); | |
| 187 item->session_id = entry.id; | |
| 188 | |
| 189 return item; | |
| 190 } | |
| 191 | |
| 192 GtkWidget* GlobalHistoryMenu::AddHistoryItemToMenu(HistoryItem* item, | |
| 193 GtkWidget* menu, | |
| 194 int tag, | |
| 195 int index) { | |
| 196 base::string16 title = item->title; | |
| 197 std::string url_string = item->url.possibly_invalid_spec(); | |
| 198 | |
| 199 if (title.empty()) | |
| 200 title = base::UTF8ToUTF16(url_string); | |
| 201 gfx::ElideString(title, kMaximumMenuWidthInChars, &title); | |
| 202 | |
| 203 GtkWidget* menu_item = gtk_menu_item_new_with_label( | |
| 204 base::UTF16ToUTF8(title).c_str()); | |
| 205 | |
| 206 item->menu_item = menu_item; | |
| 207 gtk_widget_show(menu_item); | |
| 208 g_object_set_data(G_OBJECT(menu_item), "type-tag", GINT_TO_POINTER(tag)); | |
| 209 g_signal_connect(menu_item, "activate", | |
| 210 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this); | |
| 211 | |
| 212 std::string tooltip = gtk_util::BuildTooltipTitleFor(item->title, item->url); | |
| 213 gtk_widget_set_tooltip_markup(menu_item, tooltip.c_str()); | |
| 214 | |
| 215 menu_item_history_map_.insert(std::make_pair(menu_item, item)); | |
| 216 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, index); | |
| 217 | |
| 218 return menu_item; | |
| 219 } | |
| 220 | |
| 221 int GlobalHistoryMenu::GetIndexOfMenuItemWithTag(GtkWidget* menu, int tag_id) { | |
| 222 GetIndexClosure closure; | |
| 223 closure.found = false; | |
| 224 closure.current_index = 0; | |
| 225 closure.tag = tag_id; | |
| 226 | |
| 227 gtk_container_foreach( | |
| 228 GTK_CONTAINER(menu), | |
| 229 reinterpret_cast<void (*)(GtkWidget*, void*)>(GetIndexCallback), | |
| 230 &closure); | |
| 231 | |
| 232 return closure.current_index; | |
| 233 } | |
| 234 | |
| 235 void GlobalHistoryMenu::ClearMenuSection(GtkWidget* menu, int tag) { | |
| 236 ClearMenuClosure closure; | |
| 237 closure.container = menu; | |
| 238 closure.menu_bar = this; | |
| 239 closure.tag = tag; | |
| 240 | |
| 241 gtk_container_foreach( | |
| 242 GTK_CONTAINER(menu), | |
| 243 reinterpret_cast<void (*)(GtkWidget*, void*)>(ClearMenuCallback), | |
| 244 &closure); | |
| 245 } | |
| 246 | |
| 247 // static | |
| 248 void GlobalHistoryMenu::GetIndexCallback(GtkWidget* menu_item, | |
| 249 GetIndexClosure* closure) { | |
| 250 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag")); | |
| 251 if (tag == closure->tag) | |
| 252 closure->found = true; | |
| 253 | |
| 254 if (!closure->found) | |
| 255 closure->current_index++; | |
| 256 } | |
| 257 | |
| 258 // static | |
| 259 void GlobalHistoryMenu::ClearMenuCallback(GtkWidget* menu_item, | |
| 260 ClearMenuClosure* closure) { | |
| 261 DCHECK_NE(closure->tag, 0); | |
| 262 | |
| 263 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag")); | |
| 264 if (closure->tag == tag) { | |
| 265 HistoryItem* item = closure->menu_bar->HistoryItemForMenuItem(menu_item); | |
| 266 | |
| 267 if (item) { | |
| 268 closure->menu_bar->menu_item_history_map_.erase(menu_item); | |
| 269 delete item; | |
| 270 } | |
| 271 | |
| 272 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
| 273 if (submenu) | |
| 274 closure->menu_bar->ClearMenuSection(submenu, closure->tag); | |
| 275 | |
| 276 gtk_container_remove(GTK_CONTAINER(closure->container), menu_item); | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 void GlobalHistoryMenu::Observe(int type, | |
| 281 const content::NotificationSource& source, | |
| 282 const content::NotificationDetails& details) { | |
| 283 if (type == chrome::NOTIFICATION_TOP_SITES_CHANGED) { | |
| 284 GetTopSitesData(); | |
| 285 } else { | |
| 286 NOTREACHED(); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 void GlobalHistoryMenu::TabRestoreServiceChanged(TabRestoreService* service) { | |
| 291 const TabRestoreService::Entries& entries = service->entries(); | |
| 292 | |
| 293 ClearMenuSection(history_menu_, GlobalMenuBar::TAG_RECENTLY_CLOSED); | |
| 294 | |
| 295 // We'll get the index the "Recently Closed" header. (This can vary depending | |
| 296 // on the number of "Most Visited" items. | |
| 297 int index = GetIndexOfMenuItemWithTag( | |
| 298 history_menu_, | |
| 299 GlobalMenuBar::TAG_RECENTLY_CLOSED_HEADER) + 1; | |
| 300 | |
| 301 unsigned int added_count = 0; | |
| 302 for (TabRestoreService::Entries::const_iterator it = entries.begin(); | |
| 303 it != entries.end() && added_count < kRecentlyClosedCount; ++it) { | |
| 304 TabRestoreService::Entry* entry = *it; | |
| 305 | |
| 306 if (entry->type == TabRestoreService::WINDOW) { | |
| 307 TabRestoreService::Window* entry_win = | |
| 308 static_cast<TabRestoreService::Window*>(entry); | |
| 309 std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs; | |
| 310 if (tabs.empty()) | |
| 311 continue; | |
| 312 | |
| 313 // Create the item for the parent/window. | |
| 314 HistoryItem* item = new HistoryItem(); | |
| 315 item->session_id = entry_win->id; | |
| 316 | |
| 317 GtkWidget* submenu = gtk_menu_new(); | |
| 318 GtkWidget* restore_item = gtk_menu_item_new_with_label( | |
| 319 l10n_util::GetStringUTF8( | |
| 320 IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str()); | |
| 321 g_object_set_data(G_OBJECT(restore_item), "type-tag", | |
| 322 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED)); | |
| 323 g_signal_connect(restore_item, "activate", | |
| 324 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this); | |
| 325 gtk_widget_show(restore_item); | |
| 326 | |
| 327 // The mac version of this code allows the user to click on the parent | |
| 328 // menu item to have the same effect as clicking the restore window | |
| 329 // submenu item. GTK+ helpfully activates a menu item when it shows a | |
| 330 // submenu so toss that feature out. | |
| 331 menu_item_history_map_.insert(std::make_pair(restore_item, item)); | |
| 332 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), restore_item); | |
| 333 | |
| 334 GtkWidget* separator = gtk_separator_menu_item_new(); | |
| 335 gtk_widget_show(separator); | |
| 336 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), separator); | |
| 337 | |
| 338 // Loop over the window's tabs and add them to the submenu. | |
| 339 int subindex = 2; | |
| 340 std::vector<TabRestoreService::Tab>::const_iterator iter; | |
| 341 for (iter = tabs.begin(); iter != tabs.end(); ++iter) { | |
| 342 TabRestoreService::Tab tab = *iter; | |
| 343 HistoryItem* tab_item = HistoryItemForTab(tab); | |
| 344 item->tabs.push_back(tab_item); | |
| 345 AddHistoryItemToMenu(tab_item, | |
| 346 submenu, | |
| 347 GlobalMenuBar::TAG_RECENTLY_CLOSED, | |
| 348 subindex++); | |
| 349 } | |
| 350 | |
| 351 std::string title = item->tabs.size() == 1 ? | |
| 352 l10n_util::GetStringUTF8( | |
| 353 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) : | |
| 354 l10n_util::GetStringFUTF8( | |
| 355 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE, | |
| 356 base::IntToString16(item->tabs.size())); | |
| 357 | |
| 358 // Create the menu item parent. Unlike mac, it's can't be activated. | |
| 359 GtkWidget* parent_item = gtk_menu_item_new_with_label(title.c_str()); | |
| 360 gtk_widget_show(parent_item); | |
| 361 g_object_set_data(G_OBJECT(parent_item), "type-tag", | |
| 362 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED)); | |
| 363 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), submenu); | |
| 364 | |
| 365 gtk_menu_shell_insert(GTK_MENU_SHELL(history_menu_), parent_item, | |
| 366 index++); | |
| 367 ++added_count; | |
| 368 } else if (entry->type == TabRestoreService::TAB) { | |
| 369 TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); | |
| 370 HistoryItem* item = HistoryItemForTab(*tab); | |
| 371 AddHistoryItemToMenu(item, | |
| 372 history_menu_, | |
| 373 GlobalMenuBar::TAG_RECENTLY_CLOSED, | |
| 374 index++); | |
| 375 ++added_count; | |
| 376 } | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 void GlobalHistoryMenu::TabRestoreServiceDestroyed( | |
| 381 TabRestoreService* service) { | |
| 382 tab_restore_service_ = NULL; | |
| 383 } | |
| 384 | |
| 385 void GlobalHistoryMenu::OnRecentlyClosedItemActivated(GtkWidget* sender) { | |
| 386 WindowOpenDisposition disposition = | |
| 387 event_utils::DispositionForCurrentButtonPressEvent(); | |
| 388 HistoryItem* item = HistoryItemForMenuItem(sender); | |
| 389 | |
| 390 // If this item can be restored using TabRestoreService, do so. Otherwise, | |
| 391 // just load the URL. | |
| 392 TabRestoreService* service = | |
| 393 TabRestoreServiceFactory::GetForProfile(browser_->profile()); | |
| 394 if (item->session_id && service) { | |
| 395 service->RestoreEntryById(browser_->tab_restore_service_delegate(), | |
| 396 item->session_id, browser_->host_desktop_type(), | |
| 397 UNKNOWN); | |
| 398 } else { | |
| 399 DCHECK(item->url.is_valid()); | |
| 400 browser_->OpenURL(OpenURLParams(item->url, content::Referrer(), disposition, | |
| 401 content::PAGE_TRANSITION_AUTO_BOOKMARK, false)); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 void GlobalHistoryMenu::OnMenuActivate(GtkWidget* sender) { | |
| 406 if (!tab_restore_service_) { | |
| 407 tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_); | |
| 408 if (tab_restore_service_) { | |
| 409 tab_restore_service_->LoadTabsFromLastSession(); | |
| 410 tab_restore_service_->AddObserver(this); | |
| 411 | |
| 412 // If LoadTabsFromLastSession doesn't load tabs, it won't call | |
| 413 // TabRestoreServiceChanged(). This ensures that all new windows after | |
| 414 // the first one will have their menus populated correctly. | |
| 415 TabRestoreServiceChanged(tab_restore_service_); | |
| 416 } | |
| 417 } | |
| 418 } | |
| OLD | NEW |