Home | History | Annotate | Download | only in launcher
      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