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