1// Copyright (c) 2011 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/wrench_menu_model.h"
6
7#include <algorithm>
8#include <cmath>
9
10#include "base/command_line.h"
11#include "base/i18n/number_formatting.h"
12#include "base/string_number_conversions.h"
13#include "base/string_util.h"
14#include "base/utf_string_conversions.h"
15#include "chrome/app/chrome_command_ids.h"
16#include "chrome/browser/background_page_tracker.h"
17#include "chrome/browser/browser_process.h"
18#include "chrome/browser/defaults.h"
19#include "chrome/browser/prefs/pref_service.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/sync/profile_sync_service.h"
22#include "chrome/browser/sync/sync_ui_util.h"
23#include "chrome/browser/tabs/tab_strip_model.h"
24#include "chrome/browser/ui/browser.h"
25#include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
26#include "chrome/browser/upgrade_detector.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/pref_names.h"
29#include "chrome/common/profiling.h"
30#include "content/browser/tab_contents/tab_contents.h"
31#include "content/common/notification_service.h"
32#include "content/common/notification_source.h"
33#include "content/common/notification_type.h"
34#include "grit/chromium_strings.h"
35#include "grit/generated_resources.h"
36#include "grit/theme_resources.h"
37#include "ui/base/l10n/l10n_util.h"
38#include "ui/base/models/button_menu_item_model.h"
39#include "ui/base/resource/resource_bundle.h"
40
41#if defined(OS_LINUX)
42#include <gtk/gtk.h>
43#include "chrome/browser/ui/gtk/gtk_util.h"
44#endif
45
46#if defined(OS_MACOSX)
47#include "chrome/browser/ui/browser_window.h"
48#endif
49
50#if defined(OS_CHROMEOS)
51#include "chrome/browser/chromeos/cros/cros_library.h"
52#include "chrome/browser/chromeos/cros/update_library.h"
53#endif
54
55#if defined(OS_WIN)
56#include "chrome/browser/enumerate_modules_model_win.h"
57#endif
58
59////////////////////////////////////////////////////////////////////////////////
60// EncodingMenuModel
61
62EncodingMenuModel::EncodingMenuModel(Browser* browser)
63    : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
64      browser_(browser) {
65  Build();
66}
67
68EncodingMenuModel::~EncodingMenuModel() {
69}
70
71void EncodingMenuModel::Build() {
72  EncodingMenuController::EncodingMenuItemList encoding_menu_items;
73  EncodingMenuController encoding_menu_controller;
74  encoding_menu_controller.GetEncodingMenuItems(browser_->profile(),
75                                                &encoding_menu_items);
76
77  int group_id = 0;
78  EncodingMenuController::EncodingMenuItemList::iterator it =
79      encoding_menu_items.begin();
80  for (; it != encoding_menu_items.end(); ++it) {
81    int id = it->first;
82    string16& label = it->second;
83    if (id == 0) {
84      AddSeparator();
85    } else {
86      if (id == IDC_ENCODING_AUTO_DETECT) {
87        AddCheckItem(id, label);
88      } else {
89        // Use the id of the first radio command as the id of the group.
90        if (group_id <= 0)
91          group_id = id;
92        AddRadioItem(id, label, group_id);
93      }
94    }
95  }
96}
97
98bool EncodingMenuModel::IsCommandIdChecked(int command_id) const {
99  TabContents* current_tab = browser_->GetSelectedTabContents();
100  if (!current_tab)
101    return false;
102  EncodingMenuController controller;
103  return controller.IsItemChecked(browser_->profile(),
104                                  current_tab->encoding(), command_id);
105}
106
107bool EncodingMenuModel::IsCommandIdEnabled(int command_id) const {
108  bool enabled = browser_->command_updater()->IsCommandEnabled(command_id);
109  // Special handling for the contents of the Encoding submenu. On Mac OS,
110  // instead of enabling/disabling the top-level menu item, the submenu's
111  // contents get disabled, per Apple's HIG.
112#if defined(OS_MACOSX)
113  enabled &= browser_->command_updater()->IsCommandEnabled(IDC_ENCODING_MENU);
114#endif
115  return enabled;
116}
117
118bool EncodingMenuModel::GetAcceleratorForCommandId(
119    int command_id,
120    ui::Accelerator* accelerator) {
121  return false;
122}
123
124void EncodingMenuModel::ExecuteCommand(int command_id) {
125  browser_->ExecuteCommand(command_id);
126}
127
128////////////////////////////////////////////////////////////////////////////////
129// ZoomMenuModel
130
131ZoomMenuModel::ZoomMenuModel(ui::SimpleMenuModel::Delegate* delegate)
132    : SimpleMenuModel(delegate) {
133  Build();
134}
135
136ZoomMenuModel::~ZoomMenuModel() {
137}
138
139void ZoomMenuModel::Build() {
140  AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
141  AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL);
142  AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
143}
144
145////////////////////////////////////////////////////////////////////////////////
146// ToolsMenuModel
147
148ToolsMenuModel::ToolsMenuModel(ui::SimpleMenuModel::Delegate* delegate,
149                               Browser* browser)
150    : SimpleMenuModel(delegate) {
151  Build(browser);
152}
153
154ToolsMenuModel::~ToolsMenuModel() {}
155
156void ToolsMenuModel::Build(Browser* browser) {
157  AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR);
158
159  AddSeparator();
160
161#if !defined(OS_CHROMEOS)
162#if defined(OS_MACOSX)
163  AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_APPLICATION_MAC);
164#else
165  AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS);
166#endif
167  AddSeparator();
168#endif
169
170  AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS);
171#if defined(OS_CHROMEOS)
172  AddItemWithStringId(IDC_FILE_MANAGER, IDS_FILE_MANAGER);
173#endif
174  AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
175  AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA);
176
177  AddSeparator();
178
179#if !defined(OS_CHROMEOS)
180  // Show IDC_FEEDBACK in "Tools" menu for non-ChromeOS platforms.
181  AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
182  AddSeparator();
183#endif
184
185  encoding_menu_model_.reset(new EncodingMenuModel(browser));
186  AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU,
187                         encoding_menu_model_.get());
188  AddItemWithStringId(IDC_VIEW_SOURCE, IDS_VIEW_SOURCE);
189  AddItemWithStringId(IDC_DEV_TOOLS, IDS_DEV_TOOLS);
190  AddItemWithStringId(IDC_DEV_TOOLS_CONSOLE, IDS_DEV_TOOLS_CONSOLE);
191
192#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC)
193  AddSeparator();
194  AddCheckItemWithStringId(IDC_PROFILING_ENABLED, IDS_PROFILING_ENABLED);
195#endif
196}
197
198////////////////////////////////////////////////////////////////////////////////
199// WrenchMenuModel
200
201WrenchMenuModel::WrenchMenuModel(ui::AcceleratorProvider* provider,
202                                 Browser* browser)
203    : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
204      provider_(provider),
205      browser_(browser),
206      tabstrip_model_(browser_->tabstrip_model()) {
207  Build();
208  UpdateZoomControls();
209
210  tabstrip_model_->AddObserver(this);
211
212  registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED,
213                 Source<Profile>(browser_->profile()));
214  registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
215                 NotificationService::AllSources());
216}
217
218WrenchMenuModel::~WrenchMenuModel() {
219  if (tabstrip_model_)
220    tabstrip_model_->RemoveObserver(this);
221}
222
223bool WrenchMenuModel::DoesCommandIdDismissMenu(int command_id) const {
224  return command_id != IDC_ZOOM_MINUS && command_id != IDC_ZOOM_PLUS;
225}
226
227bool WrenchMenuModel::IsItemForCommandIdDynamic(int command_id) const {
228  return command_id == IDC_ZOOM_PERCENT_DISPLAY ||
229#if defined(OS_MACOSX)
230         command_id == IDC_FULLSCREEN ||
231#endif
232         command_id == IDC_SYNC_BOOKMARKS ||
233         command_id == IDC_VIEW_BACKGROUND_PAGES ||
234         command_id == IDC_UPGRADE_DIALOG;
235}
236
237string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const {
238  switch (command_id) {
239    case IDC_SYNC_BOOKMARKS:
240      return GetSyncMenuLabel();
241    case IDC_ZOOM_PERCENT_DISPLAY:
242      return zoom_label_;
243#if defined(OS_MACOSX)
244    case IDC_FULLSCREEN: {
245      int string_id = IDS_ENTER_FULLSCREEN_MAC;  // Default to Enter.
246      // Note: On startup, |window()| may be NULL.
247      if (browser_->window() && browser_->window()->IsFullscreen())
248        string_id = IDS_EXIT_FULLSCREEN_MAC;
249      return l10n_util::GetStringUTF16(string_id);
250    }
251#endif
252    case IDC_VIEW_BACKGROUND_PAGES: {
253      string16 num_background_pages = base::FormatNumber(
254          BackgroundPageTracker::GetInstance()->GetBackgroundPageCount());
255      return l10n_util::GetStringFUTF16(IDS_VIEW_BACKGROUND_PAGES,
256                                        num_background_pages);
257    }
258    case IDC_UPGRADE_DIALOG: {
259#if defined(OS_CHROMEOS)
260      const string16 product_name =
261          l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME);
262#else
263      const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
264#endif
265
266      return l10n_util::GetStringFUTF16(IDS_UPDATE_NOW, product_name);
267    }
268    default:
269      NOTREACHED();
270      return string16();
271  }
272}
273
274bool WrenchMenuModel::GetIconForCommandId(int command_id,
275                                          SkBitmap* icon) const {
276  switch (command_id) {
277    case IDC_UPGRADE_DIALOG: {
278      ResourceBundle& rb = ResourceBundle::GetSharedInstance();
279      int resource_id;
280      UpgradeDetector::UpgradeNotificationAnnoyanceLevel stage =
281          UpgradeDetector::GetInstance()->upgrade_notification_stage();
282      switch (stage) {
283        case UpgradeDetector::UPGRADE_ANNOYANCE_SEVERE:
284          resource_id = IDR_UPDATE_MENU4;
285          break;
286        case UpgradeDetector::UPGRADE_ANNOYANCE_HIGH:
287          resource_id = IDR_UPDATE_MENU3;
288          break;
289        case UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED:
290          resource_id = IDR_UPDATE_MENU2;
291          break;
292        default:
293          resource_id = IDR_UPDATE_MENU;
294          break;
295      }
296      *icon = *rb.GetBitmapNamed(resource_id);
297      break;
298    }
299    default:
300      break;
301  }
302  return false;
303}
304
305void WrenchMenuModel::ExecuteCommand(int command_id) {
306  browser_->ExecuteCommand(command_id);
307}
308
309bool WrenchMenuModel::IsCommandIdChecked(int command_id) const {
310  if (command_id == IDC_SHOW_BOOKMARK_BAR) {
311    return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
312  } else if (command_id == IDC_PROFILING_ENABLED) {
313    return Profiling::BeingProfiled();
314  }
315
316  return false;
317}
318
319bool WrenchMenuModel::IsCommandIdEnabled(int command_id) const {
320  if (command_id == IDC_SHOW_BOOKMARK_BAR) {
321    return !browser_->profile()->GetPrefs()->IsManagedPreference(
322        prefs::kEnableBookmarkBar);
323  }
324  return browser_->command_updater()->IsCommandEnabled(command_id);
325}
326
327bool WrenchMenuModel::IsCommandIdVisible(int command_id) const {
328  if (command_id == IDC_UPGRADE_DIALOG) {
329#if defined(OS_CHROMEOS)
330    return (chromeos::CrosLibrary::Get()->GetUpdateLibrary()->status().status
331            == chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT);
332#else
333    return UpgradeDetector::GetInstance()->notify_upgrade();
334#endif
335  } else if (command_id == IDC_VIEW_INCOMPATIBILITIES) {
336#if defined(OS_WIN)
337    EnumerateModulesModel* loaded_modules =
338        EnumerateModulesModel::GetInstance();
339    if (loaded_modules->confirmed_bad_modules_detected() <= 0)
340      return false;
341    loaded_modules->AcknowledgeConflictNotification();
342    return true;
343#else
344    return false;
345#endif
346  } else if (command_id == IDC_VIEW_BACKGROUND_PAGES) {
347    BackgroundPageTracker* tracker = BackgroundPageTracker::GetInstance();
348    return tracker->GetBackgroundPageCount() > 0;
349  }
350  return true;
351}
352
353bool WrenchMenuModel::GetAcceleratorForCommandId(
354      int command_id,
355      ui::Accelerator* accelerator) {
356  return provider_->GetAcceleratorForCommandId(command_id, accelerator);
357}
358
359void WrenchMenuModel::TabSelectedAt(TabContentsWrapper* old_contents,
360                                    TabContentsWrapper* new_contents,
361                                    int index,
362                                    bool user_gesture) {
363  // The user has switched between tabs and the new tab may have a different
364  // zoom setting.
365  UpdateZoomControls();
366}
367
368void WrenchMenuModel::TabReplacedAt(TabStripModel* tab_strip_model,
369                                    TabContentsWrapper* old_contents,
370                                    TabContentsWrapper* new_contents,
371                                    int index) {
372  UpdateZoomControls();
373}
374
375void WrenchMenuModel::TabStripModelDeleted() {
376  // During views shutdown, the tabstrip model/browser is deleted first, while
377  // it is the opposite in gtk land.
378  tabstrip_model_->RemoveObserver(this);
379  tabstrip_model_ = NULL;
380}
381
382void WrenchMenuModel::Observe(NotificationType type,
383                              const NotificationSource& source,
384                              const NotificationDetails& details) {
385  switch (type.value) {
386    case NotificationType::ZOOM_LEVEL_CHANGED:
387    case NotificationType::NAV_ENTRY_COMMITTED:
388      UpdateZoomControls();
389      break;
390    default:
391      NOTREACHED();
392  }
393}
394
395// For testing.
396WrenchMenuModel::WrenchMenuModel()
397    : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
398      provider_(NULL),
399      browser_(NULL),
400      tabstrip_model_(NULL) {
401}
402
403void WrenchMenuModel::Build() {
404  AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB);
405  AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW);
406#if defined(OS_CHROMEOS)
407  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession))
408    AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, IDS_NEW_INCOGNITO_WINDOW);
409#else
410  AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, IDS_NEW_INCOGNITO_WINDOW);
411#endif
412
413  AddSeparator();
414#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS))
415  // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the
416  // layout for this menu item in Toolbar.xib. It does, however, use the
417  // command_id value from AddButtonItem() to identify this special item.
418  edit_menu_item_model_.reset(new ui::ButtonMenuItemModel(IDS_EDIT, this));
419  edit_menu_item_model_->AddGroupItemWithStringId(IDC_CUT, IDS_CUT);
420  edit_menu_item_model_->AddGroupItemWithStringId(IDC_COPY, IDS_COPY);
421  edit_menu_item_model_->AddGroupItemWithStringId(IDC_PASTE, IDS_PASTE);
422  AddButtonItem(IDC_EDIT_MENU, edit_menu_item_model_.get());
423#else
424  // TODO(port): Move to the above.
425  CreateCutCopyPaste();
426#endif
427
428  AddSeparator();
429#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS))
430  // WARNING: See above comment.
431  zoom_menu_item_model_.reset(
432      new ui::ButtonMenuItemModel(IDS_ZOOM_MENU, this));
433  zoom_menu_item_model_->AddGroupItemWithStringId(
434      IDC_ZOOM_MINUS, IDS_ZOOM_MINUS2);
435  zoom_menu_item_model_->AddButtonLabel(IDC_ZOOM_PERCENT_DISPLAY,
436                                        IDS_ZOOM_PLUS2);
437  zoom_menu_item_model_->AddGroupItemWithStringId(
438      IDC_ZOOM_PLUS, IDS_ZOOM_PLUS2);
439  zoom_menu_item_model_->AddSpace();
440  zoom_menu_item_model_->AddItemWithImage(
441      IDC_FULLSCREEN, IDR_FULLSCREEN_MENU_BUTTON);
442  AddButtonItem(IDC_ZOOM_MENU, zoom_menu_item_model_.get());
443#else
444  // TODO(port): Move to the above.
445  CreateZoomFullscreen();
446#endif
447
448  AddSeparator();
449  AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE);
450  AddItemWithStringId(IDC_FIND, IDS_FIND);
451  AddItemWithStringId(IDC_PRINT, IDS_PRINT);
452
453  tools_menu_model_.reset(new ToolsMenuModel(this, browser_));
454  AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_TOOLS_MENU,
455                         tools_menu_model_.get());
456
457  AddSeparator();
458  AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER);
459  AddItemWithStringId(IDC_SHOW_HISTORY, IDS_SHOW_HISTORY);
460  AddItemWithStringId(IDC_SHOW_DOWNLOADS, IDS_SHOW_DOWNLOADS);
461  AddSeparator();
462
463#if defined(OS_CHROMEOS)
464  AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS);
465#elif defined(OS_MACOSX)
466  AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES);
467#elif defined(OS_LINUX)
468  string16 preferences = gtk_util::GetStockPreferencesMenuLabel();
469  if (!preferences.empty())
470    AddItem(IDC_OPTIONS, preferences);
471  else
472    AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES);
473#else
474  AddItemWithStringId(IDC_OPTIONS, IDS_OPTIONS);
475#endif
476
477#if defined(OS_CHROMEOS)
478  const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME);
479#else
480  const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
481#endif
482  AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16(IDS_ABOUT, product_name));
483  string16 num_background_pages = base::FormatNumber(
484      BackgroundPageTracker::GetInstance()->GetBackgroundPageCount());
485  AddItem(IDC_VIEW_BACKGROUND_PAGES, l10n_util::GetStringFUTF16(
486      IDS_VIEW_BACKGROUND_PAGES, num_background_pages));
487  AddItem(IDC_UPGRADE_DIALOG, l10n_util::GetStringFUTF16(
488      IDS_UPDATE_NOW, product_name));
489  AddItem(IDC_VIEW_INCOMPATIBILITIES, l10n_util::GetStringUTF16(
490      IDS_VIEW_INCOMPATIBILITIES));
491
492#if defined(OS_WIN)
493  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
494  SetIcon(GetIndexOfCommandId(IDC_VIEW_INCOMPATIBILITIES),
495          *rb.GetBitmapNamed(IDR_CONFLICT_MENU));
496#endif
497
498  AddItemWithStringId(IDC_HELP_PAGE, IDS_HELP_PAGE);
499#if defined(OS_CHROMEOS)
500  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
501  // Use an icon for IDC_HELP_PAGE menu item.
502  SetIcon(GetIndexOfCommandId(IDC_HELP_PAGE),
503          *rb.GetBitmapNamed(IDR_HELP_MENU));
504
505  // Show IDC_FEEDBACK in top-tier wrench menu for ChromeOS.
506  AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
507#endif
508
509  if (browser_defaults::kShowExitMenuItem) {
510    AddSeparator();
511#if defined(OS_CHROMEOS)
512    if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) {
513      AddItemWithStringId(IDC_EXIT, IDS_EXIT_GUEST_MODE);
514    } else {
515      AddItemWithStringId(IDC_EXIT, IDS_SIGN_OUT);
516    }
517#else
518    AddItemWithStringId(IDC_EXIT, IDS_EXIT);
519#endif
520  }
521}
522
523void WrenchMenuModel::CreateCutCopyPaste() {
524  // WARNING: views/wrench_menu assumes these items are added in this order. If
525  // you change the order you'll need to update wrench_menu as well.
526  AddItemWithStringId(IDC_CUT, IDS_CUT);
527  AddItemWithStringId(IDC_COPY, IDS_COPY);
528  AddItemWithStringId(IDC_PASTE, IDS_PASTE);
529}
530
531void WrenchMenuModel::CreateZoomFullscreen() {
532  // WARNING: views/wrench_menu assumes these items are added in this order. If
533  // you change the order you'll need to update wrench_menu as well.
534  AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS);
535  AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS);
536  AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN);
537}
538
539void WrenchMenuModel::UpdateZoomControls() {
540  bool enable_increment = false;
541  bool enable_decrement = false;
542  int zoom_percent = 100;
543  if (browser_->GetSelectedTabContents()) {
544    zoom_percent = browser_->GetSelectedTabContents()->GetZoomPercent(
545        &enable_increment, &enable_decrement);
546  }
547  zoom_label_ = l10n_util::GetStringFUTF16(
548      IDS_ZOOM_PERCENT, base::IntToString16(zoom_percent));
549}
550
551string16 WrenchMenuModel::GetSyncMenuLabel() const {
552  return sync_ui_util::GetSyncMenuLabel(
553      browser_->profile()->GetOriginalProfile()->GetProfileSyncService());
554}
555