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 "apps/app_window.h" 8#include "apps/ui/native_app_window.h" 9#include "ash/shelf/shelf_model.h" 10#include "ash/wm/window_state.h" 11#include "ash/wm/window_util.h" 12#include "chrome/browser/extensions/webstore_install_with_prompt.h" 13#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h" 14#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_v2app.h" 15#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" 16#include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h" 17#include "chrome/browser/ui/ash/launcher/launcher_context_menu.h" 18#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h" 19#include "content/public/browser/web_contents.h" 20#include "skia/ext/image_operations.h" 21#include "ui/aura/client/aura_constants.h" 22#include "ui/aura/window.h" 23#include "ui/events/event.h" 24#include "ui/gfx/image/image_skia.h" 25#include "ui/wm/core/window_animations.h" 26 27using apps::AppWindow; 28 29namespace { 30 31// Size of the icon in the shelf launcher in display-independent pixels. 32const int kAppListIconSize = 24; 33 34// This will return a slightly smaller icon than the app icon to be used in 35// the application list menu. 36scoped_ptr<gfx::Image> GetAppListIcon(AppWindow* app_window) { 37 // TODO(skuhne): We instead might want to use LoadImages in 38 // AppWindow::UpdateExtensionAppIcon() to let the extension give us 39 // pre-defined icons in the launcher and the launcher list sizes. Since there 40 // is no mock yet, doing this now seems a bit premature and we scale for the 41 // time being. 42 if (app_window->app_icon().IsEmpty()) 43 return make_scoped_ptr(new gfx::Image()); 44 45 SkBitmap bmp = 46 skia::ImageOperations::Resize(*app_window->app_icon().ToSkBitmap(), 47 skia::ImageOperations::RESIZE_BEST, 48 kAppListIconSize, 49 kAppListIconSize); 50 return make_scoped_ptr( 51 new 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 scoped_ptr<gfx::Image> image(GetAppListIcon(app_window)); 205 items.push_back(new ChromeLauncherAppMenuItemV2App( 206 app_window->GetTitle(), 207 image.get(), // Will be copied 208 app_id(), 209 launcher_controller(), 210 index, 211 index == 0 /* has_leading_separator */)); 212 ++index; 213 } 214 return items.Pass(); 215} 216 217bool AppWindowLauncherItemController::ItemSelected(const ui::Event& event) { 218 if (app_windows_.empty()) 219 return false; 220 if (type() == TYPE_APP_PANEL) { 221 DCHECK(app_windows_.size() == 1); 222 AppWindow* panel = app_windows_.front(); 223 aura::Window* panel_window = panel->GetNativeWindow(); 224 // If the panel is attached on another display, move it to the current 225 // display and activate it. 226 if (ash::wm::GetWindowState(panel_window)->panel_attached() && 227 ash::wm::MoveWindowToEventRoot(panel_window, event)) { 228 if (!panel->GetBaseWindow()->IsActive()) 229 ShowAndActivateOrMinimize(panel); 230 } else { 231 ShowAndActivateOrMinimize(panel); 232 } 233 } else { 234 AppWindow* window_to_show = last_active_app_window_ 235 ? last_active_app_window_ 236 : app_windows_.front(); 237 // If the event was triggered by a keystroke, we try to advance to the next 238 // item if the window we are trying to activate is already active. 239 if (app_windows_.size() >= 1 && 240 window_to_show->GetBaseWindow()->IsActive() && 241 event.type() == ui::ET_KEY_RELEASED) { 242 ActivateOrAdvanceToNextAppWindow(window_to_show); 243 } else { 244 ShowAndActivateOrMinimize(window_to_show); 245 } 246 } 247 return false; 248} 249 250base::string16 AppWindowLauncherItemController::GetTitle() { 251 // For panels return the title of the contents if set. 252 // Otherwise return the title of the app. 253 if (type() == TYPE_APP_PANEL && !app_windows_.empty()) { 254 AppWindow* app_window = app_windows_.front(); 255 if (app_window->web_contents()) { 256 base::string16 title = app_window->web_contents()->GetTitle(); 257 if (!title.empty()) 258 return title; 259 } 260 } 261 return GetAppTitle(); 262} 263 264ui::MenuModel* AppWindowLauncherItemController::CreateContextMenu( 265 aura::Window* root_window) { 266 ash::ShelfItem item = *(launcher_controller()->model()->ItemByID(shelf_id())); 267 return new LauncherContextMenu(launcher_controller(), &item, root_window); 268} 269 270ash::ShelfMenuModel* AppWindowLauncherItemController::CreateApplicationMenu( 271 int event_flags) { 272 return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags)); 273} 274 275bool AppWindowLauncherItemController::IsDraggable() { 276 if (type() == TYPE_APP_PANEL) 277 return true; 278 return launcher_controller()->CanPin() ? true : false; 279} 280 281bool AppWindowLauncherItemController::ShouldShowTooltip() { 282 if (type() == TYPE_APP_PANEL && IsVisible()) 283 return false; 284 return true; 285} 286 287void AppWindowLauncherItemController::OnWindowPropertyChanged( 288 aura::Window* window, 289 const void* key, 290 intptr_t old) { 291 if (key == aura::client::kDrawAttentionKey) { 292 ash::ShelfItemStatus status; 293 if (ash::wm::IsActiveWindow(window)) { 294 status = ash::STATUS_ACTIVE; 295 } else if (window->GetProperty(aura::client::kDrawAttentionKey)) { 296 status = ash::STATUS_ATTENTION; 297 } else { 298 status = ash::STATUS_RUNNING; 299 } 300 launcher_controller()->SetItemStatus(shelf_id(), status); 301 } 302} 303 304void AppWindowLauncherItemController::ShowAndActivateOrMinimize( 305 AppWindow* app_window) { 306 // Either show or minimize windows when shown from the launcher. 307 launcher_controller()->ActivateWindowOrMinimizeIfActive( 308 app_window->GetBaseWindow(), GetApplicationList(0).size() == 2); 309} 310 311void AppWindowLauncherItemController::ActivateOrAdvanceToNextAppWindow( 312 AppWindow* window_to_show) { 313 AppWindowList::iterator i( 314 std::find(app_windows_.begin(), app_windows_.end(), window_to_show)); 315 if (i != app_windows_.end()) { 316 if (++i != app_windows_.end()) 317 window_to_show = *i; 318 else 319 window_to_show = app_windows_.front(); 320 } 321 if (window_to_show->GetBaseWindow()->IsActive()) { 322 // Coming here, only a single window is active. For keyboard activations 323 // the window gets animated. 324 AnimateWindow(window_to_show->GetNativeWindow(), 325 wm::WINDOW_ANIMATION_TYPE_BOUNCE); 326 } else { 327 ShowAndActivateOrMinimize(window_to_show); 328 } 329} 330