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

Side by Side Diff: chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc

Issue 11298004: alternate ntp: add "Recent Tabs" submenu to wrench menu (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed disposition, minor style cleanup Created 8 years, 1 month 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 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/toolbar/recent_tabs_sub_menu_model.h"
6
7 #include "base/bind.h"
8 #include "base/string_number_conversions.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/app/chrome_command_ids.h"
11 #include "chrome/browser/favicon/favicon_service_factory.h"
12 #include "chrome/browser/prefs/scoped_user_pref_update.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/sessions/session_restore.h"
15 #include "chrome/browser/sessions/tab_restore_service_delegate.h"
16 #include "chrome/browser/sessions/tab_restore_service_factory.h"
17 #include "chrome/browser/sync/glue/session_model_associator.h"
18 #include "chrome/browser/sync/glue/synced_session.h"
19 #include "chrome/browser/sync/profile_sync_service.h"
20 #include "chrome/browser/sync/profile_sync_service_factory.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_commands.h"
23 #include "chrome/browser/ui/browser_tabstrip.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/time_format.h"
27 #include "chrome/common/url_constants.h"
28 #include "grit/generated_resources.h"
29 #include "grit/ui_resources.h"
30 #include "ui/base/accelerators/accelerator.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/base/text/text_elider.h"
34 #include "ui/gfx/favicon_size.h"
35
36 #if defined(USE_ASH)
37 #include "ash/accelerators/accelerator_table.h"
38 #endif // defined(USE_ASH)
39
40 namespace {
41
42 // Comparator function for use with std::sort that will sort sessions by
43 // descending modified_time (i.e., most recent first).
44 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
45 const browser_sync::SyncedSession* s2) {
46 return s1->modified_time > s2->modified_time;
47 }
48
49 // Comparator function for use with std::sort that will sort tabs by
50 // descending timestamp (i.e., most recent first).
51 bool SortTabsByRecency(const SessionTab* t1, const SessionTab* t2) {
52 return t1->timestamp > t2->timestamp;
53 }
54
55 // Convert |model_index| into index in menu, since model and menu are not 1-1.
56 int ModelIndexToMenuIndex(int model_index) {
57 // |RecentTabsSubMenuModel::model_| doesn't include IDC_RESTORE_TAB and the
58 // separator after.
59 const int kNumItemsNotInModel = 2;
60 // |model_index| indexes into |model_|, which doesn't include
61 // kNumItemsNotInModel, so add that to get index in menu.
62 return model_index + kNumItemsNotInModel;
63 }
64
65 } // namepace
66
67 // An element in |RecentTabsSubMenuModel::model_| that stores the navigation
68 // information of a local or foreign tab required to restore the tab.
69 // Because |model_| maps to the actual menu items for easier and faster access,
70 // it also stores device section headers and separators, which use -1 for tab_id
71 // to indicate non-navigatable (and hence non-executable) items.
72 struct RecentTabsSubMenuModel::NavigationItem {
73 NavigationItem() : tab_id(-1) {}
74
75 NavigationItem(const std::string& session_tag,
76 const SessionID::id_type& tab_id,
77 const GURL& url)
78 : session_tag(session_tag),
79 tab_id(tab_id),
80 url(url) {}
81
82 // For use by std::set for sorting.
83 bool operator<(const NavigationItem& other) const {
84 return url < other.url;
85 }
86
87 std::string session_tag; // Empty for local tabs, non-empty for foreign tabs.
88 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
89 GURL url;
90 };
91
92 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
93 ui::AcceleratorProvider* accelerator_provider,
94 Browser* browser)
95 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
96 browser_(browser),
97 associator_(NULL),
98 default_favicon_(NULL),
99 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
100 Build();
101
102 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
103 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
104 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
105 #if defined(USE_ASH)
106 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
107 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
108 if (accel_data.action == ash::RESTORE_TAB) {
109 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
110 accel_data.modifiers);
111 break;
112 }
113 }
114 #else
115 accelerator_provider->GetAcceleratorForCommandId(
116 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
117 #endif // defined(USE_ASH)
118 }
119
120 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
121 ui::AcceleratorProvider* accelerator_provider,
122 Browser* browser,
123 browser_sync::SessionModelAssociator* associator,
124 bool setup_for_test)
125 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
126 browser_(browser),
127 associator_(associator),
128 default_favicon_(NULL),
129 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
130 DCHECK(associator_);
131 DCHECK(setup_for_test);
132 Build();
133 }
134
135 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
136 }
137
138 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
139 return false;
140 }
141
142 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
143 if (command_id == IDC_RESTORE_TAB)
144 return chrome::IsCommandEnabled(browser_, command_id);
145 // An empty |model_| means there's no menu item to enable.
146 if (model_.empty())
147 return false;
148 // If |command_id| is 0, it's a device section header to be disabled.
149 return command_id != 0;
150 }
151
152 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
153 int command_id, ui::Accelerator* accelerator) {
154 if (command_id == IDC_RESTORE_TAB &&
155 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
156 *accelerator = reopen_closed_tab_accelerator_;
157 return true;
158 }
159 return false;
160 }
161
162 bool RecentTabsSubMenuModel::IsItemForCommandIdDynamic(int command_id) const {
163 return command_id == IDC_RESTORE_TAB;
164 }
165
166 string16 RecentTabsSubMenuModel::GetLabelForCommandId(int command_id) const {
167 DCHECK_EQ(command_id, IDC_RESTORE_TAB);
168
169 int string_id = IDS_RESTORE_TAB;
170 if (IsCommandIdEnabled(command_id)) {
171 TabRestoreService* service =
172 TabRestoreServiceFactory::GetForProfile(browser_->profile());
173 if (service &&
sky 2012/11/09 18:13:05 Check the size here.
kuan 2012/11/09 20:27:52 size is checked in CanRestoreTab which is called f
174 service->entries().front()->type == TabRestoreService::WINDOW) {
175 string_id = IDS_RESTORE_WINDOW;
176 }
177 }
178 return l10n_util::GetStringUTF16(string_id);
179 }
180
181 void RecentTabsSubMenuModel::ExecuteCommand(int command_id) {
182 ExecuteCommand(command_id, 0);
183 }
184
185 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
186 if (command_id == IDC_RESTORE_TAB) {
187 chrome::ExecuteCommandWithDisposition(browser_, command_id,
188 chrome::DispositionFromEventFlags(event_flags));
189 return;
190 }
191
192 // See IsCommandIdEnabled for explanation.
193 DCHECK(!model_.empty() && command_id > 0);
sky 2012/11/09 18:13:05 How about starting at a particular command id for
kuan 2012/11/09 20:27:52 Done.
194
195 DCHECK_LT(command_id, static_cast<int>(model_.size()));
196 const NavigationItem& item = model_[command_id];
197 DCHECK(item.tab_id > -1 && item.url.is_valid());
198
199 WindowOpenDisposition disposition =
200 chrome::DispositionFromEventFlags(event_flags);
201 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
202 disposition = NEW_FOREGROUND_TAB;
203
204 if (item.session_tag.empty()) { // Restore tab of local session.
205 TabRestoreService* service =
206 TabRestoreServiceFactory::GetForProfile(browser_->profile());
207 if (!service)
208 return;
209 TabRestoreServiceDelegate* delegate =
210 TabRestoreServiceDelegate::FindDelegateForWebContents(
211 chrome::GetActiveWebContents(browser_));
212 if (!delegate)
213 return;
214 service->RestoreEntryById(delegate, item.tab_id, disposition);
215 } else { // Restore tab of foreign session.
216 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
217 if (!associator)
218 return;
219 const SessionTab* tab;
220 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
221 return;
222 if (tab->navigations.empty())
223 return;
224 int prev_num_tabs = browser_->tab_strip_model()->count();
225 SessionRestore::RestoreForeignSessionTab(
226 chrome::GetActiveWebContents(browser_), *tab, disposition);
227 if (browser_->tab_strip_model()->count() == prev_num_tabs + 1)
228 chrome::ActivateTabAt(browser_, prev_num_tabs, true);
229 }
230 }
231
232 void RecentTabsSubMenuModel::Build() {
233 // The menu contains:
234 // - Reopen closed tab, then separator
235 // - device 1 section header, then list of tabs from device, then separator
236 // - device 2 section header, then list of tabs from device, then separator
237 // ...
238 // |model_| only contains items for other devices:
239 // - section header for device name (command_id = 0)
240 // - tabs from other devices (command_id = index into |model_|, which will
241 // never be 0 since there would be always be a section header)
242 // - separator that separates tabs of each device (comand id = -1)
243 BuildLastClosed();
244 BuildDevices();
245 if (model_.empty())
246 AddItemWithStringId(0, IDS_RECENT_TABS_NO_DEVICE_TABS);
247 }
248
249 void RecentTabsSubMenuModel::BuildLastClosed() {
250 AddItem(IDC_RESTORE_TAB, GetLabelForCommandId(IDC_RESTORE_TAB));
251 AddSeparator(ui::NORMAL_SEPARATOR);
252 }
253
254 void RecentTabsSubMenuModel::BuildDevices() {
255 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
256 if (!associator)
257 return;
258
259 std::vector<const browser_sync::SyncedSession*> sessions;
260 if (!associator->GetAllForeignSessions(&sessions))
261 return;
262
263 // Sort sessions from most recent to least recent.
264 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
265
266 const size_t kMaxSessionsToShow = 3;
267 bool need_separator = false;
268 for (size_t i = 0; i < std::min(sessions.size(), kMaxSessionsToShow); ++i) {
sky 2012/11/09 18:13:05 Shouldn't you only consider sessions you actually
kuan 2012/11/09 20:27:52 Done.
269 const browser_sync::SyncedSession* session = sessions[i];
270 const std::string& session_tag = session->session_tag;
271
272 // Get windows of session.
273 std::vector<const SessionWindow*> windows;
274 if (!associator->GetForeignSession(session_tag, &windows) ||
275 windows.empty()) {
276 continue;
277 }
278
279 // Sort tabs in all windows of session from most recent to least recent,
280 // independent of which window the tabs were from.
281 std::vector<SessionTab*> tabs_in_session;
282 for (size_t j = 0; j < windows.size(); ++j) {
283 tabs_in_session.insert(tabs_in_session.end(), windows[j]->tabs.begin(),
284 windows[j]->tabs.end());
285 }
286 if (tabs_in_session.empty())
287 continue;
288 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
289 SortTabsByRecency);
290
291 // Build tab menu items from sorted session tabs.
292 const size_t kMaxTabsPerSessionToShow = 4;
293 for (size_t k = 0;
294 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
295 ++k) {
296 if (BuildForeignTabItem(session_tag, *tabs_in_session[k],
297 // Only need |session_name| for the first tab of the session.
298 !k ? session->session_name : std::string(), need_separator)) {
sky 2012/11/09 18:13:05 Since BuildForeightTabItem may return false, your
kuan 2012/11/09 20:27:52 duh.. i had previously counted the tabs added, the
sky 2012/11/09 21:44:56 By prune I meant remove ones that match the criter
kuan 2012/11/09 23:25:05 Done.
299 need_separator = false;
300 }
301 } // for all tabs in one session
302
303 need_separator = true;
304 } // for all sessions
305 }
306
307 bool RecentTabsSubMenuModel::BuildForeignTabItem(
308 const std::string& session_tag,
309 const SessionTab& tab,
310 const std::string& session_name,
311 bool need_separator) {
312 if (tab.navigations.empty())
313 return false;
314
315 int selected_index = tab.normalized_navigation_index();
316 const TabNavigation& current_navigation = tab.navigations.at(selected_index);
317 const GURL& tab_url = current_navigation.virtual_url();
318 if (tab_url == GURL(chrome::kChromeUINewTabURL))
319 return false;
320
321 if (need_separator) {
322 AddSeparator(ui::NORMAL_SEPARATOR);
323 model_.push_back(NavigationItem());
sky 2012/11/09 18:13:05 Why do you need to add an item to the model here a
kuan 2012/11/09 20:27:52 Done.
324 }
325
326 if (!session_name.empty()) {
327 AddItem(0, UTF8ToUTF16(session_name));
328 model_.push_back(NavigationItem());
329 }
330
331 NavigationItem item(session_tag, tab.tab_id.id(),
332 current_navigation.virtual_url());
333 const int kMaxTabTitleWidth = 320;
334 string16 tab_title = ui::ElideText(current_navigation.title(), gfx::Font(),
335 kMaxTabTitleWidth, ui::ELIDE_AT_END);
336 AddItem(model_.size(), tab_title);
337 AddFavicon(model_.size(), item.url);
338 model_.push_back(item);
339 return true;
340 }
341
342 void RecentTabsSubMenuModel::AddFavicon(int model_index, const GURL& url) {
343 if (!default_favicon_) {
344 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
345 default_favicon_ = &rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON);
sky 2012/11/09 18:13:05 Is there a compelling reason to make this a pointe
kuan 2012/11/09 20:27:52 Done.
346 }
347 // Set default icon first.
348 SetIcon(ModelIndexToMenuIndex(model_index), *default_favicon_);
349 // Start request to fetch actual icon if possible.
350 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
351 browser_->profile(), Profile::EXPLICIT_ACCESS);
352 if (!favicon_service)
353 return;
354 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
sky 2012/11/09 18:13:05 See email from Kai about this being deprecated.
kuan 2012/11/09 20:27:52 i just saw that; however he hasn't changed Favicon
355 FaviconService::FaviconForURLParams(browser_->profile(), url,
356 history::FAVICON, gfx::kFaviconSize, &favicon_consumer_),
357 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
358 weak_ptr_factory_.GetWeakPtr()));
359 favicon_consumer_.SetClientData(favicon_service, handle, model_index);
360 }
361
362 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
363 FaviconService::Handle handle,
364 const history::FaviconImageResult& image_result) {
365 if (image_result.image.IsEmpty())
366 return;
367 DCHECK(!model_.empty());
368 int model_index = favicon_consumer_.GetClientData(
369 FaviconServiceFactory::GetForProfile(browser_->profile(),
370 Profile::EXPLICIT_ACCESS),
371 handle);
372 DCHECK(model_index > 0 && model_index < static_cast<int>(model_.size()));
373 DCHECK(model_[model_index].tab_id > -1 && model_[model_index].url.is_valid());
374 int index_in_menu = ModelIndexToMenuIndex(model_index);
375 SetIcon(index_in_menu, image_result.image);
376 if (menu_model_delegate())
377 menu_model_delegate()->OnIconChanged(index_in_menu);
378 }
379
380 browser_sync::SessionModelAssociator*
381 RecentTabsSubMenuModel::GetModelAssociator() {
382 if (!associator_) {
383 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
384 GetForProfile(browser_->profile());
385 // Only return the associator if it exists and it is done syncing sessions.
386 if (service && service->ShouldPushChanges())
387 associator_ = service->GetSessionModelAssociator();
388 }
389 return associator_;
390 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698