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 "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 
     28 using extensions::AppWindow;
     29 
     30 namespace {
     31 
     32 // Size of the icon in the shelf launcher in display-independent pixels.
     33 const 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.
     37 gfx::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.
     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 
    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 
    225 bool 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 
    258 base::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 
    272 ui::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 
    278 ash::ShelfMenuModel* AppWindowLauncherItemController::CreateApplicationMenu(
    279     int event_flags) {
    280   return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
    281 }
    282 
    283 bool AppWindowLauncherItemController::IsDraggable() {
    284   if (type() == TYPE_APP_PANEL)
    285     return true;
    286   return launcher_controller()->CanPin() ? true : false;
    287 }
    288 
    289 bool AppWindowLauncherItemController::ShouldShowTooltip() {
    290   if (type() == TYPE_APP_PANEL && IsVisible())
    291     return false;
    292   return true;
    293 }
    294 
    295 void 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 
    312 void 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 
    319 void 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