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

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: addressed fred's comments 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 // First comamnd id for navigatable (and hence executable) tab menu item.
43 // The model and menu are not 1-1:
44 // - menu has "Reopen closed tab", "No tabs from other devices", device section
45 // headers and separtors
46 // - model only has navigatabale (and hence executable) tab items.
47 // Using an initial command id for tab items makes it easier and less
48 // error-prone to manipulate the model and menu.
49 // This value must be bigger than the maximum possible number of items in menu,
50 // so that index of last menu item doesn't clash with this value when menu items
51 // are retrieved via GetIndexOfCommandId.
52 const int kFirstTabCommandId = 100;
53
54 // Command Id for disabled menu items, e.g. device section header,
55 // "No tabs from other devices", etc.
56 const int kDisabledCommandId = 1000;
57
58 // Comparator function for use with std::sort that will sort sessions by
59 // descending modified_time (i.e., most recent first).
60 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
61 const browser_sync::SyncedSession* s2) {
62 return s1->modified_time > s2->modified_time;
63 }
64
65 // Comparator function for use with std::sort that will sort tabs by
66 // descending timestamp (i.e., most recent first).
67 bool SortTabsByRecency(const SessionTab* t1, const SessionTab* t2) {
68 return t1->timestamp > t2->timestamp;
69 }
70
71 // Convert |model_index| to command id of menu item.
72 int ModelIndexToCommandId(int model_index) {
73 int command_id = model_index + kFirstTabCommandId;
74 DCHECK(command_id != kDisabledCommandId);
75 return command_id;
76 }
77
78 // Convert |command_id| of menu item to index in model.
79 int CommandIdToModelIndex(int command_id) {
80 DCHECK(command_id != kDisabledCommandId);
81 return command_id - kFirstTabCommandId;
82 }
83
84 } // namepace
85
86 // An element in |RecentTabsSubMenuModel::model_| that stores the navigation
87 // information of a local or foreign tab required to restore the tab.
88 struct RecentTabsSubMenuModel::NavigationItem {
89 NavigationItem() : tab_id(-1) {}
90
91 NavigationItem(const std::string& session_tag,
92 const SessionID::id_type& tab_id,
93 const GURL& url)
94 : session_tag(session_tag),
95 tab_id(tab_id),
96 url(url) {}
97
98 // For use by std::set for sorting.
99 bool operator<(const NavigationItem& other) const {
100 return url < other.url;
101 }
102
103 std::string session_tag; // Empty for local tabs, non-empty for foreign tabs.
104 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
105 GURL url;
106 };
107
108 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
109 ui::AcceleratorProvider* accelerator_provider,
110 Browser* browser)
111 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
112 browser_(browser),
113 associator_(NULL),
114 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
115 Build();
116
117 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
118 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
119 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
120 #if defined(USE_ASH)
121 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
122 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
123 if (accel_data.action == ash::RESTORE_TAB) {
124 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
125 accel_data.modifiers);
126 break;
127 }
128 }
129 #else
130 accelerator_provider->GetAcceleratorForCommandId(
131 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
132 #endif // defined(USE_ASH)
133 }
134
135 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
136 ui::AcceleratorProvider* accelerator_provider,
137 Browser* browser,
138 browser_sync::SessionModelAssociator* associator,
139 bool setup_for_test)
140 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
141 browser_(browser),
142 associator_(associator),
143 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
144 DCHECK(associator_);
145 DCHECK(setup_for_test);
sky 2012/11/12 04:13:03 Why the DCHECK for setup_for_test? If always true,
kuan 2012/11/12 13:24:26 Done. i was following what SessionModelAssociator
146 Build();
147 }
148
149 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
150 }
151
152 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
153 return false;
154 }
155
156 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
157 if (command_id == IDC_RESTORE_TAB)
158 return chrome::IsCommandEnabled(browser_, command_id);
159 if (command_id == kDisabledCommandId)
160 return false;
161 int model_index = CommandIdToModelIndex(command_id);
162 return model_index >= 0 && model_index < static_cast<int>(model_.size());
163 }
164
165 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
166 int command_id, ui::Accelerator* accelerator) {
167 if (command_id == IDC_RESTORE_TAB &&
168 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
169 *accelerator = reopen_closed_tab_accelerator_;
170 return true;
171 }
172 return false;
173 }
174
175 bool RecentTabsSubMenuModel::IsItemForCommandIdDynamic(int command_id) const {
176 return command_id == IDC_RESTORE_TAB;
177 }
178
179 string16 RecentTabsSubMenuModel::GetLabelForCommandId(int command_id) const {
180 DCHECK_EQ(command_id, IDC_RESTORE_TAB);
181
182 int string_id = IDS_RESTORE_TAB;
183 if (IsCommandIdEnabled(command_id)) {
184 TabRestoreService* service =
185 TabRestoreServiceFactory::GetForProfile(browser_->profile());
186 if (service &&
sky 2012/11/12 04:13:03 !service->entries().empty()
kuan 2012/11/12 13:24:26 Done.
187 service->entries().front()->type == TabRestoreService::WINDOW) {
188 string_id = IDS_RESTORE_WINDOW;
189 }
190 }
191 return l10n_util::GetStringUTF16(string_id);
192 }
193
194 void RecentTabsSubMenuModel::ExecuteCommand(int command_id) {
195 ExecuteCommand(command_id, 0);
196 }
197
198 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
199 if (command_id == IDC_RESTORE_TAB) {
200 chrome::ExecuteCommandWithDisposition(browser_, command_id,
201 chrome::DispositionFromEventFlags(event_flags));
202 return;
203 }
204
205 DCHECK(command_id != kDisabledCommandId);
206 int model_idx = CommandIdToModelIndex(command_id);
207 DCHECK(model_idx >= 0 && model_idx < static_cast<int>(model_.size()));
208 const NavigationItem& item = model_[model_idx];
209 DCHECK(item.tab_id > -1 && item.url.is_valid());
210
211 WindowOpenDisposition disposition =
212 chrome::DispositionFromEventFlags(event_flags);
213 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
214 disposition = NEW_FOREGROUND_TAB;
215
216 if (item.session_tag.empty()) { // Restore tab of local session.
217 TabRestoreService* service =
218 TabRestoreServiceFactory::GetForProfile(browser_->profile());
219 if (!service)
220 return;
221 TabRestoreServiceDelegate* delegate =
222 TabRestoreServiceDelegate::FindDelegateForWebContents(
223 chrome::GetActiveWebContents(browser_));
224 if (!delegate)
225 return;
226 service->RestoreEntryById(delegate, item.tab_id, disposition);
227 } else { // Restore tab of foreign session.
228 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
229 if (!associator)
230 return;
231 const SessionTab* tab;
232 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
233 return;
234 if (tab->navigations.empty())
235 return;
236 int prev_num_tabs = browser_->tab_strip_model()->count();
237 SessionRestore::RestoreForeignSessionTab(
238 chrome::GetActiveWebContents(browser_), *tab, disposition);
239 // If tab is successfully added (i.e. there's 1 more tab), select it.
240 if (browser_->tab_strip_model()->count() == prev_num_tabs + 1)
241 chrome::SelectNextTab(browser_);
242 }
243 }
244
245 void RecentTabsSubMenuModel::Build() {
246 // The menu contains:
247 // - Reopen closed tab, then separator
248 // - device 1 section header, then list of tabs from device, then separator
249 // - device 2 section header, then list of tabs from device, then separator
250 // ...
251 // |model_| only contains navigatable (and hence executable) tab items for
252 // other devices.
253 BuildLastClosed();
254 BuildDevices();
255 if (model_.empty())
256 AddItemWithStringId(kDisabledCommandId, IDS_RECENT_TABS_NO_DEVICE_TABS);
257 }
258
259 void RecentTabsSubMenuModel::BuildLastClosed() {
260 AddItem(IDC_RESTORE_TAB, GetLabelForCommandId(IDC_RESTORE_TAB));
261 AddSeparator(ui::NORMAL_SEPARATOR);
262 }
263
264 void RecentTabsSubMenuModel::BuildDevices() {
265 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
266 if (!associator)
267 return;
268
269 std::vector<const browser_sync::SyncedSession*> sessions;
270 if (!associator->GetAllForeignSessions(&sessions))
271 return;
272
273 // Sort sessions from most recent to least recent.
274 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
275
276 const size_t kMaxSessionsToShow = 3;
277 size_t num_sessions_added = 0;
278 bool need_separator = false;
279 for (size_t i = 0;
280 i < sessions.size() && num_sessions_added < kMaxSessionsToShow;
281 ++i) {
282 const browser_sync::SyncedSession* session = sessions[i];
283 const std::string& session_tag = session->session_tag;
284
285 // Get windows of session.
286 std::vector<const SessionWindow*> windows;
287 if (!associator->GetForeignSession(session_tag, &windows) ||
288 windows.empty()) {
289 continue;
290 }
291
292 // Collect tabs from all windows of session, pruning those that are not
293 // syncable or are NewTabPage, then sort them from most recent to least
294 // recent, independent of which window the tabs were from.
295 std::vector<const SessionTab*> tabs_in_session;
296 for (size_t j = 0; j < windows.size(); ++j) {
297 const SessionWindow* window = windows[j];
298 for (size_t t = 0; t < window->tabs.size(); ++t) {
299 const SessionTab* tab = window->tabs[t];
300 if (tab->navigations.empty())
301 continue;
302 const TabNavigation& current_navigation =
303 tab->navigations.at(tab->normalized_navigation_index());
304 if (current_navigation.virtual_url() ==
305 GURL(chrome::kChromeUINewTabURL)) {
306 continue;
307 }
308 tabs_in_session.push_back(tab);
309 }
310 }
311 if (tabs_in_session.empty())
312 continue;
313 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
314 SortTabsByRecency);
315
316 // Build tab menu items from sorted session tabs.
317 const size_t kMaxTabsPerSessionToShow = 4;
318 for (size_t k = 0;
319 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
320 ++k) {
321 BuildForeignTabItem(session_tag, *tabs_in_session[k],
322 // Only need |session_name| for the first tab of the session.
323 !k ? session->session_name : std::string(), need_separator);
324 need_separator = false;
325 } // for all tabs in one session
326
327 ++num_sessions_added;
328 need_separator = true;
329 } // for all sessions
330 }
331
332 void RecentTabsSubMenuModel::BuildForeignTabItem(
333 const std::string& session_tag,
334 const SessionTab& tab,
335 const std::string& session_name,
336 bool need_separator) {
337 if (need_separator)
338 AddSeparator(ui::NORMAL_SEPARATOR);
339
340 if (!session_name.empty())
341 AddItem(kDisabledCommandId, UTF8ToUTF16(session_name));
342
343 const TabNavigation& current_navigation =
344 tab.navigations.at(tab.normalized_navigation_index());
345 NavigationItem item(session_tag, tab.tab_id.id(),
346 current_navigation.virtual_url());
347 const int kMaxTabTitleWidth = 320;
sky 2012/11/12 04:13:03 How come you need this? Does the menu handle it fo
kuan 2012/11/12 13:24:26 i thought this would fix the text and menu item wi
348 string16 tab_title = ui::ElideText(current_navigation.title(), gfx::Font(),
349 kMaxTabTitleWidth, ui::ELIDE_AT_END);
350 int command_id = ModelIndexToCommandId(model_.size());
351 AddItem(command_id, tab_title);
352 AddFavicon(model_.size(), command_id, item.url);
353 model_.push_back(item);
354 }
355
356 void RecentTabsSubMenuModel::AddFavicon(int model_index, int command_id,
357 const GURL& url) {
358 if (default_favicon_.IsEmpty()) {
sky 2012/11/12 04:13:03 nit: move to constructor so you don't need to chec
kuan 2012/11/12 13:24:26 Done.
359 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
360 default_favicon_ = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON);
361 }
362 // Set default icon first.
363 SetIcon(GetIndexOfCommandId(command_id), default_favicon_);
364 // Start request to fetch actual icon if possible.
365 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
366 browser_->profile(), Profile::EXPLICIT_ACCESS);
367 if (!favicon_service)
368 return;
369 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
370 FaviconService::FaviconForURLParams(browser_->profile(), url,
371 history::FAVICON, gfx::kFaviconSize, &favicon_consumer_),
372 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
373 weak_ptr_factory_.GetWeakPtr()));
374 favicon_consumer_.SetClientData(favicon_service, handle, command_id);
375 }
376
377 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
378 FaviconService::Handle handle,
379 const history::FaviconImageResult& image_result) {
380 if (image_result.image.IsEmpty())
381 return;
382 DCHECK(!model_.empty());
383 int command_id = favicon_consumer_.GetClientData(
384 FaviconServiceFactory::GetForProfile(browser_->profile(),
385 Profile::EXPLICIT_ACCESS),
386 handle);
387 int index_in_menu = GetIndexOfCommandId(command_id);
388 DCHECK(index_in_menu != -1);
389 SetIcon(index_in_menu, image_result.image);
390 if (menu_model_delegate())
391 menu_model_delegate()->OnIconChanged(index_in_menu);
392 }
393
394 browser_sync::SessionModelAssociator*
395 RecentTabsSubMenuModel::GetModelAssociator() {
396 if (!associator_) {
397 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
398 GetForProfile(browser_->profile());
399 // Only return the associator if it exists and it is done syncing sessions.
400 if (service && service->ShouldPushChanges())
401 associator_ = service->GetSessionModelAssociator();
402 }
403 return associator_;
404 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698