app_context_menu.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright 2013 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/app_list/app_context_menu.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "chrome/app/chrome_command_ids.h"
10#include "chrome/browser/extensions/context_menu_matcher.h"
11#include "chrome/browser/extensions/menu_manager.h"
12#include "chrome/browser/prefs/incognito_mode_prefs.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
15#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
16#include "chrome/common/chrome_switches.h"
17#include "chrome/common/extensions/manifest_url_handler.h"
18#include "content/public/common/context_menu_params.h"
19#include "grit/chromium_strings.h"
20#include "grit/generated_resources.h"
21#include "ui/base/l10n/l10n_util.h"
22
23#if defined(USE_ASH)
24#include "ash/shell.h"
25#endif
26
27namespace app_list {
28
29namespace {
30
31enum CommandId {
32  LAUNCH_NEW = 100,
33  TOGGLE_PIN,
34  CREATE_SHORTCUTS,
35  SHOW_APP_INFO,
36  OPTIONS,
37  UNINSTALL,
38  REMOVE_FROM_FOLDER,
39  MENU_NEW_WINDOW,
40  MENU_NEW_INCOGNITO_WINDOW,
41  // Order matters in USE_LAUNCH_TYPE_* and must match the LaunchType enum.
42  USE_LAUNCH_TYPE_COMMAND_START = 200,
43  USE_LAUNCH_TYPE_PINNED = USE_LAUNCH_TYPE_COMMAND_START,
44  USE_LAUNCH_TYPE_REGULAR,
45  USE_LAUNCH_TYPE_FULLSCREEN,
46  USE_LAUNCH_TYPE_WINDOW,
47  USE_LAUNCH_TYPE_COMMAND_END,
48};
49
50bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
51  return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
52}
53
54}  // namespace
55
56AppContextMenu::AppContextMenu(AppContextMenuDelegate* delegate,
57                               Profile* profile,
58                               const std::string& app_id,
59                               AppListControllerDelegate* controller)
60    : delegate_(delegate),
61      profile_(profile),
62      app_id_(app_id),
63      controller_(controller),
64      is_platform_app_(false),
65      is_search_result_(false),
66      is_in_folder_(false) {
67}
68
69AppContextMenu::~AppContextMenu() {
70}
71
72ui::MenuModel* AppContextMenu::GetMenuModel() {
73  if (!controller_->IsExtensionInstalled(profile_, app_id_))
74    return NULL;
75
76  if (menu_model_.get())
77    return menu_model_.get();
78
79  menu_model_.reset(new ui::SimpleMenuModel(this));
80
81  if (app_id_ == extension_misc::kChromeAppId) {
82    menu_model_->AddItemWithStringId(
83        MENU_NEW_WINDOW,
84        IDS_APP_LIST_NEW_WINDOW);
85    if (!profile_->IsOffTheRecord()) {
86      menu_model_->AddItemWithStringId(
87          MENU_NEW_INCOGNITO_WINDOW,
88          IDS_APP_LIST_NEW_INCOGNITO_WINDOW);
89    }
90    if (controller_->CanDoShowAppInfoFlow()) {
91      menu_model_->AddItemWithStringId(SHOW_APP_INFO,
92                                       IDS_APP_CONTEXT_MENU_SHOW_INFO);
93    }
94  } else {
95    extension_menu_items_.reset(new extensions::ContextMenuMatcher(
96        profile_, this, menu_model_.get(),
97        base::Bind(MenuItemHasLauncherContext)));
98
99    // First, add the primary actions.
100    if (!is_platform_app_)
101      menu_model_->AddItem(LAUNCH_NEW, base::string16());
102
103    // Show Pin/Unpin option if shelf is available.
104    if (controller_->GetPinnable() != AppListControllerDelegate::NO_PIN) {
105      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
106      menu_model_->AddItemWithStringId(
107          TOGGLE_PIN,
108          controller_->IsAppPinned(app_id_) ?
109              IDS_APP_LIST_CONTEXT_MENU_UNPIN :
110              IDS_APP_LIST_CONTEXT_MENU_PIN);
111    }
112
113    if (controller_->CanDoCreateShortcutsFlow()) {
114      menu_model_->AddItemWithStringId(CREATE_SHORTCUTS,
115                                       IDS_NEW_TAB_APP_CREATE_SHORTCUT);
116    }
117    menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
118
119    if (!is_platform_app_) {
120      // Streamlined hosted apps can only toggle between USE_LAUNCH_TYPE_WINDOW
121      // and USE_LAUNCH_TYPE_REGULAR.
122      if (CommandLine::ForCurrentProcess()->HasSwitch(
123          switches::kEnableStreamlinedHostedApps)) {
124        menu_model_->AddCheckItemWithStringId(
125            USE_LAUNCH_TYPE_REGULAR,
126            IDS_APP_CONTEXT_MENU_OPEN_TAB);
127      } else {
128        menu_model_->AddCheckItemWithStringId(
129            USE_LAUNCH_TYPE_REGULAR,
130            IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
131        menu_model_->AddCheckItemWithStringId(
132            USE_LAUNCH_TYPE_PINNED,
133            IDS_APP_CONTEXT_MENU_OPEN_PINNED);
134#if defined(OS_MACOSX)
135        // Mac does not support standalone web app browser windows or maximize.
136        menu_model_->AddCheckItemWithStringId(
137            USE_LAUNCH_TYPE_FULLSCREEN,
138            IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN);
139#else
140        menu_model_->AddCheckItemWithStringId(
141            USE_LAUNCH_TYPE_WINDOW,
142            IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
143        // Even though the launch type is Full Screen it is more accurately
144        // described as Maximized in Ash.
145        menu_model_->AddCheckItemWithStringId(
146            USE_LAUNCH_TYPE_FULLSCREEN,
147            IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
148#endif
149      }
150      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
151    }
152
153    // Assign unique IDs to commands added by the app itself.
154    int index = USE_LAUNCH_TYPE_COMMAND_END;
155    extension_menu_items_->AppendExtensionItems(
156        extensions::MenuItem::ExtensionKey(app_id_), base::string16(), &index);
157
158    // If at least 1 item was added, add another separator after the list.
159    if (index > USE_LAUNCH_TYPE_COMMAND_END)
160      menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
161
162    if (!is_platform_app_)
163      menu_model_->AddItemWithStringId(OPTIONS, IDS_NEW_TAB_APP_OPTIONS);
164
165    menu_model_->AddItemWithStringId(UNINSTALL,
166                                     is_platform_app_
167                                         ? IDS_APP_LIST_UNINSTALL_ITEM
168                                         : IDS_APP_LIST_EXTENSIONS_UNINSTALL);
169
170    if (controller_->CanDoShowAppInfoFlow()) {
171      menu_model_->AddItemWithStringId(SHOW_APP_INFO,
172                                       IDS_APP_CONTEXT_MENU_SHOW_INFO);
173    }
174  }
175
176  return menu_model_.get();
177}
178
179bool AppContextMenu::IsItemForCommandIdDynamic(int command_id) const {
180  return command_id == TOGGLE_PIN || command_id == LAUNCH_NEW;
181}
182
183base::string16 AppContextMenu::GetLabelForCommandId(int command_id) const {
184  if (command_id == TOGGLE_PIN) {
185    return controller_->IsAppPinned(app_id_) ?
186        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_UNPIN) :
187        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_PIN);
188  } else if (command_id == LAUNCH_NEW) {
189#if defined(OS_MACOSX)
190    // Even fullscreen windows launch in a browser tab on Mac.
191    const bool launches_in_tab = true;
192#else
193    const bool launches_in_tab = IsCommandIdChecked(USE_LAUNCH_TYPE_PINNED) ||
194        IsCommandIdChecked(USE_LAUNCH_TYPE_REGULAR);
195#endif
196    return launches_in_tab ?
197        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_TAB) :
198        l10n_util::GetStringUTF16(IDS_APP_LIST_CONTEXT_MENU_NEW_WINDOW);
199  } else {
200    NOTREACHED();
201    return base::string16();
202  }
203}
204
205bool AppContextMenu::IsCommandIdChecked(int command_id) const {
206  if (command_id >= USE_LAUNCH_TYPE_COMMAND_START &&
207      command_id < USE_LAUNCH_TYPE_COMMAND_END) {
208    return static_cast<int>(controller_->GetExtensionLaunchType(
209        profile_, app_id_)) + USE_LAUNCH_TYPE_COMMAND_START == command_id;
210  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
211             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
212    return extension_menu_items_->IsCommandIdChecked(command_id);
213  }
214  return false;
215}
216
217bool AppContextMenu::IsCommandIdEnabled(int command_id) const {
218  if (command_id == TOGGLE_PIN) {
219    return controller_->GetPinnable() ==
220        AppListControllerDelegate::PIN_EDITABLE;
221  } else if (command_id == OPTIONS) {
222    return controller_->HasOptionsPage(profile_, app_id_);
223  } else if (command_id == UNINSTALL) {
224    return controller_->UserMayModifySettings(profile_, app_id_);
225  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
226             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
227    return extension_menu_items_->IsCommandIdEnabled(command_id);
228  } else if (command_id == MENU_NEW_WINDOW) {
229    // "Normal" windows are not allowed when incognito is enforced.
230    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
231        IncognitoModePrefs::FORCED;
232  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
233    // Incognito windows are not allowed when incognito is disabled.
234    return IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) !=
235        IncognitoModePrefs::DISABLED;
236  }
237  return true;
238}
239
240bool AppContextMenu::GetAcceleratorForCommandId(
241    int command_id,
242    ui::Accelerator* acclelrator) {
243  return false;
244}
245
246void AppContextMenu::ExecuteCommand(int command_id, int event_flags) {
247  if (command_id == LAUNCH_NEW) {
248    delegate_->ExecuteLaunchCommand(event_flags);
249  } else if (command_id == TOGGLE_PIN && controller_->GetPinnable() ==
250      AppListControllerDelegate::PIN_EDITABLE) {
251    if (controller_->IsAppPinned(app_id_))
252      controller_->UnpinApp(app_id_);
253    else
254      controller_->PinApp(app_id_);
255  } else if (command_id == CREATE_SHORTCUTS) {
256    controller_->DoCreateShortcutsFlow(profile_, app_id_);
257  } else if (command_id == SHOW_APP_INFO) {
258    controller_->DoShowAppInfoFlow(profile_, app_id_);
259  } else if (command_id >= USE_LAUNCH_TYPE_COMMAND_START &&
260             command_id < USE_LAUNCH_TYPE_COMMAND_END) {
261    extensions::LaunchType launch_type = static_cast<extensions::LaunchType>(
262        command_id - USE_LAUNCH_TYPE_COMMAND_START);
263    // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
264    // LAUNCH_TYPE_REGULAR.
265    if (CommandLine::ForCurrentProcess()->HasSwitch(
266        switches::kEnableStreamlinedHostedApps)) {
267      launch_type = (controller_->GetExtensionLaunchType(profile_, app_id_) ==
268                     extensions::LAUNCH_TYPE_REGULAR) ?
269                    extensions::LAUNCH_TYPE_WINDOW :
270                    extensions::LAUNCH_TYPE_REGULAR;
271    }
272    controller_->SetExtensionLaunchType(profile_, app_id_, launch_type);
273  } else if (command_id == OPTIONS) {
274    controller_->ShowOptionsPage(profile_, app_id_);
275  } else if (command_id == UNINSTALL) {
276    controller_->UninstallApp(profile_, app_id_);
277  } else if (command_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST &&
278             command_id <= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) {
279    extension_menu_items_->ExecuteCommand(command_id, NULL,
280                                          content::ContextMenuParams());
281  } else if (command_id == MENU_NEW_WINDOW) {
282    controller_->CreateNewWindow(profile_, false);
283  } else if (command_id == MENU_NEW_INCOGNITO_WINDOW) {
284    controller_->CreateNewWindow(profile_, true);
285  }
286}
287
288}  // namespace app_list
289