1// Copyright 2014 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/app_window_launcher_item_controller.h"
6
7#include "ash/shelf/shelf_model.h"
8#include "ash/wm/window_state.h"
9#include "ash/wm/window_util.h"
10#include "chrome/browser/extensions/webstore_install_with_prompt.h"
11#include "chrome/browser/favicon/favicon_tab_helper.h"
12#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
13#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h"
14#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
15#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
16#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
17#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
18#include "content/public/browser/web_contents.h"
19#include "extensions/browser/app_window/app_window.h"
20#include "extensions/browser/app_window/native_app_window.h"
21#include "skia/ext/image_operations.h"
22#include "ui/aura/client/aura_constants.h"
23#include "ui/aura/window.h"
24#include "ui/events/event.h"
25#include "ui/gfx/image/image_skia.h"
26#include "ui/wm/core/window_animations.h"
27
28using extensions::AppWindow;
29
30namespace {
31
32// Size of the icon in the shelf launcher in display-independent pixels.
33const int kAppListIconSize = 24;
34
35// This will return a slightly smaller icon than the app icon to be used in
36// the application list menu.
37gfx::Image GetAppListIcon(AppWindow* app_window) {
38  // TODO(skuhne): We instead might want to use LoadImages in
39  // AppWindow::UpdateExtensionAppIcon() to let the extension give us
40  // pre-defined icons in the launcher and the launcher list sizes. Since there
41  // is no mock yet, doing this now seems a bit premature and we scale for the
42  // time being.
43  if (app_window->app_icon().IsEmpty())
44    return gfx::Image();
45
46  SkBitmap bmp =
47      skia::ImageOperations::Resize(*app_window->app_icon().ToSkBitmap(),
48                                    skia::ImageOperations::RESIZE_BEST,
49                                    kAppListIconSize,
50                                    kAppListIconSize);
51  return gfx::Image(gfx::ImageSkia::CreateFrom1xBitmap(bmp));
52}
53
54// Returns true if the app window is visible on screen, i.e. not hidden or
55// minimized.
56bool IsAppWindowVisible(AppWindow* app_window) {
57  return app_window &&
58         !app_window->is_hidden() &&
59         !app_window->GetBaseWindow()->IsMinimized();
60}
61
62// Functor for std::find_if used in AppLauncherItemController.
63class AppWindowHasWindow {
64 public:
65  explicit AppWindowHasWindow(aura::Window* window) : window_(window) {}
66
67  bool operator()(AppWindow* app_window) const {
68    return app_window->GetNativeWindow() == window_;
69  }
70
71 private:
72  const aura::Window* window_;
73};
74
75}  // namespace
76
77AppWindowLauncherItemController::AppWindowLauncherItemController(
78    Type type,
79    const std::string& app_shelf_id,
80    const std::string& app_id,
81    ChromeLauncherController* controller)
82    : LauncherItemController(type, app_id, controller),
83      last_active_app_window_(NULL),
84      app_shelf_id_(app_shelf_id),
85      observed_windows_(this) {}
86
87AppWindowLauncherItemController::~AppWindowLauncherItemController() {}
88
89void AppWindowLauncherItemController::AddAppWindow(
90    AppWindow* app_window,
91    ash::ShelfItemStatus status) {
92  if (app_window->window_type_is_panel() && type() != TYPE_APP_PANEL)
93    LOG(ERROR) << "AppWindow of type Panel added to non-panel launcher item";
94  app_windows_.push_front(app_window);
95  observed_windows_.Add(app_window->GetNativeWindow());
96}
97
98void AppWindowLauncherItemController::RemoveAppWindowForWindow(
99    aura::Window* window) {
100  AppWindowList::iterator iter = std::find_if(
101      app_windows_.begin(), app_windows_.end(), AppWindowHasWindow(window));
102  if (iter != app_windows_.end()) {
103    if (*iter == last_active_app_window_)
104      last_active_app_window_ = NULL;
105    app_windows_.erase(iter);
106  }
107  observed_windows_.Remove(window);
108}
109
110void AppWindowLauncherItemController::SetActiveWindow(aura::Window* window) {
111  AppWindowList::iterator iter = std::find_if(
112      app_windows_.begin(), app_windows_.end(), AppWindowHasWindow(window));
113  if (iter != app_windows_.end())
114    last_active_app_window_ = *iter;
115}
116
117bool AppWindowLauncherItemController::IsOpen() const {
118  return !app_windows_.empty();
119}
120
121bool AppWindowLauncherItemController::IsVisible() const {
122  // Return true if any windows are visible.
123  for (AppWindowList::const_iterator iter = app_windows_.begin();
124       iter != app_windows_.end();
125       ++iter) {
126    if ((*iter)->GetNativeWindow()->IsVisible())
127      return true;
128  }
129  return false;
130}
131
132void AppWindowLauncherItemController::Launch(ash::LaunchSource source,
133                                             int event_flags) {
134  launcher_controller()->LaunchApp(app_id(), source, ui::EF_NONE);
135}
136
137bool AppWindowLauncherItemController::Activate(ash::LaunchSource source) {
138  DCHECK(!app_windows_.empty());
139  AppWindow* window_to_activate =
140      last_active_app_window_ ? last_active_app_window_ : app_windows_.back();
141  window_to_activate->GetBaseWindow()->Activate();
142  return false;
143}
144
145void AppWindowLauncherItemController::Close() {
146  // Note: Closing windows may affect the contents of app_windows_.
147  AppWindowList windows_to_close = app_windows_;
148  for (AppWindowList::iterator iter = windows_to_close.begin();
149       iter != windows_to_close.end();
150       ++iter) {
151    (*iter)->GetBaseWindow()->Close();
152  }
153}
154
155void AppWindowLauncherItemController::ActivateIndexedApp(size_t index) {
156  if (index >= app_windows_.size())
157    return;
158  AppWindowList::iterator it = app_windows_.begin();
159  std::advance(it, index);
160  ShowAndActivateOrMinimize(*it);
161}
162
163void AppWindowLauncherItemController::InstallApp() {
164  // Find a visible window in order to position the install dialog. If there is
165  // no visible window, the dialog will be centered on the screen.
166  AppWindow* parent_window = NULL;
167  if (IsAppWindowVisible(last_active_app_window_)) {
168    parent_window = last_active_app_window_;
169  } else {
170    for (AppWindowList::iterator iter = app_windows_.begin();
171         iter != app_windows_.end(); ++iter) {
172      if (IsAppWindowVisible(*iter)) {
173        parent_window = *iter;
174        break;
175      }
176    }
177  }
178
179  scoped_refptr<extensions::WebstoreInstallWithPrompt> installer;
180  if (parent_window) {
181    installer = new extensions::WebstoreInstallWithPrompt(
182        app_id(),
183        launcher_controller()->profile(),
184        parent_window->GetNativeWindow(),
185        extensions::WebstoreInstallWithPrompt::Callback());
186  } else {
187    installer = new extensions::WebstoreInstallWithPrompt(
188        app_id(),
189        launcher_controller()->profile(),
190        extensions::WebstoreInstallWithPrompt::Callback());
191  }
192  installer->BeginInstall();
193}
194
195ChromeLauncherAppMenuItems AppWindowLauncherItemController::GetApplicationList(
196    int event_flags) {
197  ChromeLauncherAppMenuItems items;
198  items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
199  int index = 0;
200  for (AppWindowList::iterator iter = app_windows_.begin();
201       iter != app_windows_.end();
202       ++iter) {
203    AppWindow* app_window = *iter;
204
205    // If the app's web contents provides a favicon, use it. Otherwise, use a
206    // scaled down app icon.
207    FaviconTabHelper* favicon_tab_helper =
208        FaviconTabHelper::FromWebContents(app_window->web_contents());
209    gfx::Image result = favicon_tab_helper->GetFavicon();
210    if (result.IsEmpty())
211      result = GetAppListIcon(app_window);
212
213    items.push_back(new ChromeLauncherAppMenuItemV2App(
214        app_window->GetTitle(),
215        &result,  // Will be copied
216        app_id(),
217        launcher_controller(),
218        index,
219        index == 0 /* has_leading_separator */));
220    ++index;
221  }
222  return items.Pass();
223}
224
225bool AppWindowLauncherItemController::ItemSelected(const ui::Event& event) {
226  if (app_windows_.empty())
227    return false;
228  if (type() == TYPE_APP_PANEL) {
229    DCHECK(app_windows_.size() == 1);
230    AppWindow* panel = app_windows_.front();
231    aura::Window* panel_window = panel->GetNativeWindow();
232    // If the panel is attached on another display, move it to the current
233    // display and activate it.
234    if (ash::wm::GetWindowState(panel_window)->panel_attached() &&
235        ash::wm::MoveWindowToEventRoot(panel_window, event)) {
236      if (!panel->GetBaseWindow()->IsActive())
237        ShowAndActivateOrMinimize(panel);
238    } else {
239      ShowAndActivateOrMinimize(panel);
240    }
241  } else {
242    AppWindow* window_to_show = last_active_app_window_
243                                    ? last_active_app_window_
244                                    : app_windows_.front();
245    // If the event was triggered by a keystroke, we try to advance to the next
246    // item if the window we are trying to activate is already active.
247    if (app_windows_.size() >= 1 &&
248        window_to_show->GetBaseWindow()->IsActive() &&
249        event.type() == ui::ET_KEY_RELEASED) {
250      ActivateOrAdvanceToNextAppWindow(window_to_show);
251    } else {
252      ShowAndActivateOrMinimize(window_to_show);
253    }
254  }
255  return false;
256}
257
258base::string16 AppWindowLauncherItemController::GetTitle() {
259  // For panels return the title of the contents if set.
260  // Otherwise return the title of the app.
261  if (type() == TYPE_APP_PANEL && !app_windows_.empty()) {
262    AppWindow* app_window = app_windows_.front();
263    if (app_window->web_contents()) {
264      base::string16 title = app_window->web_contents()->GetTitle();
265      if (!title.empty())
266        return title;
267    }
268  }
269  return GetAppTitle();
270}
271
272ui::MenuModel* AppWindowLauncherItemController::CreateContextMenu(
273    aura::Window* root_window) {
274  ash::ShelfItem item = *(launcher_controller()->model()->ItemByID(shelf_id()));
275  return new LauncherContextMenu(launcher_controller(), &item, root_window);
276}
277
278ash::ShelfMenuModel* AppWindowLauncherItemController::CreateApplicationMenu(
279    int event_flags) {
280  return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
281}
282
283bool AppWindowLauncherItemController::IsDraggable() {
284  if (type() == TYPE_APP_PANEL)
285    return true;
286  return launcher_controller()->CanPin() ? true : false;
287}
288
289bool AppWindowLauncherItemController::ShouldShowTooltip() {
290  if (type() == TYPE_APP_PANEL && IsVisible())
291    return false;
292  return true;
293}
294
295void AppWindowLauncherItemController::OnWindowPropertyChanged(
296    aura::Window* window,
297    const void* key,
298    intptr_t old) {
299  if (key == aura::client::kDrawAttentionKey) {
300    ash::ShelfItemStatus status;
301    if (ash::wm::IsActiveWindow(window)) {
302      status = ash::STATUS_ACTIVE;
303    } else if (window->GetProperty(aura::client::kDrawAttentionKey)) {
304      status = ash::STATUS_ATTENTION;
305    } else {
306      status = ash::STATUS_RUNNING;
307    }
308    launcher_controller()->SetItemStatus(shelf_id(), status);
309  }
310}
311
312void AppWindowLauncherItemController::ShowAndActivateOrMinimize(
313    AppWindow* app_window) {
314  // Either show or minimize windows when shown from the launcher.
315  launcher_controller()->ActivateWindowOrMinimizeIfActive(
316      app_window->GetBaseWindow(), GetApplicationList(0).size() == 2);
317}
318
319void AppWindowLauncherItemController::ActivateOrAdvanceToNextAppWindow(
320    AppWindow* window_to_show) {
321  AppWindowList::iterator i(
322      std::find(app_windows_.begin(), app_windows_.end(), window_to_show));
323  if (i != app_windows_.end()) {
324    if (++i != app_windows_.end())
325      window_to_show = *i;
326    else
327      window_to_show = app_windows_.front();
328  }
329  if (window_to_show->GetBaseWindow()->IsActive()) {
330    // Coming here, only a single window is active. For keyboard activations
331    // the window gets animated.
332    AnimateWindow(window_to_show->GetNativeWindow(),
333                  wm::WINDOW_ANIMATION_TYPE_BOUNCE);
334  } else {
335    ShowAndActivateOrMinimize(window_to_show);
336  }
337}
338