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

Side by Side Diff: chrome/browser/ui/gtk/global_history_menu.cc

Issue 6840068: GTK: Add Recently Closed tabs to the History menu in the global menu bar. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove current most visited section. will redo with TopSites in a different patch. Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/ui/gtk/global_history_menu.h ('k') | chrome/browser/ui/gtk/global_menu_bar.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/stl_util-inl.h"
10 #include "base/utf_string_conversions.h"
11 #include "base/string_number_conversions.h"
12 #include "chrome/browser/favicon_service.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_tab_restore_service_delegate.h"
16 #include "chrome/browser/ui/gtk/global_menu_bar.h"
17 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
18 #include "chrome/browser/ui/gtk/gtk_util.h"
19 #include "chrome/browser/ui/gtk/owned_widget_gtk.h"
20 #include "chrome/common/url_constants.h"
21 #include "content/common/notification_service.h"
22 #include "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/text/text_elider.h"
25 #include "ui/gfx/codec/png_codec.h"
26 #include "ui/gfx/gtk_util.h"
27
28 namespace {
29
30 // The number of recently closed items to get.
31 const unsigned int kRecentlyClosedCount = 10;
32
33 // Menus more than this many chars long will get trimmed.
34 const int kMaximumMenuWidthInChars = 50;
35
36 } // namespace
37
38 struct GlobalHistoryMenu::ClearMenuClosure {
39 GtkWidget* container;
40 GlobalHistoryMenu* menu_bar;
41 int tag;
42 };
43
44 struct GlobalHistoryMenu::GetIndexClosure {
45 bool found;
46 int current_index;
47 int tag;
48 };
49
50 class GlobalHistoryMenu::HistoryItem {
51 public:
52 HistoryItem()
53 : icon_requested(false),
54 menu_item(NULL),
55 session_id(0) {}
56
57 // The title for the menu item.
58 string16 title;
59 // The URL that will be navigated to if the user selects this item.
60 GURL url;
61
62 // If the icon is being requested from the FaviconService, |icon_requested|
63 // will be true and |icon_handle| will be non-NULL. If this is false, then
64 // |icon_handle| will be NULL.
65 bool icon_requested;
66 // The Handle given to us by the FaviconService for the icon fetch request.
67 FaviconService::Handle icon_handle;
68
69 // The icon as a GtkImage for inclusion in a GtkImageMenuItem.
70 OwnedWidgetGtk icon_image;
71
72 // A pointer to the menu_item. This is a weak reference in the GTK+ version
73 // because the GtkMenu must sink the reference.
74 GtkWidget* menu_item;
75
76 // This ID is unique for a browser session and can be passed to the
77 // TabRestoreService to re-open the closed window or tab that this
78 // references. A non-0 session ID indicates that this is an entry can be
79 // restored that way. Otherwise, the URL will be used to open the item and
80 // this ID will be 0.
81 SessionID::id_type session_id;
82
83 // If the HistoryItem is a window, this will be the vector of tabs. Note
84 // that this is a list of weak references. The |menu_item_map_| is the owner
85 // of all items. If it is not a window, then the entry is a single page and
86 // the vector will be empty.
87 std::vector<HistoryItem*> tabs;
88
89 private:
90 DISALLOW_COPY_AND_ASSIGN(HistoryItem);
91 };
92
93 GlobalHistoryMenu::GlobalHistoryMenu(Browser* browser)
94 : browser_(browser),
95 profile_(browser_->profile()),
96 default_favicon_(NULL),
97 tab_restore_service_(NULL) {
98 }
99
100 GlobalHistoryMenu::~GlobalHistoryMenu() {
101 if (tab_restore_service_)
102 tab_restore_service_->RemoveObserver(this);
103
104 STLDeleteContainerPairSecondPointers(menu_item_history_map_.begin(),
105 menu_item_history_map_.end());
106 menu_item_history_map_.clear();
107 }
108
109 void GlobalHistoryMenu::Init(GtkWidget* history_menu) {
110 history_menu_ = history_menu;
111
112 default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
113
114 if (profile_) {
115 tab_restore_service_ = profile_->GetTabRestoreService();
116 if (tab_restore_service_) {
117 tab_restore_service_->LoadTabsFromLastSession();
118 tab_restore_service_->AddObserver(this);
119
120 // If LoadTabsFromLastSession doesn't load tabs, it won't call
121 // TabRestoreServiceChanged(). This ensures that all new windows after
122 // the first one will have their menus populated correctly.
123 TabRestoreServiceChanged(tab_restore_service_);
124 }
125
126 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
127 Source<Profile>(profile_));
128 }
129 }
130
131 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForMenuItem(
132 GtkWidget* menu_item) {
133 MenuItemToHistoryMap::iterator it = menu_item_history_map_.find(menu_item);
134 return it != menu_item_history_map_.end() ? it->second : NULL;
135 }
136
137 bool GlobalHistoryMenu::HasValidHistoryItemForTab(
138 const TabRestoreService::Tab& entry) {
139 if (entry.navigations.empty())
140 return false;
141
142 const TabNavigation& current_navigation =
143 entry.navigations.at(entry.current_navigation_index);
144 if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
145 return false;
146
147 return true;
148 }
149
150 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForTab(
151 const TabRestoreService::Tab& entry) {
152 if (!HasValidHistoryItemForTab(entry))
153 return NULL;
154
155 const TabNavigation& current_navigation =
156 entry.navigations.at(entry.current_navigation_index);
157 HistoryItem* item = new HistoryItem();
158 item->title = current_navigation.title();
159 item->url = current_navigation.virtual_url();
160 item->session_id = entry.id;
161
162 // Tab navigations don't come with icons, so we always have to request them.
163 GetFaviconForHistoryItem(item);
164
165 return item;
166 }
167
168 GtkWidget* GlobalHistoryMenu::AddHistoryItemToMenu(HistoryItem* item,
169 GtkWidget* menu,
170 int tag,
171 int index) {
172 string16 title = item->title;
173 std::string url_string = item->url.possibly_invalid_spec();
174
175 if (title.empty())
176 title = UTF8ToUTF16(url_string);
177 ui::ElideString(title, kMaximumMenuWidthInChars, &title);
178
179 GtkWidget* menu_item = gtk_image_menu_item_new_with_label(
180 UTF16ToUTF8(title).c_str());
181 gtk_util::SetAlwaysShowImage(menu_item);
182
183 item->menu_item = menu_item;
184 gtk_widget_show(menu_item);
185 g_object_set_data(G_OBJECT(menu_item), "type-tag", GINT_TO_POINTER(tag));
186 g_signal_connect(menu_item, "activate",
187 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
188 if (item->icon_image.get()) {
189 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
190 item->icon_image.get());
191 } else if (!item->tabs.size()) {
192 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
193 gtk_image_new_from_pixbuf(default_favicon_));
194 }
195
196 std::string tooltip = gtk_util::BuildTooltipTitleFor(item->title, item->url);
197 gtk_widget_set_tooltip_markup(menu_item, tooltip.c_str());
198
199 menu_item_history_map_.insert(std::make_pair(menu_item, item));
200 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, index);
201
202 return menu_item;
203 }
204
205 void GlobalHistoryMenu::GetFaviconForHistoryItem(HistoryItem* item) {
206 FaviconService* service =
207 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
208 FaviconService::Handle handle = service->GetFaviconForURL(
209 item->url,
210 history::FAVICON,
211 &favicon_consumer_,
212 NewCallback(this, &GlobalHistoryMenu::GotFaviconData));
213 favicon_consumer_.SetClientData(service, handle, item);
214 item->icon_handle = handle;
215 item->icon_requested = true;
216 }
217
218 void GlobalHistoryMenu::GotFaviconData(FaviconService::Handle handle,
219 history::FaviconData favicon) {
220 HistoryItem* item =
221 favicon_consumer_.GetClientData(
222 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
223 DCHECK(item);
224 item->icon_requested = false;
225 item->icon_handle = NULL;
226
227 SkBitmap icon;
228 if (favicon.is_valid() &&
229 gfx::PNGCodec::Decode(favicon.image_data->front(),
230 favicon.image_data->size(), &icon)) {
231 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
232 if (pixbuf) {
233 item->icon_image.Own(gtk_image_new_from_pixbuf(pixbuf));
234 g_object_unref(pixbuf);
235
236 if (item->menu_item) {
237 gtk_image_menu_item_set_image(
238 GTK_IMAGE_MENU_ITEM(item->menu_item),
239 item->icon_image.get());
240 }
241 }
242 }
243 }
244
245 void GlobalHistoryMenu::CancelFaviconRequest(HistoryItem* item) {
246 DCHECK(item);
247 if (item->icon_requested) {
248 FaviconService* service =
249 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
250 service->CancelRequest(item->icon_handle);
251 item->icon_requested = false;
252 item->icon_handle = NULL;
253 }
254 }
255
256 int GlobalHistoryMenu::GetIndexOfMenuItemWithTag(GtkWidget* menu, int tag_id) {
257 GetIndexClosure closure;
258 closure.found = false;
259 closure.current_index = 0;
260 closure.tag = tag_id;
261
262 gtk_container_foreach(
263 GTK_CONTAINER(menu),
264 reinterpret_cast<void (*)(GtkWidget*, void*)>(GetIndexCallback),
265 &closure);
266
267 return closure.current_index;
268 }
269
270 void GlobalHistoryMenu::ClearMenuSection(GtkWidget* menu, int tag) {
271 ClearMenuClosure closure;
272 closure.container = menu;
273 closure.menu_bar = this;
274 closure.tag = tag;
275
276 gtk_container_foreach(
277 GTK_CONTAINER(menu),
278 reinterpret_cast<void (*)(GtkWidget*, void*)>(ClearMenuCallback),
279 &closure);
280 }
281
282 // static
283 void GlobalHistoryMenu::GetIndexCallback(GtkWidget* menu_item,
284 GetIndexClosure* closure) {
285 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
286 if (tag == closure->tag)
287 closure->found = true;
288
289 if (!closure->found)
290 closure->current_index++;
291 }
292
293 // static
294 void GlobalHistoryMenu::ClearMenuCallback(GtkWidget* menu_item,
295 ClearMenuClosure* closure) {
296 DCHECK_NE(closure->tag, 0);
297
298 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
299 if (closure->tag == tag) {
300 HistoryItem* item = closure->menu_bar->HistoryItemForMenuItem(menu_item);
301
302 if (item) {
303 closure->menu_bar->CancelFaviconRequest(item);
304 closure->menu_bar->menu_item_history_map_.erase(menu_item);
305 delete item;
306 }
307
308 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
309 if (submenu)
310 closure->menu_bar->ClearMenuSection(submenu, closure->tag);
311
312 gtk_container_remove(GTK_CONTAINER(closure->container), menu_item);
313 }
314 }
315
316 void GlobalHistoryMenu::Observe(NotificationType type,
317 const NotificationSource& source,
318 const NotificationDetails& details) {
319 DCHECK(type.value == NotificationType::BROWSER_THEME_CHANGED);
320
321 // Keeping track of which menu items have the default icon is going an
322 // error-prone pain, so instead just store the new default favicon and
323 // we'll update on the next menu change event.
324 default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
325 }
326
327 void GlobalHistoryMenu::TabRestoreServiceChanged(TabRestoreService* service) {
328 const TabRestoreService::Entries& entries = service->entries();
329
330 ClearMenuSection(history_menu_, GlobalMenuBar::TAG_RECENTLY_CLOSED);
331
332 // We'll get the index the "Recently Closed" header. (This can vary depending
333 // on the number of "Most Visited" items.
334 int index = GetIndexOfMenuItemWithTag(
335 history_menu_,
336 GlobalMenuBar::TAG_RECENTLY_CLOSED_HEADER) + 1;
337
338 unsigned int added_count = 0;
339 for (TabRestoreService::Entries::const_iterator it = entries.begin();
340 it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
341 TabRestoreService::Entry* entry = *it;
342
343 if (entry->type == TabRestoreService::WINDOW) {
344 TabRestoreService::Window* entry_win =
345 static_cast<TabRestoreService::Window*>(entry);
346 std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
347 if (tabs.empty())
348 continue;
349
350 // Check that this window has valid content. Sometimes it is possible for
351 // there to not be any subitems for a given window; if that is the case,
352 // do not add the entry to the main menu.
353 int valid_tab_count = 0;
354 std::vector<TabRestoreService::Tab>::const_iterator it;
355 for (it = tabs.begin(); it != tabs.end(); ++it) {
356 if (HasValidHistoryItemForTab(*it))
357 valid_tab_count++;
358 }
359 if (valid_tab_count == 0)
360 continue;
361
362 // Create the item for the parent/window. Do not set the title yet
363 // because the actual number of items that are in the menu will not be
364 // known until things like the NTP are filtered out, which is done when
365 // the tab items are actually created.
366 HistoryItem* item = new HistoryItem();
367 item->session_id = entry_win->id;
368
369 GtkWidget* submenu = gtk_menu_new();
370
371 GtkWidget* restore_item = gtk_menu_item_new_with_label(
372 l10n_util::GetStringUTF8(
373 IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str());
374 g_object_set_data(G_OBJECT(restore_item), "type-tag",
375 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
376 g_signal_connect(restore_item, "activate",
377 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
378 gtk_widget_show(restore_item);
379
380 // The mac version of this code allows the user to click on the parent
381 // menu item to have the same effect as clicking the restore window
382 // submenu item. GTK+ helpfully activates a menu item when it shows a
383 // submenu so toss that feature out.
384 menu_item_history_map_.insert(std::make_pair(restore_item, item));
385 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), restore_item);
386
387 GtkWidget* separator = gtk_separator_menu_item_new();
388 gtk_widget_show(separator);
389 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), separator);
390
391 // Loop over the window's tabs and add them to the submenu.
392 int subindex = 2;
393 for (it = tabs.begin(); it != tabs.end(); ++it) {
394 TabRestoreService::Tab tab = *it;
395 HistoryItem* tab_item = HistoryItemForTab(tab);
396 if (tab_item) {
397 item->tabs.push_back(tab_item);
398 AddHistoryItemToMenu(tab_item,
399 submenu,
400 GlobalMenuBar::TAG_RECENTLY_CLOSED,
401 subindex++);
402 }
403 }
404
405 // Now that the number of tabs that has been added is known, set the
406 // title of the parent menu item.
407 std::string title =
408 (item->tabs.size() == 1) ?
409 l10n_util::GetStringUTF8(
410 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
411 l10n_util::GetStringFUTF8(
412 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
413 base::IntToString16(item->tabs.size()));
414
415 // Create the menu item parent. Unlike mac, it's can't be activated.
416 GtkWidget* parent_item = gtk_image_menu_item_new_with_label(
417 title.c_str());
418 gtk_widget_show(parent_item);
419 g_object_set_data(G_OBJECT(parent_item), "type-tag",
420 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
421 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), submenu);
422
423 gtk_menu_shell_insert(GTK_MENU_SHELL(history_menu_), parent_item,
424 index++);
425 ++added_count;
426 } else if (entry->type == TabRestoreService::TAB) {
427 TabRestoreService::Tab* tab =
428 static_cast<TabRestoreService::Tab*>(entry);
429 HistoryItem* item = HistoryItemForTab(*tab);
430 if (item) {
431 AddHistoryItemToMenu(item,
432 history_menu_,
433 GlobalMenuBar::TAG_RECENTLY_CLOSED,
434 index++);
435 ++added_count;
436 }
437 }
438 }
439 }
440
441 void GlobalHistoryMenu::TabRestoreServiceDestroyed(
442 TabRestoreService* service) {
443 tab_restore_service_ = NULL;
444 }
445
446 void GlobalHistoryMenu::OnRecentlyClosedItemActivated(GtkWidget* sender) {
447 WindowOpenDisposition disposition =
448 gtk_util::DispositionForCurrentButtonPressEvent();
449 HistoryItem* item = HistoryItemForMenuItem(sender);
450
451 // If this item can be restored using TabRestoreService, do so. Otherwise,
452 // just load the URL.
453 TabRestoreService* service = browser_->profile()->GetTabRestoreService();
454 if (item->session_id && service) {
455 service->RestoreEntryById(browser_->tab_restore_service_delegate(),
456 item->session_id, false);
457 } else {
458 DCHECK(item->url.is_valid());
459 browser_->OpenURL(item->url, GURL(), disposition,
460 PageTransition::AUTO_BOOKMARK);
461 }
462 }
OLDNEW
« no previous file with comments | « chrome/browser/ui/gtk/global_history_menu.h ('k') | chrome/browser/ui/gtk/global_menu_bar.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698