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

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: rebased to resolve conflicts 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, separators and executable tab items.
46 // - model only has navigatabale (and hence executable) tab items.
47 // Using an initial command id for tab items makes it easier and less error-
48 // 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 browser_sync::SessionModelAssociator* associator)
112 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
113 browser_(browser),
114 associator_(associator),
115 default_favicon_(ResourceBundle::GetSharedInstance().
116 GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
117 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
118 Build();
119
120 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
121 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
122 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
123 #if defined(USE_ASH)
124 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
125 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
126 if (accel_data.action == ash::RESTORE_TAB) {
127 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
128 accel_data.modifiers);
129 break;
130 }
131 }
132 #else
133 accelerator_provider->GetAcceleratorForCommandId(
134 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
135 #endif // defined(USE_ASH)
136 }
137
138 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
139 }
140
141 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
142 return false;
143 }
144
145 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
146 if (command_id == IDC_RESTORE_TAB)
147 return chrome::IsCommandEnabled(browser_, command_id);
148 if (command_id == kDisabledCommandId)
149 return false;
150 int model_index = CommandIdToModelIndex(command_id);
151 return model_index >= 0 && model_index < static_cast<int>(model_.size());
152 }
153
154 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
155 int command_id, ui::Accelerator* accelerator) {
156 if (command_id == IDC_RESTORE_TAB &&
157 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
158 *accelerator = reopen_closed_tab_accelerator_;
159 return true;
160 }
161 return false;
162 }
163
164 bool RecentTabsSubMenuModel::IsItemForCommandIdDynamic(int command_id) const {
165 return command_id == IDC_RESTORE_TAB;
166 }
167
168 string16 RecentTabsSubMenuModel::GetLabelForCommandId(int command_id) const {
169 DCHECK_EQ(command_id, IDC_RESTORE_TAB);
170
171 int string_id = IDS_RESTORE_TAB;
172 if (IsCommandIdEnabled(command_id)) {
173 TabRestoreService* service =
174 TabRestoreServiceFactory::GetForProfile(browser_->profile());
175 if (service && !service->entries().empty() &&
176 service->entries().front()->type == TabRestoreService::WINDOW) {
177 string_id = IDS_RESTORE_WINDOW;
178 }
179 }
180 return l10n_util::GetStringUTF16(string_id);
181 }
182
183 void RecentTabsSubMenuModel::ExecuteCommand(int command_id) {
184 ExecuteCommand(command_id, 0);
185 }
186
187 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
188 if (command_id == IDC_RESTORE_TAB) {
189 chrome::ExecuteCommandWithDisposition(browser_, command_id,
190 chrome::DispositionFromEventFlags(event_flags));
191 return;
192 }
193
194 DCHECK(command_id != kDisabledCommandId);
195 int model_idx = CommandIdToModelIndex(command_id);
196 DCHECK(model_idx >= 0 && model_idx < static_cast<int>(model_.size()));
197 const NavigationItem& item = model_[model_idx];
198 DCHECK(item.tab_id > -1 && item.url.is_valid());
199
200 WindowOpenDisposition disposition =
201 chrome::DispositionFromEventFlags(event_flags);
202 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
203 disposition = NEW_FOREGROUND_TAB;
204
205 if (item.session_tag.empty()) { // Restore tab of local session.
206 TabRestoreService* service =
207 TabRestoreServiceFactory::GetForProfile(browser_->profile());
208 if (!service)
209 return;
210 TabRestoreServiceDelegate* delegate =
211 TabRestoreServiceDelegate::FindDelegateForWebContents(
212 chrome::GetActiveWebContents(browser_));
213 if (!delegate)
214 return;
215 service->RestoreEntryById(delegate, item.tab_id, disposition);
216 } else { // Restore tab of foreign session.
217 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
218 if (!associator)
219 return;
220 const SessionTab* tab;
221 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
222 return;
223 if (tab->navigations.empty())
224 return;
225 int prev_num_tabs = browser_->tab_strip_model()->count();
226 SessionRestore::RestoreForeignSessionTab(
227 chrome::GetActiveWebContents(browser_), *tab, disposition);
228 // If tab is successfully added (i.e. there's 1 more tab), select it.
229 if (browser_->tab_strip_model()->count() == prev_num_tabs + 1)
230 chrome::SelectNextTab(browser_);
231 }
232 }
233
234 void RecentTabsSubMenuModel::Build() {
235 // The menu contains:
236 // - Reopen closed tab, then separator
237 // - device 1 section header, then list of tabs from device, then separator
238 // - device 2 section header, then list of tabs from device, then separator
239 // ...
240 // |model_| only contains navigatable (and hence executable) tab items for
241 // other devices.
242 BuildLastClosed();
243 BuildDevices();
244 if (model_.empty())
245 AddItemWithStringId(kDisabledCommandId, IDS_RECENT_TABS_NO_DEVICE_TABS);
246 }
247
248 void RecentTabsSubMenuModel::BuildLastClosed() {
249 AddItem(IDC_RESTORE_TAB, GetLabelForCommandId(IDC_RESTORE_TAB));
250 AddSeparator(ui::NORMAL_SEPARATOR);
251 }
252
253 void RecentTabsSubMenuModel::BuildDevices() {
254 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
255 if (!associator)
256 return;
257
258 std::vector<const browser_sync::SyncedSession*> sessions;
259 if (!associator->GetAllForeignSessions(&sessions))
260 return;
261
262 // Sort sessions from most recent to least recent.
263 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
264
265 const size_t kMaxSessionsToShow = 3;
266 size_t num_sessions_added = 0;
267 bool need_separator = false;
268 for (size_t i = 0;
269 i < sessions.size() && num_sessions_added < kMaxSessionsToShow;
270 ++i) {
271 const browser_sync::SyncedSession* session = sessions[i];
272 const std::string& session_tag = session->session_tag;
273
274 // Get windows of session.
275 std::vector<const SessionWindow*> windows;
276 if (!associator->GetForeignSession(session_tag, &windows) ||
277 windows.empty()) {
278 continue;
279 }
280
281 // Collect tabs from all windows of session, pruning those that are not
282 // syncable or are NewTabPage, then sort them from most recent to least
283 // recent, independent of which window the tabs were from.
284 std::vector<const SessionTab*> tabs_in_session;
285 for (size_t j = 0; j < windows.size(); ++j) {
286 const SessionWindow* window = windows[j];
287 for (size_t t = 0; t < window->tabs.size(); ++t) {
288 const SessionTab* tab = window->tabs[t];
289 if (tab->navigations.empty())
290 continue;
291 const TabNavigation& current_navigation =
292 tab->navigations.at(tab->normalized_navigation_index());
293 if (current_navigation.virtual_url() ==
294 GURL(chrome::kChromeUINewTabURL)) {
295 continue;
296 }
297 tabs_in_session.push_back(tab);
298 }
299 }
300 if (tabs_in_session.empty())
301 continue;
302 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
303 SortTabsByRecency);
304
305 // Build tab menu items from sorted session tabs.
306 const size_t kMaxTabsPerSessionToShow = 4;
307 for (size_t k = 0;
308 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
309 ++k) {
310 BuildForeignTabItem(session_tag, *tabs_in_session[k],
311 // Only need |session_name| for the first tab of the session.
312 !k ? session->session_name : std::string(), need_separator);
313 need_separator = false;
314 } // for all tabs in one session
315
316 ++num_sessions_added;
317 need_separator = true;
318 } // for all sessions
319 }
320
321 void RecentTabsSubMenuModel::BuildForeignTabItem(
322 const std::string& session_tag,
323 const SessionTab& tab,
324 const std::string& session_name,
325 bool need_separator) {
326 if (need_separator)
327 AddSeparator(ui::NORMAL_SEPARATOR);
328
329 if (!session_name.empty())
330 AddItem(kDisabledCommandId, UTF8ToUTF16(session_name));
331
332 const TabNavigation& current_navigation =
333 tab.navigations.at(tab.normalized_navigation_index());
334 NavigationItem item(session_tag, tab.tab_id.id(),
335 current_navigation.virtual_url());
336 int command_id = ModelIndexToCommandId(model_.size());
337 AddItem(command_id, current_navigation.title());
338 AddFavicon(model_.size(), command_id, item.url);
339 model_.push_back(item);
340 }
341
342 void RecentTabsSubMenuModel::AddFavicon(int model_index, int command_id,
343 const GURL& url) {
344 // Set default icon first.
345 SetIcon(GetIndexOfCommandId(command_id), default_favicon_);
346 // Start request to fetch actual icon if possible.
347 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
348 browser_->profile(), Profile::EXPLICIT_ACCESS);
349 if (!favicon_service)
350 return;
351 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
352 FaviconService::FaviconForURLParams(browser_->profile(), url,
353 history::FAVICON, gfx::kFaviconSize, &favicon_consumer_),
354 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
355 weak_ptr_factory_.GetWeakPtr()));
356 favicon_consumer_.SetClientData(favicon_service, handle, command_id);
357 }
358
359 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
360 FaviconService::Handle handle,
361 const history::FaviconImageResult& image_result) {
362 if (image_result.image.IsEmpty())
363 return;
364 DCHECK(!model_.empty());
365 int command_id = favicon_consumer_.GetClientData(
366 FaviconServiceFactory::GetForProfile(browser_->profile(),
367 Profile::EXPLICIT_ACCESS),
368 handle);
369 int index_in_menu = GetIndexOfCommandId(command_id);
370 DCHECK(index_in_menu != -1);
371 SetIcon(index_in_menu, image_result.image);
372 if (menu_model_delegate())
373 menu_model_delegate()->OnIconChanged(index_in_menu);
374 }
375
376 browser_sync::SessionModelAssociator*
377 RecentTabsSubMenuModel::GetModelAssociator() {
378 if (!associator_) {
379 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
380 GetForProfile(browser_->profile());
381 // Only return the associator if it exists and it is done syncing sessions.
382 if (service && service->ShouldPushChanges())
383 associator_ = service->GetSessionModelAssociator();
384 }
385 return associator_;
386 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698