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.
4
5#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
6
7#include <string>
8
9#include "ash/desktop_background/user_wallpaper_delegate.h"
10#include "ash/metrics/user_metrics_recorder.h"
11#include "ash/root_window_controller.h"
12#include "ash/shelf/shelf_item_delegate.h"
13#include "ash/shelf/shelf_widget.h"
14#include "ash/shell.h"
15#include "base/bind.h"
16#include "base/command_line.h"
17#include "base/prefs/pref_service.h"
18#include "chrome/browser/extensions/context_menu_matcher.h"
19#include "chrome/browser/fullscreen.h"
20#include "chrome/browser/prefs/incognito_mode_prefs.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/ash/chrome_shell_delegate.h"
23#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/extensions/extension_constants.h"
26#include "content/public/common/context_menu_params.h"
27#include "grit/ash_strings.h"
28#include "grit/generated_resources.h"
29#include "ui/base/l10n/l10n_util.h"
30
31namespace {
32
33bool MenuItemHasLauncherContext(const extensions::MenuItem* item) {
34  return item->contexts().Contains(extensions::MenuItem::LAUNCHER);
35}
36
37}  // namespace
38
39LauncherContextMenu::LauncherContextMenu(ChromeLauncherController* controller,
40                                         const ash::ShelfItem* item,
41                                         aura::Window* root)
42    : ui::SimpleMenuModel(NULL),
43      controller_(controller),
44      item_(*item),
45      shelf_alignment_menu_(root),
46      root_window_(root),
47      item_delegate_(NULL) {
48  DCHECK(item);
49  DCHECK(root_window_);
50  Init();
51}
52
53LauncherContextMenu::LauncherContextMenu(ChromeLauncherController* controller,
54                                         ash::ShelfItemDelegate* item_delegate,
55                                         ash::ShelfItem* item,
56                                         aura::Window* root)
57    : ui::SimpleMenuModel(NULL),
58      controller_(controller),
59      item_(*item),
60      shelf_alignment_menu_(root),
61      root_window_(root),
62      item_delegate_(item_delegate) {
63  DCHECK(item);
64  DCHECK(root_window_);
65  Init();
66}
67
68LauncherContextMenu::LauncherContextMenu(ChromeLauncherController* controller,
69                                         aura::Window* root)
70    : ui::SimpleMenuModel(NULL),
71      controller_(controller),
72      item_(ash::ShelfItem()),
73      shelf_alignment_menu_(root),
74      extension_items_(new extensions::ContextMenuMatcher(
75          controller->profile(), this, this,
76          base::Bind(MenuItemHasLauncherContext))),
77      root_window_(root),
78      item_delegate_(NULL) {
79  DCHECK(root_window_);
80  Init();
81}
82
83void LauncherContextMenu::Init() {
84  extension_items_.reset(new extensions::ContextMenuMatcher(
85      controller_->profile(), this, this,
86      base::Bind(MenuItemHasLauncherContext)));
87  set_delegate(this);
88
89  if (is_valid_item()) {
90    if (item_.type == ash::TYPE_APP_SHORTCUT ||
91        item_.type == ash::TYPE_WINDOWED_APP) {
92      // V1 apps can be started from the menu - but V2 apps should not.
93      if  (!controller_->IsPlatformApp(item_.id)) {
94        AddItem(MENU_OPEN_NEW, base::string16());
95        AddSeparator(ui::NORMAL_SEPARATOR);
96      }
97      AddItem(
98          MENU_PIN,
99          l10n_util::GetStringUTF16(controller_->IsPinned(item_.id) ?
100                                    IDS_LAUNCHER_CONTEXT_MENU_UNPIN :
101                                    IDS_LAUNCHER_CONTEXT_MENU_PIN));
102      if (controller_->IsOpen(item_.id)) {
103        AddItem(MENU_CLOSE,
104                l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_CLOSE));
105      }
106      if (!controller_->IsPlatformApp(item_.id) &&
107          item_.type != ash::TYPE_WINDOWED_APP) {
108        AddSeparator(ui::NORMAL_SEPARATOR);
109        if (CommandLine::ForCurrentProcess()->HasSwitch(
110            switches::kEnableStreamlinedHostedApps)) {
111          // Streamlined hosted apps launch in a window by default. This menu
112          // item is re-interpreted as a single, toggle-able option to launch
113          // the hosted app as a tab.
114          AddCheckItemWithStringId(
115              LAUNCH_TYPE_REGULAR_TAB,
116              IDS_APP_CONTEXT_MENU_OPEN_TAB);
117        } else {
118          AddCheckItemWithStringId(
119              LAUNCH_TYPE_REGULAR_TAB,
120              IDS_APP_CONTEXT_MENU_OPEN_REGULAR);
121          AddCheckItemWithStringId(
122              LAUNCH_TYPE_PINNED_TAB,
123              IDS_APP_CONTEXT_MENU_OPEN_PINNED);
124          AddCheckItemWithStringId(
125              LAUNCH_TYPE_WINDOW,
126              IDS_APP_CONTEXT_MENU_OPEN_WINDOW);
127          // Even though the launch type is Full Screen it is more accurately
128          // described as Maximized in Ash.
129          AddCheckItemWithStringId(
130              LAUNCH_TYPE_FULLSCREEN,
131              IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED);
132        }
133      }
134    } else if (item_.type == ash::TYPE_BROWSER_SHORTCUT) {
135      AddItem(MENU_NEW_WINDOW,
136              l10n_util::GetStringUTF16(IDS_LAUNCHER_NEW_WINDOW));
137      if (!controller_->IsLoggedInAsGuest()) {
138        AddItem(MENU_NEW_INCOGNITO_WINDOW,
139                l10n_util::GetStringUTF16(IDS_LAUNCHER_NEW_INCOGNITO_WINDOW));
140      }
141    } else if (item_.type == ash::TYPE_DIALOG) {
142      AddItem(MENU_CLOSE,
143              l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_CLOSE));
144    } else {
145      if (item_.type == ash::TYPE_PLATFORM_APP) {
146        AddItem(
147            MENU_PIN,
148            l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_PIN));
149        AddItem(MENU_INSTALL, l10n_util::GetStringUTF16(IDS_APP_INSTALL_TITLE));
150      }
151      if (controller_->IsOpen(item_.id)) {
152        AddItem(MENU_CLOSE,
153                l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_CLOSE));
154      }
155    }
156    AddSeparator(ui::NORMAL_SEPARATOR);
157    if (item_.type == ash::TYPE_APP_SHORTCUT ||
158        item_.type == ash::TYPE_WINDOWED_APP ||
159        item_.type == ash::TYPE_PLATFORM_APP) {
160      const extensions::MenuItem::ExtensionKey app_key(
161          controller_->GetAppIDForShelfID(item_.id));
162      if (!app_key.empty()) {
163        int index = 0;
164        extension_items_->AppendExtensionItems(
165            app_key, base::string16(), &index);
166        AddSeparator(ui::NORMAL_SEPARATOR);
167      }
168    }
169  }
170  // In fullscreen, the launcher is either hidden or autohidden depending on
171  // the type of fullscreen. Do not show the auto-hide menu item while in
172  // fullscreen because it is confusing when the preference appears not to
173  // apply.
174  if (!IsFullScreenMode() &&
175      controller_->CanUserModifyShelfAutoHideBehavior(root_window_)) {
176    AddCheckItemWithStringId(MENU_AUTO_HIDE,
177                             IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE);
178  }
179  if (ash::ShelfWidget::ShelfAlignmentAllowed()) {
180    AddSubMenuWithStringId(MENU_ALIGNMENT_MENU,
181                           IDS_ASH_SHELF_CONTEXT_MENU_POSITION,
182                           &shelf_alignment_menu_);
183  }
184#if defined(OS_CHROMEOS)
185  AddItem(MENU_CHANGE_WALLPAPER,
186       l10n_util::GetStringUTF16(IDS_AURA_SET_DESKTOP_WALLPAPER));
187#endif
188}
189
190LauncherContextMenu::~LauncherContextMenu() {
191}
192
193bool LauncherContextMenu::IsItemForCommandIdDynamic(int command_id) const {
194  return command_id == MENU_OPEN_NEW;
195}
196
197base::string16 LauncherContextMenu::GetLabelForCommandId(int command_id) const {
198  if (command_id == MENU_OPEN_NEW) {
199    if (item_.type == ash::TYPE_PLATFORM_APP) {
200      return l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_NEW_WINDOW);
201    }
202    switch (controller_->GetLaunchType(item_.id)) {
203      case extensions::LAUNCH_TYPE_PINNED:
204      case extensions::LAUNCH_TYPE_REGULAR:
205        return l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_NEW_TAB);
206      case extensions::LAUNCH_TYPE_FULLSCREEN:
207      case extensions::LAUNCH_TYPE_WINDOW:
208        return l10n_util::GetStringUTF16(IDS_LAUNCHER_CONTEXT_MENU_NEW_WINDOW);
209      default:
210        NOTREACHED();
211        return base::string16();
212    }
213  }
214  NOTREACHED();
215  return base::string16();
216}
217
218bool LauncherContextMenu::IsCommandIdChecked(int command_id) const {
219  switch (command_id) {
220    case LAUNCH_TYPE_PINNED_TAB:
221      return controller_->GetLaunchType(item_.id) ==
222          extensions::LAUNCH_TYPE_PINNED;
223    case LAUNCH_TYPE_REGULAR_TAB:
224      return controller_->GetLaunchType(item_.id) ==
225          extensions::LAUNCH_TYPE_REGULAR;
226    case LAUNCH_TYPE_WINDOW:
227      return controller_->GetLaunchType(item_.id) ==
228          extensions::LAUNCH_TYPE_WINDOW;
229    case LAUNCH_TYPE_FULLSCREEN:
230      return controller_->GetLaunchType(item_.id) ==
231          extensions::LAUNCH_TYPE_FULLSCREEN;
232    case MENU_AUTO_HIDE:
233      return controller_->GetShelfAutoHideBehavior(root_window_) ==
234          ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
235    default:
236      return extension_items_->IsCommandIdChecked(command_id);
237  }
238}
239
240bool LauncherContextMenu::IsCommandIdEnabled(int command_id) const {
241  switch (command_id) {
242    case MENU_PIN:
243      return controller_->IsPinnable(item_.id);
244#if defined(OS_CHROMEOS)
245    case MENU_CHANGE_WALLPAPER:
246      return ash::Shell::GetInstance()->user_wallpaper_delegate()->
247          CanOpenSetWallpaperPage();
248#endif
249    case MENU_NEW_WINDOW:
250      // "Normal" windows are not allowed when incognito is enforced.
251      return IncognitoModePrefs::GetAvailability(
252          controller_->profile()->GetPrefs()) != IncognitoModePrefs::FORCED;
253    case MENU_AUTO_HIDE:
254      return controller_->CanUserModifyShelfAutoHideBehavior(root_window_);
255    case MENU_NEW_INCOGNITO_WINDOW:
256      // Incognito windows are not allowed when incognito is disabled.
257      return IncognitoModePrefs::GetAvailability(
258          controller_->profile()->GetPrefs()) != IncognitoModePrefs::DISABLED;
259    default:
260      return extension_items_->IsCommandIdEnabled(command_id);
261  }
262}
263
264bool LauncherContextMenu::IsCommandIdVisible(int command_id) const {
265  if (item_.type != ash::TYPE_PLATFORM_APP)
266    return true;
267
268  switch (command_id) {
269    case MENU_PIN:
270      return !controller_->CanInstall(item_.id);
271    case MENU_INSTALL:
272      return controller_->CanInstall(item_.id);
273    default:
274      return true;
275  }
276}
277
278bool LauncherContextMenu::GetAcceleratorForCommandId(
279      int command_id,
280      ui::Accelerator* accelerator) {
281  return false;
282}
283
284void LauncherContextMenu::ExecuteCommand(int command_id, int event_flags) {
285  switch (static_cast<MenuItem>(command_id)) {
286    case MENU_OPEN_NEW:
287      controller_->Launch(item_.id, ui::EF_NONE);
288      break;
289    case MENU_CLOSE:
290      if (item_.type == ash::TYPE_DIALOG) {
291        DCHECK(item_delegate_);
292        item_delegate_->Close();
293      } else {
294        // TODO(simonhong): Use ShelfItemDelegate::Close().
295        controller_->Close(item_.id);
296      }
297      ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
298          ash::UMA_CLOSE_THROUGH_CONTEXT_MENU);
299      break;
300    case MENU_PIN:
301      controller_->TogglePinned(item_.id);
302      break;
303    case MENU_INSTALL:
304      controller_->Install(item_.id);
305      break;
306    case LAUNCH_TYPE_PINNED_TAB:
307      controller_->SetLaunchType(item_.id, extensions::LAUNCH_TYPE_PINNED);
308      break;
309    case LAUNCH_TYPE_REGULAR_TAB: {
310      extensions::LaunchType launch_type =
311          extensions::LAUNCH_TYPE_REGULAR;
312      // Streamlined hosted apps can only toggle between LAUNCH_WINDOW and
313      // LAUNCH_REGULAR.
314      if (CommandLine::ForCurrentProcess()->HasSwitch(
315              switches::kEnableStreamlinedHostedApps)) {
316        launch_type = controller_->GetLaunchType(item_.id) ==
317                    extensions::LAUNCH_TYPE_REGULAR
318                ? extensions::LAUNCH_TYPE_WINDOW
319                : extensions::LAUNCH_TYPE_REGULAR;
320      }
321      controller_->SetLaunchType(item_.id, launch_type);
322      break;
323    }
324    case LAUNCH_TYPE_WINDOW:
325      controller_->SetLaunchType(item_.id, extensions::LAUNCH_TYPE_WINDOW);
326      break;
327    case LAUNCH_TYPE_FULLSCREEN:
328      controller_->SetLaunchType(item_.id, extensions::LAUNCH_TYPE_FULLSCREEN);
329      break;
330    case MENU_AUTO_HIDE:
331      controller_->ToggleShelfAutoHideBehavior(root_window_);
332      break;
333    case MENU_NEW_WINDOW:
334      controller_->CreateNewWindow();
335      break;
336    case MENU_NEW_INCOGNITO_WINDOW:
337      controller_->CreateNewIncognitoWindow();
338      break;
339    case MENU_ALIGNMENT_MENU:
340      break;
341#if defined(OS_CHROMEOS)
342    case MENU_CHANGE_WALLPAPER:
343      ash::Shell::GetInstance()->user_wallpaper_delegate()->
344          OpenSetWallpaperPage();
345      break;
346#endif
347    default:
348      extension_items_->ExecuteCommand(command_id, NULL,
349                                       content::ContextMenuParams());
350  }
351}
352