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