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 27 using apps::AppWindow; 28 29 namespace { 30 31 // Size of the icon in the shelf launcher in display-independent pixels. 32 const 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. 36 scoped_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. 56 bool 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. 63 class 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 77 AppWindowLauncherItemController::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 87 AppWindowLauncherItemController::~AppWindowLauncherItemController() {} 88 89 void 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 98 void 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 110 void 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 117 bool AppWindowLauncherItemController::IsOpen() const { 118 return !app_windows_.empty(); 119 } 120 121 bool 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 132 void AppWindowLauncherItemController::Launch(ash::LaunchSource source, 133 int event_flags) { 134 launcher_controller()->LaunchApp(app_id(), source, ui::EF_NONE); 135 } 136 137 bool 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 145 void 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 155 void 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 163 void 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 195 ChromeLauncherAppMenuItems 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 217 bool 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 250 base::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 264 ui::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 270 ash::ShelfMenuModel* AppWindowLauncherItemController::CreateApplicationMenu( 271 int event_flags) { 272 return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags)); 273 } 274 275 bool AppWindowLauncherItemController::IsDraggable() { 276 if (type() == TYPE_APP_PANEL) 277 return true; 278 return launcher_controller()->CanPin() ? true : false; 279 } 280 281 bool AppWindowLauncherItemController::ShouldShowTooltip() { 282 if (type() == TYPE_APP_PANEL && IsVisible()) 283 return false; 284 return true; 285 } 286 287 void 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 304 void 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 311 void 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