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

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 accel key for reopen tab - it broke unittest 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/common/pref_names.h"
25 #include "chrome/common/time_format.h"
26 #include "chrome/common/url_constants.h"
27 #include "grit/generated_resources.h"
28 #include "grit/ui_resources.h"
29 #include "ui/base/accelerators/accelerator.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/base/text/text_elider.h"
33 #include "ui/gfx/favicon_size.h"
34
35 #if defined(USE_ASH)
36 #include "ash/accelerators/accelerator_table.h"
37 #endif // defined(USE_ASH)
38
39 namespace {
40
41 // Comparator function for use with std::sort that will sort sessions by
42 // descending modified_time (i.e., most recent first).
43 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
44 const browser_sync::SyncedSession* s2) {
45 return s1->modified_time > s2->modified_time;
46 }
47
48 // Helper function that returns the largest tab timestamp for the window.
49 base::Time GetMostRecentTabTimestamp(const SessionWindow* window) {
50 base::Time max_timestamp;
51 for (size_t i = 0, num_tabs = window->tabs.size(); i < num_tabs; ++i) {
52 SessionTab* tab = window->tabs[i];
53 if (tab->timestamp > max_timestamp)
54 max_timestamp = tab->timestamp;
55 }
56 return max_timestamp;
57 }
58
59 // Comparator function to sort windows by last-modified time. Windows in a
60 // session share the same timestamp, so we need to use tab timestamps instead.
61 // instead.
62 bool SortWindowsByRecency(const SessionWindow* w1, const SessionWindow* w2) {
63 return GetMostRecentTabTimestamp(w1) > GetMostRecentTabTimestamp(w2);
64 }
65
66 // Convert |model_index| into index in menu, since model and menu are not 1-1.
67 int ModelIndexToMenuIndex(int model_index) {
68 // |RecentTabsSubMenuModel::model_| doesn't include IDC_RESTORE_TAB and the
69 // separator after.
70 const int kNumItemsNotInModel = 2;
71 // |model_index| indexes into |model_|, which doesn't include
72 // kNumItemsNotInModel, so add that to get index in menu.
73 return model_index + kNumItemsNotInModel;
74 }
75
76 } // namepace
77
78 // An element in |RecentTabsSubMenuModel::model_| that stores the navigation
79 // information of a local or foreign tab required to restore the tab.
80 // Because |model_| maps to the actual menu items for easier and faster access,
81 // it also stores device section headers and separators, which use -1 for tab_id
82 // to indicate non-navigatable (and hence non-executable) items.
83 struct RecentTabsSubMenuModel::NavigationItem {
84 NavigationItem() : tab_id(-1) {}
85
86 NavigationItem(const std::string& session_tag,
87 const SessionID::id_type& tab_id,
88 const GURL& url)
89 : session_tag(session_tag),
90 tab_id(tab_id),
91 url(url) {}
92
93 // For use by std::set for sorting.
94 bool operator<(const NavigationItem& other) const {
95 return url < other.url;
96 }
97
98 std::string session_tag; // Empty for local tabs, non-empty for foreign tabs.
99 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
100 GURL url;
101 };
102
103 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
104 ui::AcceleratorProvider* accelerator_provider,
105 Browser* browser)
106 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
107 browser_(browser),
108 default_favicon_(NULL),
109 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
110 Build();
111
112 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
113 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
114 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
115 #if defined(USE_ASH)
116 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
117 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
118 if (accel_data.action == ash::RESTORE_TAB) {
119 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
120 accel_data.modifiers);
121 break;
122 }
123 }
124 #else
125 accelerator_provider->GetAcceleratorForCommandId(
126 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
127 #endif // defined(USE_ASH)
128 }
129
130 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
131 weak_ptr_factory_.InvalidateWeakPtrs();
sky 2012/11/07 22:33:40 Isn't this done automatically for you?
kuan 2012/11/08 22:57:54 Done.
132 }
133
134 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
135 return false;
136 }
137
138 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
139 if (command_id == IDC_RESTORE_TAB)
140 return chrome::IsCommandEnabled(browser_, command_id);
141 // An empty |model_| means there's no menu item to enable.
142 if (model_.empty())
143 return false;
144 // If |command_id| is 0, it's a device section header to be disabled.
145 return command_id != 0;
146 }
147
148 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
149 int command_id, ui::Accelerator* accelerator) {
150 if (command_id == IDC_RESTORE_TAB &&
151 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
152 *accelerator = reopen_closed_tab_accelerator_;
153 return true;
154 }
155 return false;
156 }
157
158 bool RecentTabsSubMenuModel::IsItemForCommandIdDynamic(int command_id) const {
159 return command_id == IDC_RESTORE_TAB;
160 }
161
162 string16 RecentTabsSubMenuModel::GetLabelForCommandId(int command_id) const {
163 DCHECK_EQ(command_id, IDC_RESTORE_TAB);
164
165 int string_id = IDS_RESTORE_TAB;
166 if (IsCommandIdEnabled(command_id)) {
167 TabRestoreService* service =
168 TabRestoreServiceFactory::GetForProfile(browser_->profile());
169 if (service &&
170 service->entries().front()->type == TabRestoreService::WINDOW) {
171 string_id = IDS_RESTORE_WINDOW;
172 }
173 }
174 return l10n_util::GetStringUTF16(string_id);
175 }
176
177 void RecentTabsSubMenuModel::ExecuteCommand(int command_id) {
178 ExecuteCommand(command_id, 0);
179 }
180
181 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
182 if (command_id == IDC_RESTORE_TAB) {
183 chrome::ExecuteCommandWithDisposition(browser_, command_id,
184 chrome::DispositionFromEventFlags(event_flags));
185 return;
186 }
187
188 // See IsCommandIdEnabled for explanation.
189 DCHECK(!model_.empty() && command_id > 0);
190
191 DCHECK_LT(command_id, static_cast<int>(model_.size()));
192 const NavigationItem& item = model_[command_id];
193 DCHECK(item.tab_id > -1 && item.url.is_valid());
194
195 if (item.session_tag.empty()) { // Restore tab of local session.
196 TabRestoreService* service =
197 TabRestoreServiceFactory::GetForProfile(browser_->profile());
198 if (!service)
199 return;
200 TabRestoreServiceDelegate* delegate =
201 TabRestoreServiceDelegate::FindDelegateForWebContents(
202 chrome::GetActiveWebContents(browser_));
203 if (!delegate)
204 return;
205 service->RestoreEntryById(delegate, item.tab_id,
206 chrome::DispositionFromEventFlags(event_flags));
207 } else { // Restore tab of foreign session.
208 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
209 if (!associator)
210 return;
211 const SessionTab* tab;
212 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
213 return;
214 if (tab->navigations.empty())
215 return;
216 SessionRestore::RestoreForeignSessionTab(
217 chrome::GetActiveWebContents(browser_), *tab,
218 chrome::DispositionFromEventFlags(event_flags));
219 }
220 }
221
222 void RecentTabsSubMenuModel::Build() {
223 // The menu contains:
224 // - Reopen closed tab, then separator
225 // - device 1 section header, then list of tabs from device, then separator
226 // - device 2 section header, then list of tabs from device, then separator
227 // ...
228 // |model_| only contains items for other devices:
229 // - section header for device name (command_id = 0)
230 // - tabs from other devices (command_id = index into |model_|, which will
231 // never be 0 since there would be always be a section header)
232 // - separator that separates tabs of each device (comand id = -1)
233 BuildLastClosed();
234 BuildDevices();
235 if (model_.empty())
236 AddItemWithStringId(0, IDS_RECENT_TABS_NO_DEVICE_TABS);
237 }
238
239 void RecentTabsSubMenuModel::BuildLastClosed() {
240 AddItem(IDC_RESTORE_TAB, GetLabelForCommandId(IDC_RESTORE_TAB));
241 AddSeparator(ui::NORMAL_SEPARATOR);
242 }
243
244 void RecentTabsSubMenuModel::BuildDevices() {
245 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
246 if (!associator)
247 return;
248
249 std::vector<const browser_sync::SyncedSession*> sessions;
250 if (!associator->GetAllForeignSessions(&sessions))
251 return;
252
253 // Sort sessions from most recent to least recent.
254 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
255
256 const size_t kMaxSessionsToShow = 10;
257 bool need_separator = false;
258 for (size_t i = 0; i < sessions.size() && i < kMaxSessionsToShow; ++i) {
259 const browser_sync::SyncedSession* session = sessions[i];
260 const std::string& session_tag = session->session_tag;
261
262 // Get windows of session.
263 std::vector<const SessionWindow*> windows;
264 if (!associator->GetForeignSession(session_tag, &windows) ||
265 windows.empty()) {
266 continue;
267 }
268 // Sort windows from most recent to least recent, based on tabs in windows.
269 std::sort(windows.begin(), windows.end(), SortWindowsByRecency);
270
271 const int kMaxTabsPerSessionToShow = 18;
sky 2012/11/07 22:33:40 Does this mean we might show 180 items?!?
kuan 2012/11/08 22:57:54 i've changed as per spec from cole who spoke w/ gl
272 int num_tabs_in_session = 0;
273 for (size_t j = 0;
274 j < windows.size() && num_tabs_in_session < kMaxTabsPerSessionToShow;
275 ++j) {
276 const SessionWindow* window = windows[j];
277 // Get tabs of window.
278 for (size_t k = 0;
279 k < window->tabs.size() &&
280 num_tabs_in_session < kMaxTabsPerSessionToShow;
281 ++k) {
282 if (BuildForeignTabItem(session_tag, *window->tabs[k],
283 // Only need |session_name| for the first tab of the session.
284 !num_tabs_in_session ? session->session_name : std::string(),
285 need_separator)) {
286 need_separator = false;
287 ++num_tabs_in_session;
288 }
289 } // for all tabs of one window
290 } // for all windows of one session
291
292 if (num_tabs_in_session > 0)
293 need_separator = true;
294 } // for all sessions
295 }
296
297 bool RecentTabsSubMenuModel::BuildForeignTabItem(
298 const std::string& session_tag,
299 const SessionTab& tab,
300 const std::string& session_name,
301 bool need_separator) {
302 if (tab.navigations.empty())
303 return false;
304
305 int selected_index = std::min(tab.current_navigation_index,
306 static_cast<int>(tab.navigations.size() - 1));
307 const TabNavigation& current_navigation = tab.navigations.at(selected_index);
308 const GURL& tab_url = current_navigation.virtual_url();
309 if (tab_url == GURL(chrome::kChromeUINewTabURL))
310 return false;
311
312 if (need_separator) {
313 AddSeparator(ui::NORMAL_SEPARATOR);
314 model_.push_back(NavigationItem());
315 }
316
317 if (!session_name.empty()) {
318 AddItem(0, UTF8ToUTF16(session_name));
319 model_.push_back(NavigationItem());
320 }
321
322 NavigationItem item(session_tag, tab.tab_id.id(),
323 current_navigation.virtual_url());
324 const int kMaxTabTitleWidth = 320;
325 string16 tab_title = ui::ElideText(current_navigation.title(), gfx::Font(),
326 kMaxTabTitleWidth, ui::ELIDE_AT_END);
327 AddItem(model_.size(), tab_title);
328 AddFavicon(model_.size(), item.url);
329 model_.push_back(item);
330 return true;
331 }
332
333 void RecentTabsSubMenuModel::AddFavicon(int model_index, const GURL& url) {
334 if (!default_favicon_) {
335 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
336 default_favicon_ = &rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON);
337 }
338 // Set default icon first.
339 SetIcon(ModelIndexToMenuIndex(model_index), *default_favicon_);
340 // Start request to fetch actual icon if possible.
341 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
342 browser_->profile(), Profile::EXPLICIT_ACCESS);
343 if (!favicon_service)
344 return;
345 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
346 FaviconService::FaviconForURLParams(browser_->profile(), url,
347 history::FAVICON, gfx::kFaviconSize, &favicon_consumer_),
348 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
349 weak_ptr_factory_.GetWeakPtr()));
350 favicon_consumer_.SetClientData(favicon_service, handle, model_index);
351 }
352
353 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
354 FaviconService::Handle handle,
355 const history::FaviconImageResult& image_result) {
356 if (image_result.image.IsEmpty())
357 return;
358 DCHECK(!model_.empty());
359 int model_index = favicon_consumer_.GetClientData(
360 FaviconServiceFactory::GetForProfile(browser_->profile(),
361 Profile::EXPLICIT_ACCESS),
362 handle);
363 DCHECK(model_index > 0 && model_index < static_cast<int>(model_.size()));
364 DCHECK(model_[model_index].tab_id > -1 && model_[model_index].url.is_valid());
365 int index_in_menu = ModelIndexToMenuIndex(model_index);
366 SetIcon(index_in_menu, image_result.image);
367 if (menu_model_delegate())
368 menu_model_delegate()->OnIconChanged(index_in_menu);
369 }
370
371 browser_sync::SessionModelAssociator*
372 RecentTabsSubMenuModel::GetModelAssociator() const {
373 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
374 GetForProfile(browser_->profile());
375 // Only return the associator if it exists and it is done syncing sessions.
376 return service && service->ShouldPushChanges() ?
377 service->GetSessionModelAssociator() : NULL;
378 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698