ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / wrench_menu_model.cc
blob5f9ca12ae7d21024e8709f2d56fc691a8e654ba1
1 // Copyright (c) 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.
5 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/command_line.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/app/chrome_command_ids.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/defaults.h"
19 #include "chrome/browser/extensions/extension_util.h"
20 #include "chrome/browser/prefs/incognito_mode_prefs.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/profiles/profile_manager.h"
23 #include "chrome/browser/search/search.h"
24 #include "chrome/browser/signin/signin_manager_factory.h"
25 #include "chrome/browser/signin/signin_ui_util.h"
26 #include "chrome/browser/task_manager/task_manager.h"
27 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_commands.h"
30 #include "chrome/browser/ui/browser_finder.h"
31 #include "chrome/browser/ui/browser_window.h"
32 #include "chrome/browser/ui/global_error/global_error.h"
33 #include "chrome/browser/ui/global_error/global_error_service.h"
34 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
36 #include "chrome/browser/ui/toolbar/bookmark_sub_menu_model.h"
37 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
38 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
39 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
40 #include "chrome/browser/upgrade_detector.h"
41 #include "chrome/common/chrome_paths.h"
42 #include "chrome/common/chrome_switches.h"
43 #include "chrome/common/pref_names.h"
44 #include "chrome/common/profiling.h"
45 #include "chrome/grit/chromium_strings.h"
46 #include "chrome/grit/generated_resources.h"
47 #include "components/dom_distiller/core/dom_distiller_switches.h"
48 #include "components/signin/core/browser/signin_manager.h"
49 #include "components/signin/core/common/profile_management_switches.h"
50 #include "components/ui/zoom/zoom_controller.h"
51 #include "components/ui/zoom/zoom_event_manager.h"
52 #include "content/public/browser/host_zoom_map.h"
53 #include "content/public/browser/navigation_entry.h"
54 #include "content/public/browser/notification_service.h"
55 #include "content/public/browser/notification_source.h"
56 #include "content/public/browser/notification_types.h"
57 #include "content/public/browser/user_metrics.h"
58 #include "content/public/browser/web_contents.h"
59 #include "extensions/common/feature_switch.h"
60 #include "grit/theme_resources.h"
61 #include "ui/base/l10n/l10n_util.h"
62 #include "ui/base/layout.h"
63 #include "ui/base/models/button_menu_item_model.h"
64 #include "ui/base/resource/resource_bundle.h"
65 #include "ui/gfx/image/image.h"
66 #include "ui/gfx/image/image_skia.h"
68 #if defined(OS_CHROMEOS)
69 #include "chromeos/chromeos_switches.h"
70 #endif
72 #if defined(OS_WIN)
73 #include "base/win/metro.h"
74 #include "base/win/windows_version.h"
75 #include "chrome/browser/enumerate_modules_model_win.h"
76 #include "chrome/browser/ui/metro_pin_tab_helper_win.h"
77 #include "content/public/browser/gpu_data_manager.h"
78 #endif
80 #if defined(USE_ASH)
81 #include "ash/shell.h"
82 #endif
84 using base::UserMetricsAction;
85 using content::WebContents;
87 namespace {
89 #if defined(OS_MACOSX)
90 // An empty command used because of a bug in AppKit menus.
91 // See comment in CreateActionToolbarOverflowMenu().
92 const int kEmptyMenuItemCommand = 0;
93 #endif
95 // Conditionally return the update app menu item title based on upgrade detector
96 // state.
97 base::string16 GetUpgradeDialogMenuItemName() {
98 if (UpgradeDetector::GetInstance()->is_outdated_install() ||
99 UpgradeDetector::GetInstance()->is_outdated_install_no_au()) {
100 return l10n_util::GetStringUTF16(IDS_UPGRADE_BUBBLE_MENU_ITEM);
101 } else {
102 return l10n_util::GetStringUTF16(IDS_UPDATE_NOW);
106 #if defined(OS_WIN)
107 bool GetRestartMenuItemIfRequired(const chrome::HostDesktopType& desktop_type,
108 int* command_id,
109 int* string_id) {
110 if (base::win::GetVersion() == base::win::VERSION_WIN8 ||
111 base::win::GetVersion() == base::win::VERSION_WIN8_1) {
112 if (desktop_type != chrome::HOST_DESKTOP_TYPE_ASH) {
113 *command_id = IDC_WIN8_METRO_RESTART;
114 *string_id = IDS_WIN8_METRO_RESTART;
115 } else {
116 *command_id = IDC_WIN_DESKTOP_RESTART;
117 *string_id = IDS_WIN_DESKTOP_RESTART;
119 return true;
122 // Windows 7 ASH mode is only supported in DEBUG for now.
123 #if !defined(NDEBUG)
124 // Windows 8 can support ASH mode using WARP, but Windows 7 requires a working
125 // GPU compositor.
126 if (base::win::GetVersion() == base::win::VERSION_WIN7 &&
127 content::GpuDataManager::GetInstance()->CanUseGpuBrowserCompositor()) {
128 if (desktop_type != chrome::HOST_DESKTOP_TYPE_ASH) {
129 *command_id = IDC_WIN_CHROMEOS_RESTART;
130 *string_id = IDS_WIN_CHROMEOS_RESTART;
131 } else {
132 *command_id = IDC_WIN_DESKTOP_RESTART;
133 *string_id = IDS_WIN_DESKTOP_RESTART;
135 return true;
137 #endif
138 return false;
140 #endif
142 } // namespace
144 ////////////////////////////////////////////////////////////////////////////////
145 // EncodingMenuModel
147 EncodingMenuModel::EncodingMenuModel(Browser* browser)
148 : ui::SimpleMenuModel(this),
149 browser_(browser) {
150 Build();
153 EncodingMenuModel::~EncodingMenuModel() {
156 void EncodingMenuModel::Build() {
157 EncodingMenuController::EncodingMenuItemList encoding_menu_items;
158 EncodingMenuController encoding_menu_controller;
159 encoding_menu_controller.GetEncodingMenuItems(browser_->profile(),
160 &encoding_menu_items);
162 int group_id = 0;
163 EncodingMenuController::EncodingMenuItemList::iterator it =
164 encoding_menu_items.begin();
165 for (; it != encoding_menu_items.end(); ++it) {
166 int id = it->first;
167 base::string16& label = it->second;
168 if (id == 0) {
169 AddSeparator(ui::NORMAL_SEPARATOR);
170 } else {
171 if (id == IDC_ENCODING_AUTO_DETECT) {
172 AddCheckItem(id, label);
173 } else {
174 // Use the id of the first radio command as the id of the group.
175 if (group_id <= 0)
176 group_id = id;
177 AddRadioItem(id, label, group_id);
183 bool EncodingMenuModel::IsCommandIdChecked(int command_id) const {
184 WebContents* current_tab =
185 browser_->tab_strip_model()->GetActiveWebContents();
186 if (!current_tab)
187 return false;
188 EncodingMenuController controller;
189 return controller.IsItemChecked(browser_->profile(),
190 current_tab->GetEncoding(), command_id);
193 bool EncodingMenuModel::IsCommandIdEnabled(int command_id) const {
194 bool enabled = chrome::IsCommandEnabled(browser_, command_id);
195 // Special handling for the contents of the Encoding submenu. On Mac OS,
196 // instead of enabling/disabling the top-level menu item, the submenu's
197 // contents get disabled, per Apple's HIG.
198 #if defined(OS_MACOSX)
199 enabled &= chrome::IsCommandEnabled(browser_, IDC_ENCODING_MENU);
200 #endif
201 return enabled;
204 bool EncodingMenuModel::GetAcceleratorForCommandId(
205 int command_id,
206 ui::Accelerator* accelerator) {
207 return false;
210 void EncodingMenuModel::ExecuteCommand(int command_id, int event_flags) {
211 chrome::ExecuteCommand(browser_, command_id);
214 ////////////////////////////////////////////////////////////////////////////////
215 // ZoomMenuModel
217 ZoomMenuModel::ZoomMenuModel(ui::SimpleMenuModel::Delegate* delegate)
218 : SimpleMenuModel(delegate) {
219 Build();
222 ZoomMenuModel::~ZoomMenuModel() {
225 void ZoomMenuModel::Build() {
226 AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
227 AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL);
228 AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
231 ////////////////////////////////////////////////////////////////////////////////
232 // HelpMenuModel
234 #if defined(GOOGLE_CHROME_BUILD)
236 class WrenchMenuModel::HelpMenuModel : public ui::SimpleMenuModel {
237 public:
238 HelpMenuModel(ui::SimpleMenuModel::Delegate* delegate,
239 Browser* browser)
240 : SimpleMenuModel(delegate) {
241 Build(browser);
244 private:
245 void Build(Browser* browser) {
246 #if defined(OS_CHROMEOS) && defined(OFFICIAL_BUILD)
247 int help_string_id = IDS_GET_HELP;
248 #else
249 int help_string_id = IDS_HELP_PAGE;
250 #endif
251 AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
252 AddItemWithStringId(IDC_HELP_PAGE_VIA_MENU, help_string_id);
253 if (browser_defaults::kShowHelpMenuItemIcon) {
254 ui::ResourceBundle& rb = ResourceBundle::GetSharedInstance();
255 SetIcon(GetIndexOfCommandId(IDC_HELP_PAGE_VIA_MENU),
256 rb.GetNativeImageNamed(IDR_HELP_MENU));
258 AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
261 DISALLOW_COPY_AND_ASSIGN(HelpMenuModel);
264 #endif // defined(GOOGLE_CHROME_BUILD)
266 ////////////////////////////////////////////////////////////////////////////////
267 // ToolsMenuModel
269 ToolsMenuModel::ToolsMenuModel(ui::SimpleMenuModel::Delegate* delegate,
270 Browser* browser)
271 : SimpleMenuModel(delegate) {
272 Build(browser);
275 ToolsMenuModel::~ToolsMenuModel() {}
277 // More tools submenu is constructed as follows:
278 // - Page specific actions overflow (save page, adding to taskbar).
279 // - Browser / OS level tools (extensions, task manager).
280 // - Developer tools.
281 // - Option to enable profiling.
282 void ToolsMenuModel::Build(Browser* browser) {
283 bool show_create_shortcuts = true;
284 #if defined(OS_CHROMEOS) || defined(OS_MACOSX)
285 show_create_shortcuts = false;
286 #elif defined(USE_ASH)
287 if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
288 show_create_shortcuts = false;
289 #endif
290 AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE);
291 if (extensions::util::IsNewBookmarkAppsEnabled()) {
292 #if defined(OS_MACOSX)
293 int string_id = IDS_ADD_TO_APPLICATIONS;
294 #elif defined(OS_WIN)
295 int string_id = IDS_ADD_TO_TASKBAR;
296 #else
297 int string_id = IDS_ADD_TO_DESKTOP;
298 #endif
299 #if defined(USE_ASH)
300 if (browser->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
301 string_id = IDS_ADD_TO_SHELF;
302 #endif
303 AddItemWithStringId(IDC_CREATE_HOSTED_APP, string_id);
304 } else if (show_create_shortcuts) {
305 AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS);
308 AddSeparator(ui::NORMAL_SEPARATOR);
309 AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA);
310 AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS);
311 if (chrome::CanOpenTaskManager())
312 AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
313 #if defined(OS_CHROMEOS)
314 AddItemWithStringId(IDC_TAKE_SCREENSHOT, IDS_TAKE_SCREENSHOT);
315 #endif
316 encoding_menu_model_.reset(new EncodingMenuModel(browser));
317 AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU,
318 encoding_menu_model_.get());
320 AddSeparator(ui::NORMAL_SEPARATOR);
321 AddItemWithStringId(IDC_DEV_TOOLS, IDS_DEV_TOOLS);
323 #if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC)
324 AddSeparator(ui::NORMAL_SEPARATOR);
325 AddCheckItemWithStringId(IDC_PROFILING_ENABLED, IDS_PROFILING_ENABLED);
326 #endif
329 ////////////////////////////////////////////////////////////////////////////////
330 // WrenchMenuModel
332 WrenchMenuModel::WrenchMenuModel(ui::AcceleratorProvider* provider,
333 Browser* browser)
334 : ui::SimpleMenuModel(this),
335 uma_action_recorded_(false),
336 provider_(provider),
337 browser_(browser),
338 tab_strip_model_(browser_->tab_strip_model()) {
339 Build();
340 UpdateZoomControls();
342 browser_zoom_subscription_ =
343 ui_zoom::ZoomEventManager::GetForBrowserContext(browser->profile())
344 ->AddZoomLevelChangedCallback(base::Bind(
345 &WrenchMenuModel::OnZoomLevelChanged, base::Unretained(this)));
347 tab_strip_model_->AddObserver(this);
349 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
350 content::NotificationService::AllSources());
353 WrenchMenuModel::~WrenchMenuModel() {
354 if (tab_strip_model_)
355 tab_strip_model_->RemoveObserver(this);
358 bool WrenchMenuModel::DoesCommandIdDismissMenu(int command_id) const {
359 return command_id != IDC_ZOOM_MINUS && command_id != IDC_ZOOM_PLUS;
362 bool WrenchMenuModel::IsItemForCommandIdDynamic(int command_id) const {
363 return command_id == IDC_ZOOM_PERCENT_DISPLAY ||
364 #if defined(OS_MACOSX)
365 command_id == IDC_FULLSCREEN ||
366 #elif defined(OS_WIN)
367 command_id == IDC_PIN_TO_START_SCREEN ||
368 #endif
369 command_id == IDC_UPGRADE_DIALOG ||
370 (!switches::IsNewAvatarMenu() && command_id == IDC_SHOW_SIGNIN);
373 base::string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const {
374 switch (command_id) {
375 case IDC_ZOOM_PERCENT_DISPLAY:
376 return zoom_label_;
377 #if defined(OS_MACOSX)
378 case IDC_FULLSCREEN: {
379 int string_id = IDS_ENTER_FULLSCREEN_MAC; // Default to Enter.
380 // Note: On startup, |window()| may be NULL.
381 if (browser_->window() && browser_->window()->IsFullscreen())
382 string_id = IDS_EXIT_FULLSCREEN_MAC;
383 return l10n_util::GetStringUTF16(string_id);
385 #elif defined(OS_WIN)
386 case IDC_PIN_TO_START_SCREEN: {
387 int string_id = IDS_PIN_TO_START_SCREEN;
388 WebContents* web_contents =
389 browser_->tab_strip_model()->GetActiveWebContents();
390 MetroPinTabHelper* tab_helper =
391 web_contents ? MetroPinTabHelper::FromWebContents(web_contents)
392 : NULL;
393 if (tab_helper && tab_helper->IsPinned())
394 string_id = IDS_UNPIN_FROM_START_SCREEN;
395 return l10n_util::GetStringUTF16(string_id);
397 #endif
398 case IDC_UPGRADE_DIALOG:
399 return GetUpgradeDialogMenuItemName();
400 case IDC_SHOW_SIGNIN:
401 DCHECK(!switches::IsNewAvatarMenu());
402 return signin_ui_util::GetSigninMenuLabel(
403 browser_->profile()->GetOriginalProfile());
404 default:
405 NOTREACHED();
406 return base::string16();
410 bool WrenchMenuModel::GetIconForCommandId(int command_id,
411 gfx::Image* icon) const {
412 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
413 switch (command_id) {
414 case IDC_UPGRADE_DIALOG: {
415 if (UpgradeDetector::GetInstance()->notify_upgrade()) {
416 *icon = rb.GetNativeImageNamed(
417 UpgradeDetector::GetInstance()->GetIconResourceID());
418 return true;
420 return false;
422 case IDC_SHOW_SIGNIN: {
423 DCHECK(!switches::IsNewAvatarMenu());
424 GlobalError* error = signin_ui_util::GetSignedInServiceError(
425 browser_->profile()->GetOriginalProfile());
426 if (error) {
427 int icon_id = error->MenuItemIconResourceID();
428 if (icon_id) {
429 *icon = rb.GetNativeImageNamed(icon_id);
430 return true;
433 return false;
435 default:
436 break;
438 return false;
441 void WrenchMenuModel::ExecuteCommand(int command_id, int event_flags) {
442 GlobalError* error = GlobalErrorServiceFactory::GetForProfile(
443 browser_->profile())->GetGlobalErrorByMenuItemCommandID(command_id);
444 if (error) {
445 error->ExecuteMenuItem(browser_);
446 return;
449 if (!switches::IsNewAvatarMenu() && command_id == IDC_SHOW_SIGNIN) {
450 // If a custom error message is being shown, handle it.
451 GlobalError* error = signin_ui_util::GetSignedInServiceError(
452 browser_->profile()->GetOriginalProfile());
453 if (error) {
454 error->ExecuteMenuItem(browser_);
455 return;
459 LogMenuMetrics(command_id);
460 chrome::ExecuteCommand(browser_, command_id);
463 void WrenchMenuModel::LogMenuMetrics(int command_id) {
464 base::TimeDelta delta = timer_.Elapsed();
466 switch (command_id) {
467 case IDC_NEW_TAB:
468 if (!uma_action_recorded_)
469 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.NewTab", delta);
470 LogMenuAction(MENU_ACTION_NEW_TAB);
471 break;
472 case IDC_NEW_WINDOW:
473 if (!uma_action_recorded_)
474 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.NewWindow", delta);
475 LogMenuAction(MENU_ACTION_NEW_WINDOW);
476 break;
477 case IDC_NEW_INCOGNITO_WINDOW:
478 if (!uma_action_recorded_) {
479 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.NewIncognitoWindow",
480 delta);
482 LogMenuAction(MENU_ACTION_NEW_INCOGNITO_WINDOW);
483 break;
485 // Bookmarks sub menu.
486 case IDC_SHOW_BOOKMARK_BAR:
487 if (!uma_action_recorded_) {
488 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowBookmarkBar",
489 delta);
491 LogMenuAction(MENU_ACTION_SHOW_BOOKMARK_BAR);
492 break;
493 case IDC_SHOW_BOOKMARK_MANAGER:
494 if (!uma_action_recorded_) {
495 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowBookmarkMgr",
496 delta);
498 LogMenuAction(MENU_ACTION_SHOW_BOOKMARK_MANAGER);
499 break;
500 case IDC_IMPORT_SETTINGS:
501 if (!uma_action_recorded_) {
502 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ImportSettings",
503 delta);
505 LogMenuAction(MENU_ACTION_IMPORT_SETTINGS);
506 break;
507 case IDC_BOOKMARK_PAGE:
508 if (!uma_action_recorded_) {
509 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.BookmarkPage",
510 delta);
512 LogMenuAction(MENU_ACTION_BOOKMARK_PAGE);
513 break;
514 case IDC_BOOKMARK_ALL_TABS:
515 if (!uma_action_recorded_) {
516 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.BookmarkAllTabs",
517 delta);
519 LogMenuAction(MENU_ACTION_BOOKMARK_ALL_TABS);
520 break;
521 case IDC_PIN_TO_START_SCREEN:
522 if (!uma_action_recorded_) {
523 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.PinToStartScreen",
524 delta);
526 LogMenuAction(MENU_ACTION_PIN_TO_START_SCREEN);
527 break;
529 // Recent tabs menu.
530 case IDC_RESTORE_TAB:
531 if (!uma_action_recorded_)
532 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.RestoreTab", delta);
533 LogMenuAction(MENU_ACTION_RESTORE_TAB);
534 break;
536 // Windows.
537 case IDC_WIN_DESKTOP_RESTART:
538 if (!uma_action_recorded_) {
539 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.WinDesktopRestart",
540 delta);
542 LogMenuAction(MENU_ACTION_WIN_DESKTOP_RESTART);
543 break;
544 case IDC_WIN8_METRO_RESTART:
545 if (!uma_action_recorded_) {
546 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Win8MetroRestart",
547 delta);
549 LogMenuAction(MENU_ACTION_WIN8_METRO_RESTART);
550 break;
552 case IDC_WIN_CHROMEOS_RESTART:
553 if (!uma_action_recorded_) {
554 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ChromeOSRestart",
555 delta);
557 LogMenuAction(MENU_ACTION_WIN_CHROMEOS_RESTART);
558 break;
559 case IDC_DISTILL_PAGE:
560 if (!uma_action_recorded_) {
561 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.DistillPage",
562 delta);
564 LogMenuAction(MENU_ACTION_DISTILL_PAGE);
565 break;
566 case IDC_SAVE_PAGE:
567 if (!uma_action_recorded_)
568 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.SavePage", delta);
569 LogMenuAction(MENU_ACTION_SAVE_PAGE);
570 break;
571 case IDC_FIND:
572 if (!uma_action_recorded_)
573 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Find", delta);
574 LogMenuAction(MENU_ACTION_FIND);
575 break;
576 case IDC_PRINT:
577 if (!uma_action_recorded_)
578 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Print", delta);
579 LogMenuAction(MENU_ACTION_PRINT);
580 break;
582 // Edit menu.
583 case IDC_CUT:
584 if (!uma_action_recorded_)
585 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Cut", delta);
586 LogMenuAction(MENU_ACTION_CUT);
587 break;
588 case IDC_COPY:
589 if (!uma_action_recorded_)
590 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Copy", delta);
591 LogMenuAction(MENU_ACTION_COPY);
592 break;
593 case IDC_PASTE:
594 if (!uma_action_recorded_)
595 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Paste", delta);
596 LogMenuAction(MENU_ACTION_PASTE);
597 break;
599 // Tools menu.
600 case IDC_CREATE_HOSTED_APP:
601 if (!uma_action_recorded_) {
602 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.CreateHostedApp",
603 delta);
605 LogMenuAction(MENU_ACTION_CREATE_HOSTED_APP);
606 break;
607 case IDC_CREATE_SHORTCUTS:
608 if (!uma_action_recorded_)
609 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.CreateShortcuts",
610 delta);
611 LogMenuAction(MENU_ACTION_CREATE_SHORTCUTS);
612 break;
613 case IDC_MANAGE_EXTENSIONS:
614 if (!uma_action_recorded_) {
615 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ManageExtensions",
616 delta);
618 LogMenuAction(MENU_ACTION_MANAGE_EXTENSIONS);
619 break;
620 case IDC_TASK_MANAGER:
621 if (!uma_action_recorded_) {
622 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.TaskManager",
623 delta);
625 LogMenuAction(MENU_ACTION_TASK_MANAGER);
626 break;
627 case IDC_CLEAR_BROWSING_DATA:
628 if (!uma_action_recorded_) {
629 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ClearBrowsingData",
630 delta);
632 LogMenuAction(MENU_ACTION_CLEAR_BROWSING_DATA);
633 break;
634 case IDC_VIEW_SOURCE:
635 if (!uma_action_recorded_)
636 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ViewSource", delta);
637 LogMenuAction(MENU_ACTION_VIEW_SOURCE);
638 break;
639 case IDC_DEV_TOOLS:
640 if (!uma_action_recorded_)
641 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.DevTools", delta);
642 LogMenuAction(MENU_ACTION_DEV_TOOLS);
643 break;
644 case IDC_DEV_TOOLS_CONSOLE:
645 if (!uma_action_recorded_) {
646 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.DevToolsConsole",
647 delta);
649 LogMenuAction(MENU_ACTION_DEV_TOOLS_CONSOLE);
650 break;
651 case IDC_DEV_TOOLS_DEVICES:
652 if (!uma_action_recorded_) {
653 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.DevToolsDevices",
654 delta);
656 LogMenuAction(MENU_ACTION_DEV_TOOLS_DEVICES);
657 break;
658 case IDC_PROFILING_ENABLED:
659 if (!uma_action_recorded_) {
660 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ProfilingEnabled",
661 delta);
663 LogMenuAction(MENU_ACTION_PROFILING_ENABLED);
664 break;
666 // Zoom menu
667 case IDC_ZOOM_MINUS:
668 if (!uma_action_recorded_) {
669 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ZoomMinus", delta);
670 LogMenuAction(MENU_ACTION_ZOOM_MINUS);
672 break;
673 case IDC_ZOOM_PLUS:
674 if (!uma_action_recorded_) {
675 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ZoomPlus", delta);
676 LogMenuAction(MENU_ACTION_ZOOM_PLUS);
678 break;
679 case IDC_FULLSCREEN:
680 content::RecordAction(UserMetricsAction("EnterFullScreenWithWrenchMenu"));
682 if (!uma_action_recorded_) {
683 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.EnterFullScreen",
684 delta);
686 LogMenuAction(MENU_ACTION_FULLSCREEN);
687 break;
689 case IDC_SHOW_HISTORY:
690 if (!uma_action_recorded_) {
691 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowHistory",
692 delta);
694 LogMenuAction(MENU_ACTION_SHOW_HISTORY);
695 break;
696 case IDC_SHOW_DOWNLOADS:
697 if (!uma_action_recorded_) {
698 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowDownloads",
699 delta);
701 LogMenuAction(MENU_ACTION_SHOW_DOWNLOADS);
702 break;
703 case IDC_SHOW_SYNC_SETUP:
704 if (!uma_action_recorded_) {
705 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowSyncSetup",
706 delta);
708 LogMenuAction(MENU_ACTION_SHOW_SYNC_SETUP);
709 break;
710 case IDC_OPTIONS:
711 if (!uma_action_recorded_)
712 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Settings", delta);
713 LogMenuAction(MENU_ACTION_OPTIONS);
714 break;
715 case IDC_ABOUT:
716 if (!uma_action_recorded_)
717 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.About", delta);
718 LogMenuAction(MENU_ACTION_ABOUT);
719 break;
721 // Help menu.
722 case IDC_HELP_PAGE_VIA_MENU:
723 content::RecordAction(UserMetricsAction("ShowHelpTabViaWrenchMenu"));
725 if (!uma_action_recorded_)
726 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.HelpPage", delta);
727 LogMenuAction(MENU_ACTION_HELP_PAGE_VIA_MENU);
728 break;
729 #if defined(GOOGLE_CHROME_BUILD)
730 case IDC_FEEDBACK:
731 if (!uma_action_recorded_)
732 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Feedback", delta);
733 LogMenuAction(MENU_ACTION_FEEDBACK);
734 break;
735 #endif
737 case IDC_TOGGLE_REQUEST_TABLET_SITE:
738 if (!uma_action_recorded_) {
739 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.RequestTabletSite",
740 delta);
742 LogMenuAction(MENU_ACTION_TOGGLE_REQUEST_TABLET_SITE);
743 break;
744 case IDC_EXIT:
745 if (!uma_action_recorded_)
746 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.Exit", delta);
747 LogMenuAction(MENU_ACTION_EXIT);
748 break;
751 if (!uma_action_recorded_) {
752 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction", delta);
753 uma_action_recorded_ = true;
757 void WrenchMenuModel::LogMenuAction(int action_id) {
758 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction", action_id,
759 LIMIT_MENU_ACTION);
762 bool WrenchMenuModel::IsCommandIdChecked(int command_id) const {
763 if (command_id == IDC_SHOW_BOOKMARK_BAR) {
764 return browser_->profile()->GetPrefs()->GetBoolean(
765 bookmarks::prefs::kShowBookmarkBar);
766 } else if (command_id == IDC_PROFILING_ENABLED) {
767 return Profiling::BeingProfiled();
768 } else if (command_id == IDC_TOGGLE_REQUEST_TABLET_SITE) {
769 return chrome::IsRequestingTabletSite(browser_);
772 return false;
775 bool WrenchMenuModel::IsCommandIdEnabled(int command_id) const {
776 GlobalError* error = GlobalErrorServiceFactory::GetForProfile(
777 browser_->profile())->GetGlobalErrorByMenuItemCommandID(command_id);
778 if (error)
779 return true;
781 return chrome::IsCommandEnabled(browser_, command_id);
784 bool WrenchMenuModel::IsCommandIdVisible(int command_id) const {
785 switch (command_id) {
786 #if defined(OS_MACOSX)
787 case kEmptyMenuItemCommand:
788 return false; // Always hidden (see CreateActionToolbarOverflowMenu).
789 #endif
790 #if defined(OS_WIN)
791 case IDC_VIEW_INCOMPATIBILITIES: {
792 EnumerateModulesModel* loaded_modules =
793 EnumerateModulesModel::GetInstance();
794 if (loaded_modules->confirmed_bad_modules_detected() <= 0)
795 return false;
796 // We'll leave the wrench adornment on until the user clicks the link.
797 if (loaded_modules->modules_to_notify_about() <= 0)
798 loaded_modules->AcknowledgeConflictNotification();
799 return true;
801 case IDC_PIN_TO_START_SCREEN:
802 return base::win::IsMetroProcess();
803 #else
804 case IDC_VIEW_INCOMPATIBILITIES:
805 case IDC_PIN_TO_START_SCREEN:
806 return false;
807 #endif
808 case IDC_UPGRADE_DIALOG:
809 return browser_defaults::kShowUpgradeMenuItem &&
810 UpgradeDetector::GetInstance()->notify_upgrade();
811 #if !defined(OS_LINUX) || defined(USE_AURA)
812 case IDC_BOOKMARK_PAGE:
813 return !chrome::ShouldRemoveBookmarkThisPageUI(browser_->profile());
814 case IDC_BOOKMARK_ALL_TABS:
815 return !chrome::ShouldRemoveBookmarkOpenPagesUI(browser_->profile());
816 #endif
817 default:
818 return true;
822 bool WrenchMenuModel::GetAcceleratorForCommandId(
823 int command_id,
824 ui::Accelerator* accelerator) {
825 return provider_->GetAcceleratorForCommandId(command_id, accelerator);
828 void WrenchMenuModel::ActiveTabChanged(WebContents* old_contents,
829 WebContents* new_contents,
830 int index,
831 int reason) {
832 // The user has switched between tabs and the new tab may have a different
833 // zoom setting.
834 UpdateZoomControls();
837 void WrenchMenuModel::TabReplacedAt(TabStripModel* tab_strip_model,
838 WebContents* old_contents,
839 WebContents* new_contents,
840 int index) {
841 UpdateZoomControls();
844 void WrenchMenuModel::TabStripModelDeleted() {
845 // During views shutdown, the tabstrip model/browser is deleted first, while
846 // it is the opposite in gtk land.
847 tab_strip_model_->RemoveObserver(this);
848 tab_strip_model_ = NULL;
851 void WrenchMenuModel::Observe(int type,
852 const content::NotificationSource& source,
853 const content::NotificationDetails& details) {
854 DCHECK(type == content::NOTIFICATION_NAV_ENTRY_COMMITTED);
855 UpdateZoomControls();
858 // For testing.
859 WrenchMenuModel::WrenchMenuModel()
860 : ui::SimpleMenuModel(this),
861 uma_action_recorded_(false),
862 provider_(NULL),
863 browser_(NULL),
864 tab_strip_model_(NULL) {}
866 bool WrenchMenuModel::ShouldShowNewIncognitoWindowMenuItem() {
867 if (browser_->profile()->IsGuestSession())
868 return false;
870 return IncognitoModePrefs::GetAvailability(browser_->profile()->GetPrefs()) !=
871 IncognitoModePrefs::DISABLED;
874 // Note: When adding new menu items please place under an appropriate section.
875 // Menu is organised as follows:
876 // - Extension toolbar overflow.
877 // - Global browser errors and warnings.
878 // - Tabs and windows.
879 // - Places previously been e.g. History, bookmarks, recent tabs.
880 // - Page actions e.g. zoom, edit, find, print.
881 // - Learn about the browser and global customisation e.g. settings, help.
882 // - Browser relaunch, quit.
883 void WrenchMenuModel::Build() {
884 if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled())
885 CreateActionToolbarOverflowMenu();
887 AddItem(IDC_VIEW_INCOMPATIBILITIES,
888 l10n_util::GetStringUTF16(IDS_VIEW_INCOMPATIBILITIES));
889 SetIcon(GetIndexOfCommandId(IDC_VIEW_INCOMPATIBILITIES),
890 ui::ResourceBundle::GetSharedInstance().
891 GetNativeImageNamed(IDR_INPUT_ALERT_MENU));
892 if (IsCommandIdVisible(IDC_UPGRADE_DIALOG))
893 AddItem(IDC_UPGRADE_DIALOG, GetUpgradeDialogMenuItemName());
894 if (AddGlobalErrorMenuItems() ||
895 IsCommandIdVisible(IDC_VIEW_INCOMPATIBILITIES) ||
896 IsCommandIdVisible(IDC_UPGRADE_DIALOG))
897 AddSeparator(ui::NORMAL_SEPARATOR);
899 AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
900 AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW);
901 if (ShouldShowNewIncognitoWindowMenuItem())
902 AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, IDS_NEW_INCOGNITO_WINDOW);
903 AddSeparator(ui::NORMAL_SEPARATOR);
905 if (!browser_->profile()->IsOffTheRecord()) {
906 recent_tabs_sub_menu_model_.reset(new RecentTabsSubMenuModel(provider_,
907 browser_,
908 NULL));
909 AddSubMenuWithStringId(IDC_RECENT_TABS_MENU, IDS_HISTORY_RECENT_TABS_MENU,
910 recent_tabs_sub_menu_model_.get());
912 AddItemWithStringId(IDC_SHOW_DOWNLOADS, IDS_SHOW_DOWNLOADS);
913 if (!browser_->profile()->IsGuestSession()) {
914 bookmark_sub_menu_model_.reset(new BookmarkSubMenuModel(this, browser_));
915 AddSubMenuWithStringId(IDC_BOOKMARKS_MENU, IDS_BOOKMARKS_MENU,
916 bookmark_sub_menu_model_.get());
919 CreateZoomMenu();
920 AddItemWithStringId(IDC_PRINT, IDS_PRINT);
921 AddItemWithStringId(IDC_FIND, IDS_FIND);
922 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
923 switches::kEnableDomDistiller))
924 AddItemWithStringId(IDC_DISTILL_PAGE, IDS_DISTILL_PAGE);
925 tools_menu_model_.reset(new ToolsMenuModel(this, browser_));
926 AddSubMenuWithStringId(
927 IDC_MORE_TOOLS_MENU, IDS_MORE_TOOLS_MENU, tools_menu_model_.get());
928 // Append the full menu including separators. The final separator only gets
929 // appended when this is a touch menu - otherwise it would get added twice.
930 CreateCutCopyPasteMenu();
932 AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS);
933 #if !defined(OS_CHROMEOS)
934 if (!switches::IsNewAvatarMenu()) {
935 // No "Sign in to Chromium..." menu item on ChromeOS.
936 SigninManager* signin = SigninManagerFactory::GetForProfile(
937 browser_->profile()->GetOriginalProfile());
938 if (signin && signin->IsSigninAllowed() &&
939 signin_ui_util::GetSignedInServiceErrors(
940 browser_->profile()->GetOriginalProfile()).empty()) {
941 AddItem(IDC_SHOW_SYNC_SETUP,
942 l10n_util::GetStringFUTF16(
943 IDS_SYNC_MENU_PRE_SYNCED_LABEL,
944 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));
947 #endif
948 // The help submenu is only displayed on official Chrome builds. As the
949 // 'About' item has been moved to this submenu, it's reinstated here for
950 // Chromium builds.
951 #if defined(GOOGLE_CHROME_BUILD)
952 help_menu_model_.reset(new HelpMenuModel(this, browser_));
953 AddSubMenuWithStringId(IDC_HELP_MENU, IDS_HELP_MENU,
954 help_menu_model_.get());
955 #else
956 AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
957 #endif
958 #if defined(OS_CHROMEOS)
959 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
960 chromeos::switches::kEnableRequestTabletSite))
961 AddCheckItemWithStringId(IDC_TOGGLE_REQUEST_TABLET_SITE,
962 IDS_TOGGLE_REQUEST_TABLET_SITE);
963 #endif
965 #if defined(OS_WIN)
966 int command_id = IDC_WIN_DESKTOP_RESTART;
967 int string_id = IDS_WIN_DESKTOP_RESTART;
968 if (GetRestartMenuItemIfRequired(browser_->host_desktop_type(),
969 &command_id,
970 &string_id)) {
971 AddSeparator(ui::NORMAL_SEPARATOR);
972 AddItemWithStringId(command_id, string_id);
974 #endif
975 bool show_exit_menu = browser_defaults::kShowExitMenuItem;
976 #if defined(OS_WIN)
977 if (browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH)
978 show_exit_menu = false;
979 #endif
980 if (show_exit_menu) {
981 AddSeparator(ui::NORMAL_SEPARATOR);
982 AddItemWithStringId(IDC_EXIT, IDS_EXIT);
984 uma_action_recorded_ = false;
987 bool WrenchMenuModel::AddGlobalErrorMenuItems() {
988 // TODO(sail): Currently we only build the wrench menu once per browser
989 // window. This means that if a new error is added after the menu is built
990 // it won't show in the existing wrench menu. To fix this we need to some
991 // how update the menu if new errors are added.
992 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
993 // GetSignedInServiceErrors() can modify the global error list, so call it
994 // before iterating through that list below.
995 std::vector<GlobalError*> signin_errors;
996 signin_errors = signin_ui_util::GetSignedInServiceErrors(
997 browser_->profile()->GetOriginalProfile());
998 const GlobalErrorService::GlobalErrorList& errors =
999 GlobalErrorServiceFactory::GetForProfile(browser_->profile())->errors();
1000 bool menu_items_added = false;
1001 for (GlobalErrorService::GlobalErrorList::const_iterator
1002 it = errors.begin(); it != errors.end(); ++it) {
1003 GlobalError* error = *it;
1004 DCHECK(error);
1005 if (error->HasMenuItem()) {
1006 #if !defined(OS_CHROMEOS)
1007 // Don't add a signin error if it's already being displayed elsewhere.
1008 if (std::find(signin_errors.begin(), signin_errors.end(), error) !=
1009 signin_errors.end()) {
1010 MenuModel* model = this;
1011 int index = 0;
1012 if (MenuModel::GetModelAndIndexForCommandId(
1013 IDC_SHOW_SIGNIN, &model, &index)) {
1014 continue;
1017 #endif
1019 AddItem(error->MenuItemCommandID(), error->MenuItemLabel());
1020 int icon_id = error->MenuItemIconResourceID();
1021 if (icon_id) {
1022 const gfx::Image& image = rb.GetNativeImageNamed(icon_id);
1023 SetIcon(GetIndexOfCommandId(error->MenuItemCommandID()),
1024 image);
1026 menu_items_added = true;
1029 return menu_items_added;
1032 void WrenchMenuModel::CreateActionToolbarOverflowMenu() {
1033 // We only add the extensions overflow container if there are any icons that
1034 // aren't shown in the main container.
1035 if (!ToolbarActionsModel::Get(browser_->profile())->all_icons_visible()) {
1036 #if defined(OS_MACOSX)
1037 // There's a bug in AppKit menus, where if a menu item with a custom view
1038 // (like the extensions overflow menu) is the first menu item, it is not
1039 // highlightable or keyboard-selectable.
1040 // Adding any menu item before it (even one which is never visible) prevents
1041 // it, so add a bogus item here that will always be hidden.
1042 AddItem(kEmptyMenuItemCommand, base::string16());
1043 #endif
1044 AddItem(IDC_EXTENSIONS_OVERFLOW_MENU, base::string16());
1045 AddSeparator(ui::UPPER_SEPARATOR);
1049 void WrenchMenuModel::CreateCutCopyPasteMenu() {
1050 AddSeparator(ui::LOWER_SEPARATOR);
1052 // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the
1053 // layout for this menu item in WrenchMenu.xib. It does, however, use the
1054 // command_id value from AddButtonItem() to identify this special item.
1055 edit_menu_item_model_.reset(new ui::ButtonMenuItemModel(IDS_EDIT, this));
1056 edit_menu_item_model_->AddGroupItemWithStringId(IDC_CUT, IDS_CUT);
1057 edit_menu_item_model_->AddGroupItemWithStringId(IDC_COPY, IDS_COPY);
1058 edit_menu_item_model_->AddGroupItemWithStringId(IDC_PASTE, IDS_PASTE);
1059 AddButtonItem(IDC_EDIT_MENU, edit_menu_item_model_.get());
1061 AddSeparator(ui::UPPER_SEPARATOR);
1064 void WrenchMenuModel::CreateZoomMenu() {
1065 // This menu needs to be enclosed by separators.
1066 AddSeparator(ui::LOWER_SEPARATOR);
1068 // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the
1069 // layout for this menu item in WrenchMenu.xib. It does, however, use the
1070 // command_id value from AddButtonItem() to identify this special item.
1071 zoom_menu_item_model_.reset(
1072 new ui::ButtonMenuItemModel(IDS_ZOOM_MENU, this));
1073 zoom_menu_item_model_->AddGroupItemWithStringId(IDC_ZOOM_MINUS,
1074 IDS_ZOOM_MINUS2);
1075 zoom_menu_item_model_->AddGroupItemWithStringId(IDC_ZOOM_PLUS,
1076 IDS_ZOOM_PLUS2);
1077 zoom_menu_item_model_->AddItemWithImage(IDC_FULLSCREEN,
1078 IDR_FULLSCREEN_MENU_BUTTON);
1079 AddButtonItem(IDC_ZOOM_MENU, zoom_menu_item_model_.get());
1081 AddSeparator(ui::UPPER_SEPARATOR);
1084 void WrenchMenuModel::UpdateZoomControls() {
1085 int zoom_percent = 100;
1086 if (browser_->tab_strip_model() &&
1087 browser_->tab_strip_model()->GetActiveWebContents()) {
1088 zoom_percent = ui_zoom::ZoomController::FromWebContents(
1089 browser_->tab_strip_model()->GetActiveWebContents())
1090 ->GetZoomPercent();
1092 zoom_label_ = l10n_util::GetStringFUTF16(
1093 IDS_ZOOM_PERCENT, base::IntToString16(zoom_percent));
1096 void WrenchMenuModel::OnZoomLevelChanged(
1097 const content::HostZoomMap::ZoomLevelChange& change) {
1098 UpdateZoomControls();