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

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 debugging code 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
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
132 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForMenuItem(
133 GtkWidget* menu_item) {
134 MenuItemToHistoryMap::iterator it = menu_item_history_map_.find(menu_item);
135 return it != menu_item_history_map_.end() ? it->second : NULL;
136 }
137
138 bool GlobalHistoryMenu::HasValidHistoryItemForTab(
139 const TabRestoreService::Tab& entry) {
140 if (entry.navigations.empty())
141 return false;
142
143 const TabNavigation& current_navigation =
144 entry.navigations.at(entry.current_navigation_index);
145 if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
146 return false;
147
148 return true;
149 }
150
151 GlobalHistoryMenu::HistoryItem* GlobalHistoryMenu::HistoryItemForTab(
152 const TabRestoreService::Tab& entry) {
153 if (!HasValidHistoryItemForTab(entry))
154 return NULL;
155
156 const TabNavigation& current_navigation =
157 entry.navigations.at(entry.current_navigation_index);
158 HistoryItem* item = new HistoryItem();
159 item->title = current_navigation.title();
160 item->url = current_navigation.virtual_url();
161 item->session_id = entry.id;
162
163 // Tab navigations don't come with icons, so we always have to request them.
164 GetFaviconForHistoryItem(item);
165
166 return item;
167 }
168
169 GtkWidget* GlobalHistoryMenu::AddHistoryItemToMenu(HistoryItem* item,
170 GtkWidget* menu,
171 int tag,
172 int index) {
173 string16 title = item->title;
174 std::string url_string = item->url.possibly_invalid_spec();
175
176 if (title.empty())
177 title = UTF8ToUTF16(url_string);
178 ui::ElideString(title, kMaximumMenuWidthInChars, &title);
179
180 GtkWidget* menu_item = gtk_image_menu_item_new_with_label(
181 UTF16ToUTF8(title).c_str());
182 gtk_util::SetAlwaysShowImage(menu_item);
183
184 item->menu_item = menu_item;
185 gtk_widget_show(menu_item);
186 g_object_set_data(G_OBJECT(menu_item), "type-tag", GINT_TO_POINTER(tag));
187 g_signal_connect(menu_item, "activate",
188 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
189 if (item->icon_image.get()) {
190 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
191 item->icon_image.get());
192 } else if (!item->tabs.size()) {
193 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
194 gtk_image_new_from_pixbuf(default_favicon_));
195 }
196
197 std::string tooltip = gtk_util::BuildTooltipTitleFor(item->title, item->url);
198 gtk_widget_set_tooltip_markup(menu_item, tooltip.c_str());
199
200 menu_item_history_map_.insert(std::make_pair(menu_item, item));
201 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, index);
202
203 return menu_item;
204 }
205
206 void GlobalHistoryMenu::GetFaviconForHistoryItem(HistoryItem* item) {
207 FaviconService* service =
208 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
209 FaviconService::Handle handle = service->GetFaviconForURL(
210 item->url,
211 history::FAVICON,
212 &favicon_consumer_,
213 NewCallback(this, &GlobalHistoryMenu::GotFaviconData));
214 favicon_consumer_.SetClientData(service, handle, item);
215 item->icon_handle = handle;
216 item->icon_requested = true;
217 }
218
219 void GlobalHistoryMenu::GotFaviconData(FaviconService::Handle handle,
220 history::FaviconData favicon) {
221 HistoryItem* item =
222 favicon_consumer_.GetClientData(
223 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), handle);
224 DCHECK(item);
225 item->icon_requested = false;
226 item->icon_handle = NULL;
227
228 SkBitmap icon;
229 if (favicon.is_valid() &&
230 gfx::PNGCodec::Decode(favicon.image_data->front(),
231 favicon.image_data->size(), &icon)) {
232 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
233 if (pixbuf) {
234 item->icon_image.Own(gtk_image_new_from_pixbuf(pixbuf));
235 g_object_unref(pixbuf);
236
237 if (item->menu_item) {
238 gtk_image_menu_item_set_image(
239 GTK_IMAGE_MENU_ITEM(item->menu_item),
240 item->icon_image.get());
241 }
242 }
243 }
244 }
245
246 void GlobalHistoryMenu::CancelFaviconRequest(HistoryItem* item) {
247 DCHECK(item);
248 if (item->icon_requested) {
249 FaviconService* service =
250 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
251 service->CancelRequest(item->icon_handle);
252 item->icon_requested = false;
253 item->icon_handle = NULL;
254 }
255 }
256
257
Evan Stade 2011/04/19 18:17:10 extra line
258 int GlobalHistoryMenu::GetIndexOfMenuItemWithTag(GtkWidget* menu, int tag_id) {
259 GetIndexClosure closure;
260 closure.found = false;
261 closure.current_index = 0;
262 closure.tag = tag_id;
263
264 gtk_container_foreach(
265 GTK_CONTAINER(menu),
266 reinterpret_cast<void (*)(GtkWidget*, void*)>(GetIndexCallback),
267 &closure);
268
269 return closure.current_index;
270 }
271
272 void GlobalHistoryMenu::ClearMenuSection(GtkWidget* menu, int tag) {
273 ClearMenuClosure closure;
274 closure.container = menu;
275 closure.menu_bar = this;
276 closure.tag = tag;
277
278 gtk_container_foreach(
279 GTK_CONTAINER(menu),
280 reinterpret_cast<void (*)(GtkWidget*, void*)>(ClearMenuCallback),
281 &closure);
282 }
283
284 // static
285 void GlobalHistoryMenu::GetIndexCallback(GtkWidget* menu_item,
286 GetIndexClosure* closure) {
287 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
288 if (tag == closure->tag)
289 closure->found = true;
290
291 if (!closure->found)
292 closure->current_index++;
293 }
294
295 // static
296 void GlobalHistoryMenu::ClearMenuCallback(GtkWidget* menu_item,
297 ClearMenuClosure* closure) {
298 DCHECK_NE(closure->tag, 0);
299
300 int tag = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "type-tag"));
301 if (closure->tag == tag) {
302 HistoryItem* item = closure->menu_bar->HistoryItemForMenuItem(menu_item);
303
304 if (item) {
305 closure->menu_bar->CancelFaviconRequest(item);
306 closure->menu_bar->menu_item_history_map_.erase(menu_item);
307 delete item;
308 }
309
310 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
311 if (submenu)
312 closure->menu_bar->ClearMenuSection(submenu, closure->tag);
313
314 gtk_container_remove(GTK_CONTAINER(closure->container), menu_item);
315 }
316 }
317
318 void GlobalHistoryMenu::Observe(NotificationType type,
319 const NotificationSource& source,
320 const NotificationDetails& details) {
321 DCHECK(type.value == NotificationType::BROWSER_THEME_CHANGED);
322
323 // Keeping track of which menu items have the default icon is going an
324 // error-prone pain, so instead just store the new default favicon and
325 // we'll update on the next menu change event.
326 default_favicon_ = GtkThemeService::GetDefaultFavicon(true);
327 }
328
329
330 void GlobalHistoryMenu::TabRestoreServiceChanged(TabRestoreService* service) {
331 const TabRestoreService::Entries& entries = service->entries();
332
333 ClearMenuSection(history_menu_, GlobalMenuBar::TAG_RECENTLY_CLOSED);
334
335 // We'll get the index the "Recently Closed" header. (This can vary depending
336 // on the number of "Most Visited" items.
337 int index = GetIndexOfMenuItemWithTag(
338 history_menu_,
339 GlobalMenuBar::TAG_RECENTLY_CLOSED_HEADER) + 1;
340
341 unsigned int added_count = 0;
342 for (TabRestoreService::Entries::const_iterator it = entries.begin();
343 it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
344 TabRestoreService::Entry* entry = *it;
345
346 if (entry->type == TabRestoreService::WINDOW) {
347 TabRestoreService::Window* entry_win =
348 static_cast<TabRestoreService::Window*>(entry);
349 std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
350 if (tabs.empty())
351 continue;
352
353 // Check that this window has valid content. Sometimes it is possible for
354 // there to not be any subitems for a given window; if that is the case,
355 // do not add the entry to the main menu.
356 int valid_tab_count = 0;
357 std::vector<TabRestoreService::Tab>::const_iterator it;
358 for (it = tabs.begin(); it != tabs.end(); ++it) {
359 if (HasValidHistoryItemForTab(*it))
360 valid_tab_count++;
361 }
362 if (valid_tab_count == 0)
363 continue;
364
365 // Create the item for the parent/window. Do not set the title yet
366 // because the actual number of items that are in the menu will not be
367 // known until things like the NTP are filtered out, which is done when
368 // the tab items are actually created.
369 HistoryItem* item = new HistoryItem();
370 item->session_id = entry_win->id;
371
372 GtkWidget* submenu = gtk_menu_new();
373
374 GtkWidget* restore_item = gtk_menu_item_new_with_label(
375 l10n_util::GetStringUTF8(
376 IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str());
377 g_object_set_data(G_OBJECT(restore_item), "type-tag",
378 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
379 g_signal_connect(restore_item, "activate",
380 G_CALLBACK(OnRecentlyClosedItemActivatedThunk), this);
381 gtk_widget_show(restore_item);
382
383 // The mac version of this code allows the user to click on the parent
384 // menu item to have the same effect as clicking the restore window
385 // submenu item. GTK+ helpfully activates a menu item when it shows a
386 // submenu so toss that feature out.
387 menu_item_history_map_.insert(std::make_pair(restore_item, item));
388 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), restore_item);
389
390 GtkWidget* separator = gtk_separator_menu_item_new();
391 gtk_widget_show(separator);
392 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), separator);
393
394 // Loop over the window's tabs and add them to the submenu.
395 int subindex = 2;
396 for (it = tabs.begin(); it != tabs.end(); ++it) {
397 TabRestoreService::Tab tab = *it;
398 HistoryItem* tab_item = HistoryItemForTab(tab);
399 if (tab_item) {
400 item->tabs.push_back(tab_item);
401 AddHistoryItemToMenu(tab_item,
402 submenu,
403 GlobalMenuBar::TAG_RECENTLY_CLOSED,
404 subindex++);
405 }
406 }
407
408 // Now that the number of tabs that has been added is known, set the
409 // title of the parent menu item.
410 std::string title =
411 (item->tabs.size() == 1) ?
412 l10n_util::GetStringUTF8(
413 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
414 l10n_util::GetStringFUTF8(
415 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
416 base::IntToString16(item->tabs.size()));
417
418 // Create the menu item parent. Unlike mac, it's can't be activated.
419 GtkWidget* parent_item = gtk_image_menu_item_new_with_label(
420 title.c_str());
421 gtk_widget_show(parent_item);
422 g_object_set_data(G_OBJECT(parent_item), "type-tag",
423 GINT_TO_POINTER(GlobalMenuBar::TAG_RECENTLY_CLOSED));
424 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), submenu);
425
426 gtk_menu_shell_insert(GTK_MENU_SHELL(history_menu_), parent_item,
427 index++);
428 ++added_count;
429 } else if (entry->type == TabRestoreService::TAB) {
430 TabRestoreService::Tab* tab =
431 static_cast<TabRestoreService::Tab*>(entry);
432 HistoryItem* item = HistoryItemForTab(*tab);
433 if (item) {
434 AddHistoryItemToMenu(item,
435 history_menu_,
436 GlobalMenuBar::TAG_RECENTLY_CLOSED,
437 index++);
438 ++added_count;
439 }
440 }
441 }
442 }
443
444 void GlobalHistoryMenu::TabRestoreServiceDestroyed(
445 TabRestoreService* service) {
446 tab_restore_service_ = NULL;
447 }
448
449 void GlobalHistoryMenu::OnRecentlyClosedItemActivated(GtkWidget* sender) {
450 WindowOpenDisposition disposition =
451 gtk_util::DispositionForCurrentButtonPressEvent();
452 HistoryItem* item = HistoryItemForMenuItem(sender);
453
454 // If this item can be restored using TabRestoreService, do so. Otherwise,
455 // just load the URL.
456 TabRestoreService* service = browser_->profile()->GetTabRestoreService();
457 if (item->session_id && service) {
458 service->RestoreEntryById(browser_->tab_restore_service_delegate(),
459 item->session_id, false);
460 } else {
461 DCHECK(item->url.is_valid());
462 browser_->OpenURL(item->url, GURL(), disposition,
463 PageTransition::AUTO_BOOKMARK);
464 }
465 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698