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