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

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

Issue 1182493009: Wrench menu reorg phase 2 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix incorrect title case expr causing wrong strings to be displayed on OSX Created 5 years, 6 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
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h" 5 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/metrics/histogram.h" 8 #include "base/metrics/histogram.h"
9 #include "base/prefs/scoped_user_pref_update.h" 9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/string_number_conversions.h"
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId; 62 const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId;
63 const int kFirstLocalWindowCommandId = 1031; 63 const int kFirstLocalWindowCommandId = 1031;
64 const int kFirstOtherDevicesTabCommandId = 1051; 64 const int kFirstOtherDevicesTabCommandId = 1051;
65 const int kMinDeviceNameCommandId = 1100; 65 const int kMinDeviceNameCommandId = 1100;
66 const int kMaxDeviceNameCommandId = 1110; 66 const int kMaxDeviceNameCommandId = 1110;
67 67
68 // The maximum number of local recently closed entries (tab or window) to be 68 // The maximum number of local recently closed entries (tab or window) to be
69 // shown in the menu. 69 // shown in the menu.
70 const int kMaxLocalEntries = 8; 70 const int kMaxLocalEntries = 8;
71 71
72 // Starting index for local tab entries. Takes into account the history menu
73 // item and separator.
74 const int kLocalEntriesStartIndex = 1;
Peter Kasting 2015/06/17 22:59:42 This seems like a technically-inaccurate name give
edwardjung 2015/06/18 16:52:05 Renamed.
75
72 // Comparator function for use with std::sort that will sort sessions by 76 // Comparator function for use with std::sort that will sort sessions by
73 // descending modified_time (i.e., most recent first). 77 // descending modified_time (i.e., most recent first).
74 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1, 78 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
75 const browser_sync::SyncedSession* s2) { 79 const browser_sync::SyncedSession* s2) {
76 return s1->modified_time > s2->modified_time; 80 return s1->modified_time > s2->modified_time;
77 } 81 }
78 82
79 // Comparator function for use with std::sort that will sort tabs by 83 // Comparator function for use with std::sort that will sort tabs by
80 // descending timestamp (i.e., most recent first). 84 // descending timestamp (i.e., most recent first).
81 bool SortTabsByRecency(const sessions::SessionTab* t1, 85 bool SortTabsByRecency(const sessions::SessionTab* t1,
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
164 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120; 168 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120;
165 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121; 169 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121;
166 170
167 RecentTabsSubMenuModel::RecentTabsSubMenuModel( 171 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
168 ui::AcceleratorProvider* accelerator_provider, 172 ui::AcceleratorProvider* accelerator_provider,
169 Browser* browser, 173 Browser* browser,
170 browser_sync::OpenTabsUIDelegate* open_tabs_delegate) 174 browser_sync::OpenTabsUIDelegate* open_tabs_delegate)
171 : ui::SimpleMenuModel(this), 175 : ui::SimpleMenuModel(this),
172 browser_(browser), 176 browser_(browser),
173 open_tabs_delegate_(open_tabs_delegate), 177 open_tabs_delegate_(open_tabs_delegate),
174 last_local_model_index_(-1), 178 last_local_model_index_(kLocalEntriesStartIndex),
175 default_favicon_(ui::ResourceBundle::GetSharedInstance(). 179 default_favicon_(ui::ResourceBundle::GetSharedInstance().
176 GetNativeImageNamed(IDR_DEFAULT_FAVICON)), 180 GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
177 weak_ptr_factory_(this) { 181 weak_ptr_factory_(this) {
178 // Invoke asynchronous call to load tabs from local last session, which does 182 // Invoke asynchronous call to load tabs from local last session, which does
179 // nothing if the tabs have already been loaded or they shouldn't be loaded. 183 // nothing if the tabs have already been loaded or they shouldn't be loaded.
180 // TabRestoreServiceChanged() will be called after the tabs are loaded. 184 // TabRestoreServiceChanged() will be called after the tabs are loaded.
181 TabRestoreService* service = 185 TabRestoreService* service =
182 TabRestoreServiceFactory::GetForProfile(browser_->profile()); 186 TabRestoreServiceFactory::GetForProfile(browser_->profile());
183 if (service) { 187 if (service) {
184 service->LoadTabsFromLastSession(); 188 service->LoadTabsFromLastSession();
(...skipping 16 matching lines...) Expand all
201 if (accel_data.action == ash::RESTORE_TAB) { 205 if (accel_data.action == ash::RESTORE_TAB) {
202 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode, 206 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
203 accel_data.modifiers); 207 accel_data.modifiers);
204 break; 208 break;
205 } 209 }
206 } 210 }
207 #else 211 #else
208 if (accelerator_provider) { 212 if (accelerator_provider) {
209 accelerator_provider->GetAcceleratorForCommandId( 213 accelerator_provider->GetAcceleratorForCommandId(
210 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_); 214 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
215 accelerator_provider->GetAcceleratorForCommandId(
216 IDC_SHOW_HISTORY, &show_history_accelerator_);
Peter Kasting 2015/06/17 22:59:42 This block duplicates the one just below
edwardjung 2015/06/18 16:52:05 Removed.
211 } 217 }
212 #endif // defined(USE_ASH) 218 #endif // defined(USE_ASH)
219
220 if (accelerator_provider) {
221 accelerator_provider->GetAcceleratorForCommandId(
222 IDC_SHOW_HISTORY, &show_history_accelerator_);
223 }
213 } 224 }
214 225
215 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() { 226 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
216 TabRestoreService* service = 227 TabRestoreService* service =
217 TabRestoreServiceFactory::GetForProfile(browser_->profile()); 228 TabRestoreServiceFactory::GetForProfile(browser_->profile());
218 if (service) 229 if (service)
219 service->RemoveObserver(this); 230 service->RemoveObserver(this);
220 } 231 }
221 232
222 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const { 233 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
(...skipping 14 matching lines...) Expand all
237 int command_id, ui::Accelerator* accelerator) { 248 int command_id, ui::Accelerator* accelerator) {
238 // If there are no recently closed items, we show the accelerator beside 249 // If there are no recently closed items, we show the accelerator beside
239 // the header, otherwise, we show it beside the first item underneath it. 250 // the header, otherwise, we show it beside the first item underneath it.
240 int index_in_menu = GetIndexOfCommandId(command_id); 251 int index_in_menu = GetIndexOfCommandId(command_id);
241 int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId); 252 int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
242 if ((command_id == kDisabledRecentlyClosedHeaderCommandId || 253 if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
243 (header_index != -1 && index_in_menu == header_index + 1)) && 254 (header_index != -1 && index_in_menu == header_index + 1)) &&
244 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) { 255 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
245 *accelerator = reopen_closed_tab_accelerator_; 256 *accelerator = reopen_closed_tab_accelerator_;
246 return true; 257 return true;
258 } else if (command_id == IDC_SHOW_HISTORY) {
Peter Kasting 2015/06/17 22:59:42 Nit: No else after return
edwardjung 2015/06/18 16:52:05 Done.
259 *accelerator = show_history_accelerator_;
260 return true;
247 } 261 }
262
248 return false; 263 return false;
249 } 264 }
250 265
251 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) { 266 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
252 if (command_id == IDC_SHOW_HISTORY) { 267 if (command_id == IDC_SHOW_HISTORY) {
253 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE, 268 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
254 LIMIT_RECENT_TAB_ACTION); 269 LIMIT_RECENT_TAB_ACTION);
255 // We show all "other devices" on the history page. 270 // We show all "other devices" on the history page.
256 chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY, 271 chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
257 ui::DispositionFromEventFlags(event_flags)); 272 ui::DispositionFromEventFlags(event_flags));
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
359 const TabNavigationItem& item = (*tab_items)[tab_items_idx]; 374 const TabNavigationItem& item = (*tab_items)[tab_items_idx];
360 *url = item.url.possibly_invalid_spec(); 375 *url = item.url.possibly_invalid_spec();
361 *title = item.title; 376 *title = item.title;
362 return true; 377 return true;
363 } 378 }
364 return false; 379 return false;
365 } 380 }
366 381
367 void RecentTabsSubMenuModel::Build() { 382 void RecentTabsSubMenuModel::Build() {
368 // The menu contains: 383 // The menu contains:
384 // - History to open the full history tab.
385 // - Separator
369 // - Recently closed header, then list of local recently closed tabs/windows, 386 // - Recently closed header, then list of local recently closed tabs/windows,
370 // then separator 387 // then separator
371 // - device 1 section header, then list of tabs from device, then separator 388 // - device 1 section header, then list of tabs from device, then separator
372 // - device 2 section header, then list of tabs from device, then separator 389 // - device 2 section header, then list of tabs from device, then separator
373 // - device 3 section header, then list of tabs from device, then separator 390 // - device 3 section header, then list of tabs from device, then separator
374 // - More... to open the history tab to get more other devices.
375 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_| 391 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
376 // only contain navigatable (and hence executable) tab items for local 392 // only contain navigatable (and hence executable) tab items for local
377 // recently closed tabs and tabs from other devices respectively. 393 // recently closed tabs and tabs from other devices respectively.
378 // |local_window_items_| contains the local recently closed windows. 394 // |local_window_items_| contains the local recently closed windows.
395 // Add the History item to top of the menu followed by a separator.
Peter Kasting 2015/06/17 22:59:42 Nit: This sentence is redundant with the comments
edwardjung 2015/06/18 16:52:05 Done.
396 InsertItemWithStringIdAt(0, IDC_SHOW_HISTORY, IDS_SHOW_HISTORY);
397 InsertSeparatorAt(1, ui::NORMAL_SEPARATOR);
398
Peter Kasting 2015/06/17 22:59:42 Nit: I'd also remove this blank line
edwardjung 2015/06/18 16:52:05 Done.
379 BuildLocalEntries(); 399 BuildLocalEntries();
380 BuildTabsFromOtherDevices(); 400 BuildTabsFromOtherDevices();
381 } 401 }
382 402
383 void RecentTabsSubMenuModel::BuildLocalEntries() { 403 void RecentTabsSubMenuModel::BuildLocalEntries() {
404 // Assigning the correct index for inserting items.
405 // Always starts at the 2 second item in the list.
Peter Kasting 2015/06/17 22:59:42 These comments don't make much sense, plus they se
edwardjung 2015/06/18 16:52:05 Removed.
406 last_local_model_index_ = kLocalEntriesStartIndex;
Peter Kasting 2015/06/17 22:59:42 Nit: Do this right above the DCHECK
edwardjung 2015/06/18 16:52:05 Done.
407
384 // All local items use InsertItem*At() to append or insert a menu item. 408 // All local items use InsertItem*At() to append or insert a menu item.
385 // We're appending if building the entries for the first time i.e. invoked 409 // We're appending if building the entries for the first time i.e. invoked
386 // from Constructor(), inserting when local entries change subsequently i.e. 410 // from Constructor(), inserting when local entries change subsequently i.e.
387 // invoked from TabRestoreServiceChanged(). 411 // invoked from TabRestoreServiceChanged().
388
389 DCHECK_EQ(last_local_model_index_, -1);
390
391 TabRestoreService* service = 412 TabRestoreService* service =
392 TabRestoreServiceFactory::GetForProfile(browser_->profile()); 413 TabRestoreServiceFactory::GetForProfile(browser_->profile());
414
415 DCHECK_EQ(last_local_model_index_, 1);
Peter Kasting 2015/06/17 22:59:42 Nit: (expected, actual) Though, I don't see why y
edwardjung 2015/06/18 16:52:05 Good point, removed.
393 if (!service || service->entries().size() == 0) { 416 if (!service || service->entries().size() == 0) {
394 // This is to show a disabled restore tab entry with the accelerator to 417 // This is to show a disabled restore tab entry with the accelerator to
395 // teach users about this command. 418 // teach users about this command.
396 InsertItemWithStringIdAt(++last_local_model_index_, 419 InsertItemWithStringIdAt(++last_local_model_index_,
397 kDisabledRecentlyClosedHeaderCommandId, 420 kDisabledRecentlyClosedHeaderCommandId,
398 IDS_RECENTLY_CLOSED); 421 IDS_RECENTLY_CLOSED);
399 } else { 422 } else {
400 InsertItemWithStringIdAt(++last_local_model_index_, 423 InsertItemWithStringIdAt(++last_local_model_index_,
401 kRecentlyClosedHeaderCommandId, 424 kRecentlyClosedHeaderCommandId,
402 IDS_RECENTLY_CLOSED); 425 IDS_RECENTLY_CLOSED);
426
Peter Kasting 2015/06/17 22:59:42 Nit: Why did you add this?
edwardjung 2015/06/18 16:52:05 Removed.
403 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 427 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
404 SetIcon(last_local_model_index_, 428 SetIcon(last_local_model_index_,
405 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW)); 429 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
406 430
407 int added_count = 0; 431 int added_count = 0;
408 TabRestoreService::Entries entries = service->entries(); 432 TabRestoreService::Entries entries = service->entries();
409 for (TabRestoreService::Entries::const_iterator it = entries.begin(); 433 for (TabRestoreService::Entries::const_iterator it = entries.begin();
410 it != entries.end() && added_count < kMaxLocalEntries; ++it) { 434 it != entries.end() && added_count < kMaxLocalEntries; ++it) {
411 TabRestoreService::Entry* entry = *it; 435 TabRestoreService::Entry* entry = *it;
412 if (entry->type == TabRestoreService::TAB) { 436 if (entry->type == TabRestoreService::TAB) {
413 TabRestoreService::Tab* tab = 437 TabRestoreService::Tab* tab =
414 static_cast<TabRestoreService::Tab*>(entry); 438 static_cast<TabRestoreService::Tab*>(entry);
415 const sessions::SerializedNavigationEntry& current_navigation = 439 const sessions::SerializedNavigationEntry& current_navigation =
416 tab->navigations.at(tab->current_navigation_index); 440 tab->navigations.at(tab->current_navigation_index);
417 BuildLocalTabItem( 441 BuildLocalTabItem(
418 entry->id, 442 entry->id,
419 current_navigation.title(), 443 current_navigation.title(),
420 current_navigation.virtual_url(), 444 current_navigation.virtual_url(),
421 ++last_local_model_index_); 445 ++last_local_model_index_);
422 } else { 446 } else {
423 DCHECK_EQ(entry->type, TabRestoreService::WINDOW); 447 DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
424 BuildLocalWindowItem( 448 BuildLocalWindowItem(
425 entry->id, 449 entry->id,
426 static_cast<TabRestoreService::Window*>(entry)->tabs.size(), 450 static_cast<TabRestoreService::Window*>(entry)->tabs.size(),
427 ++last_local_model_index_); 451 ++last_local_model_index_);
428 } 452 }
429 ++added_count; 453 ++added_count;
430 } 454 }
431 } 455 }
432
433 DCHECK_GE(last_local_model_index_, 0); 456 DCHECK_GE(last_local_model_index_, 0);
434 } 457 }
435 458
436 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() { 459 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() {
437 // All other devices' items (device headers or tabs) use AddItem*() to append 460 // All other devices' items (device headers or tabs) use AddItem*() to append
438 // a menu item, because they are always only built once (i.e. invoked from 461 // a menu item, because they are always only built once (i.e. invoked from
439 // Constructor()) and don't change after that. 462 // Constructor()) and don't change after that.
440 463
441 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(); 464 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
442 std::vector<const browser_sync::SyncedSession*> sessions; 465 std::vector<const browser_sync::SyncedSession*> sessions;
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
502 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow); 525 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
503 ++k) { 526 ++k) {
504 BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]); 527 BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]);
505 } // for all tabs in one session 528 } // for all tabs in one session
506 529
507 ++num_sessions_added; 530 ++num_sessions_added;
508 } // for all sessions 531 } // for all sessions
509 532
510 // We are not supposed to get here unless at least some items were added. 533 // We are not supposed to get here unless at least some items were added.
511 DCHECK_GT(GetItemCount(), 0); 534 DCHECK_GT(GetItemCount(), 0);
512 AddSeparator(ui::NORMAL_SEPARATOR);
513 AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE);
514 } 535 }
515 536
516 void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id, 537 void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id,
517 const base::string16& title, 538 const base::string16& title,
518 const GURL& url, 539 const GURL& url,
519 int curr_model_index) { 540 int curr_model_index) {
520 TabNavigationItem item(std::string(), session_id, title, url); 541 TabNavigationItem item(std::string(), session_id, title, url);
521 int command_id = TabVectorIndexToCommandId( 542 int command_id = TabVectorIndexToCommandId(
522 local_tab_navigation_items_.size(), kFirstLocalTabCommandId); 543 local_tab_navigation_items_.size(), kFirstLocalTabCommandId);
523 // See comments in BuildLocalEntries() about usage of InsertItem*At(). 544 // See comments in BuildLocalEntries() about usage of InsertItem*At().
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
647 if (command_id >= kFirstOtherDevicesTabCommandId) { 668 if (command_id >= kFirstOtherDevicesTabCommandId) {
648 *tab_items = &other_devices_tab_navigation_items_; 669 *tab_items = &other_devices_tab_navigation_items_;
649 return command_id - kFirstOtherDevicesTabCommandId; 670 return command_id - kFirstOtherDevicesTabCommandId;
650 } 671 }
651 *tab_items = &local_tab_navigation_items_; 672 *tab_items = &local_tab_navigation_items_;
652 return command_id - kFirstLocalTabCommandId; 673 return command_id - kFirstLocalTabCommandId;
653 } 674 }
654 675
655 void RecentTabsSubMenuModel::ClearLocalEntries() { 676 void RecentTabsSubMenuModel::ClearLocalEntries() {
656 // Remove local items (recently closed tabs and windows) from menumodel. 677 // Remove local items (recently closed tabs and windows) from menumodel.
657 while (last_local_model_index_ >= 0) 678 while (last_local_model_index_ > kLocalEntriesStartIndex)
658 RemoveItemAt(last_local_model_index_--); 679 RemoveItemAt(last_local_model_index_--);
659 680
660 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of 681 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of
661 // all 682 // all local tabs.
662 // local tabs.
663 local_tab_cancelable_task_tracker_.TryCancelAll(); 683 local_tab_cancelable_task_tracker_.TryCancelAll();
664 684
665 // Remove all local tab navigation items. 685 // Remove all local tab navigation items.
666 local_tab_navigation_items_.clear(); 686 local_tab_navigation_items_.clear();
667 687
668 // Remove all local window items. 688 // Remove all local window items.
669 local_window_items_.clear(); 689 local_window_items_.clear();
670 } 690 }
671 691
672 browser_sync::OpenTabsUIDelegate* 692 browser_sync::OpenTabsUIDelegate*
(...skipping 16 matching lines...) Expand all
689 709
690 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate(); 710 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate();
691 if (menu_model_delegate) 711 if (menu_model_delegate)
692 menu_model_delegate->OnMenuStructureChanged(); 712 menu_model_delegate->OnMenuStructureChanged();
693 } 713 }
694 714
695 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed( 715 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
696 TabRestoreService* service) { 716 TabRestoreService* service) {
697 TabRestoreServiceChanged(service); 717 TabRestoreServiceChanged(service);
698 } 718 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698