Home | History | Annotate | Download | only in views
      1 // Copyright 2013 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 "ui/app_list/views/app_list_main_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/callback.h"
     11 #include "base/files/file_path.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/strings/string_util.h"
     14 #include "ui/app_list/app_list_constants.h"
     15 #include "ui/app_list/app_list_folder_item.h"
     16 #include "ui/app_list/app_list_item.h"
     17 #include "ui/app_list/app_list_model.h"
     18 #include "ui/app_list/app_list_switches.h"
     19 #include "ui/app_list/app_list_view_delegate.h"
     20 #include "ui/app_list/pagination_model.h"
     21 #include "ui/app_list/search_box_model.h"
     22 #include "ui/app_list/views/app_list_folder_view.h"
     23 #include "ui/app_list/views/app_list_item_view.h"
     24 #include "ui/app_list/views/apps_container_view.h"
     25 #include "ui/app_list/views/apps_grid_view.h"
     26 #include "ui/app_list/views/contents_switcher_view.h"
     27 #include "ui/app_list/views/contents_view.h"
     28 #include "ui/app_list/views/search_box_view.h"
     29 #include "ui/views/border.h"
     30 #include "ui/views/controls/textfield/textfield.h"
     31 #include "ui/views/layout/box_layout.h"
     32 #include "ui/views/layout/fill_layout.h"
     33 #include "ui/views/widget/widget.h"
     34 
     35 namespace app_list {
     36 
     37 namespace {
     38 
     39 // Border padding space around the bubble contents.
     40 const int kPadding = 1;
     41 
     42 // The maximum allowed time to wait for icon loading in milliseconds.
     43 const int kMaxIconLoadingWaitTimeInMs = 50;
     44 
     45 // A view that holds another view and takes its preferred size. This is used for
     46 // wrapping the search box view so it still gets laid out while hidden. This is
     47 // a separate class so it can notify the main view on search box visibility
     48 // change.
     49 class SearchBoxContainerView : public views::View {
     50  public:
     51   SearchBoxContainerView(AppListMainView* host, SearchBoxView* search_box)
     52       : host_(host), search_box_(search_box) {
     53     SetLayoutManager(new views::FillLayout());
     54     AddChildView(search_box);
     55   }
     56   virtual ~SearchBoxContainerView() {}
     57 
     58  private:
     59   // Overridden from views::View:
     60   virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
     61     DCHECK_EQ(search_box_, child);
     62     host_->NotifySearchBoxVisibilityChanged();
     63   }
     64 
     65   AppListMainView* host_;
     66   SearchBoxView* search_box_;
     67 
     68   DISALLOW_COPY_AND_ASSIGN(SearchBoxContainerView);
     69 };
     70 
     71 }  // namespace
     72 
     73 ////////////////////////////////////////////////////////////////////////////////
     74 // AppListMainView::IconLoader
     75 
     76 class AppListMainView::IconLoader : public AppListItemObserver {
     77  public:
     78   IconLoader(AppListMainView* owner,
     79              AppListItem* item,
     80              float scale)
     81       : owner_(owner),
     82         item_(item) {
     83     item_->AddObserver(this);
     84 
     85     // Triggers icon loading for given |scale_factor|.
     86     item_->icon().GetRepresentation(scale);
     87   }
     88 
     89   virtual ~IconLoader() {
     90     item_->RemoveObserver(this);
     91   }
     92 
     93  private:
     94   // AppListItemObserver overrides:
     95   virtual void ItemIconChanged() OVERRIDE {
     96     owner_->OnItemIconLoaded(this);
     97     // Note that IconLoader is released here.
     98   }
     99 
    100   AppListMainView* owner_;
    101   AppListItem* item_;
    102 
    103   DISALLOW_COPY_AND_ASSIGN(IconLoader);
    104 };
    105 
    106 ////////////////////////////////////////////////////////////////////////////////
    107 // AppListMainView:
    108 
    109 AppListMainView::AppListMainView(AppListViewDelegate* delegate,
    110                                  int initial_apps_page,
    111                                  gfx::NativeView parent)
    112     : delegate_(delegate),
    113       model_(delegate->GetModel()),
    114       search_box_view_(NULL),
    115       contents_view_(NULL),
    116       contents_switcher_view_(NULL),
    117       weak_ptr_factory_(this) {
    118   SetBorder(
    119       views::Border::CreateEmptyBorder(kPadding, kPadding, kPadding, kPadding));
    120   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
    121 
    122   search_box_view_ = new SearchBoxView(this, delegate);
    123   views::View* container = new SearchBoxContainerView(this, search_box_view_);
    124   if (switches::IsExperimentalAppListEnabled()) {
    125     container->SetBorder(
    126         views::Border::CreateEmptyBorder(kExperimentalWindowPadding,
    127                                          kExperimentalWindowPadding,
    128                                          0,
    129                                          kExperimentalWindowPadding));
    130   }
    131   AddChildView(container);
    132   AddContentsViews();
    133 
    134   // Switch the apps grid view to the specified page.
    135   app_list::PaginationModel* pagination_model = GetAppsPaginationModel();
    136   if (pagination_model->is_valid_page(initial_apps_page))
    137     pagination_model->SelectPage(initial_apps_page, false);
    138 
    139   // Starts icon loading early.
    140   PreloadIcons(parent);
    141 }
    142 
    143 void AppListMainView::AddContentsViews() {
    144   contents_view_ = new ContentsView(this);
    145   if (app_list::switches::IsExperimentalAppListEnabled()) {
    146     contents_switcher_view_ = new ContentsSwitcherView(contents_view_);
    147     contents_view_->SetContentsSwitcherView(contents_switcher_view_);
    148   }
    149   contents_view_->InitNamedPages(model_, delegate_);
    150   AddChildView(contents_view_);
    151   if (contents_switcher_view_)
    152     AddChildView(contents_switcher_view_);
    153 
    154   search_box_view_->set_contents_view(contents_view_);
    155 
    156   contents_view_->SetPaintToLayer(true);
    157   contents_view_->SetFillsBoundsOpaquely(false);
    158   contents_view_->layer()->SetMasksToBounds(true);
    159 
    160   delegate_->StartSearch();
    161 }
    162 
    163 AppListMainView::~AppListMainView() {
    164   pending_icon_loaders_.clear();
    165 }
    166 
    167 void AppListMainView::ShowAppListWhenReady() {
    168   if (pending_icon_loaders_.empty()) {
    169     icon_loading_wait_timer_.Stop();
    170     GetWidget()->Show();
    171     return;
    172   }
    173 
    174   if (icon_loading_wait_timer_.IsRunning())
    175     return;
    176 
    177   icon_loading_wait_timer_.Start(
    178       FROM_HERE,
    179       base::TimeDelta::FromMilliseconds(kMaxIconLoadingWaitTimeInMs),
    180       this, &AppListMainView::OnIconLoadingWaitTimer);
    181 }
    182 
    183 void AppListMainView::ResetForShow() {
    184   if (switches::IsExperimentalAppListEnabled()) {
    185     contents_view_->SetActivePage(contents_view_->GetPageIndexForNamedPage(
    186         ContentsView::NAMED_PAGE_START));
    187   }
    188   contents_view_->apps_container_view()->ResetForShowApps();
    189   // We clear the search when hiding so when app list appears it is not showing
    190   // search results.
    191   search_box_view_->ClearSearch();
    192 }
    193 
    194 void AppListMainView::Close() {
    195   icon_loading_wait_timer_.Stop();
    196   contents_view_->CancelDrag();
    197 }
    198 
    199 void AppListMainView::Prerender() {
    200   contents_view_->Prerender();
    201 }
    202 
    203 void AppListMainView::ModelChanged() {
    204   pending_icon_loaders_.clear();
    205   model_ = delegate_->GetModel();
    206   search_box_view_->ModelChanged();
    207   delete contents_view_;
    208   contents_view_ = NULL;
    209   if (contents_switcher_view_) {
    210     delete contents_switcher_view_;
    211     contents_switcher_view_ = NULL;
    212   }
    213   AddContentsViews();
    214   Layout();
    215 }
    216 
    217 void AppListMainView::UpdateSearchBoxVisibility() {
    218   bool visible =
    219       !contents_view_->IsNamedPageActive(ContentsView::NAMED_PAGE_START) ||
    220       contents_view_->IsShowingSearchResults();
    221   search_box_view_->SetVisible(visible);
    222   if (visible && GetWidget() && GetWidget()->IsVisible())
    223     search_box_view_->search_box()->RequestFocus();
    224 }
    225 
    226 void AppListMainView::OnStartPageSearchTextfieldChanged(
    227     const base::string16& new_contents) {
    228   search_box_view_->SetVisible(true);
    229   search_box_view_->search_box()->SetText(new_contents);
    230   search_box_view_->search_box()->RequestFocus();
    231 }
    232 
    233 void AppListMainView::SetDragAndDropHostOfCurrentAppList(
    234     ApplicationDragAndDropHost* drag_and_drop_host) {
    235   contents_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
    236 }
    237 
    238 bool AppListMainView::ShouldCenterWindow() const {
    239   return delegate_->ShouldCenterWindow();
    240 }
    241 
    242 PaginationModel* AppListMainView::GetAppsPaginationModel() {
    243   return contents_view_->apps_container_view()
    244       ->apps_grid_view()
    245       ->pagination_model();
    246 }
    247 
    248 void AppListMainView::PreloadIcons(gfx::NativeView parent) {
    249   float scale_factor = 1.0f;
    250   if (parent)
    251     scale_factor = ui::GetScaleFactorForNativeView(parent);
    252 
    253   // The PaginationModel could have -1 as the initial selected page and
    254   // assumes first page (i.e. index 0) will be used in this case.
    255   const int selected_page =
    256       std::max(0, GetAppsPaginationModel()->selected_page());
    257 
    258   const AppsGridView* const apps_grid_view =
    259       contents_view_->apps_container_view()->apps_grid_view();
    260   const int tiles_per_page =
    261       apps_grid_view->cols() * apps_grid_view->rows_per_page();
    262 
    263   const int start_model_index = selected_page * tiles_per_page;
    264   const int end_model_index =
    265       std::min(static_cast<int>(model_->top_level_item_list()->item_count()),
    266                start_model_index + tiles_per_page);
    267 
    268   pending_icon_loaders_.clear();
    269   for (int i = start_model_index; i < end_model_index; ++i) {
    270     AppListItem* item = model_->top_level_item_list()->item_at(i);
    271     if (item->icon().HasRepresentation(scale_factor))
    272       continue;
    273 
    274     pending_icon_loaders_.push_back(new IconLoader(this, item, scale_factor));
    275   }
    276 }
    277 
    278 void AppListMainView::OnIconLoadingWaitTimer() {
    279   GetWidget()->Show();
    280 }
    281 
    282 void AppListMainView::OnItemIconLoaded(IconLoader* loader) {
    283   ScopedVector<IconLoader>::iterator it = std::find(
    284       pending_icon_loaders_.begin(), pending_icon_loaders_.end(), loader);
    285   DCHECK(it != pending_icon_loaders_.end());
    286   pending_icon_loaders_.erase(it);
    287 
    288   if (pending_icon_loaders_.empty() && icon_loading_wait_timer_.IsRunning()) {
    289     icon_loading_wait_timer_.Stop();
    290     GetWidget()->Show();
    291   }
    292 }
    293 
    294 void AppListMainView::NotifySearchBoxVisibilityChanged() {
    295   // Repaint the AppListView's background which will repaint the background for
    296   // the search box. This is needed because this view paints to a layer and
    297   // won't propagate paints upward.
    298   if (parent())
    299     parent()->SchedulePaint();
    300 }
    301 
    302 void AppListMainView::ActivateApp(AppListItem* item, int event_flags) {
    303   // TODO(jennyz): Activate the folder via AppListModel notification.
    304   if (item->GetItemType() == AppListFolderItem::kItemType)
    305     contents_view_->ShowFolderContent(static_cast<AppListFolderItem*>(item));
    306   else
    307     item->Activate(event_flags);
    308 }
    309 
    310 void AppListMainView::GetShortcutPathForApp(
    311     const std::string& app_id,
    312     const base::Callback<void(const base::FilePath&)>& callback) {
    313   delegate_->GetShortcutPathForApp(app_id, callback);
    314 }
    315 
    316 void AppListMainView::CancelDragInActiveFolder() {
    317   contents_view_->apps_container_view()
    318       ->app_list_folder_view()
    319       ->items_grid_view()
    320       ->EndDrag(true);
    321 }
    322 
    323 void AppListMainView::QueryChanged(SearchBoxView* sender) {
    324   base::string16 query;
    325   base::TrimWhitespace(model_->search_box()->text(), base::TRIM_ALL, &query);
    326   bool should_show_search = !query.empty();
    327   contents_view_->ShowSearchResults(should_show_search);
    328   UpdateSearchBoxVisibility();
    329 
    330   delegate_->StartSearch();
    331 }
    332 
    333 void AppListMainView::OnResultInstalled(SearchResult* result) {
    334   // Clears the search to show the apps grid. The last installed app
    335   // should be highlighted and made visible already.
    336   search_box_view_->ClearSearch();
    337 }
    338 
    339 void AppListMainView::OnResultUninstalled(SearchResult* result) {
    340   // Resubmit the query via a posted task so that all observers for the
    341   // uninstall notification are notified.
    342   base::MessageLoop::current()->PostTask(
    343       FROM_HERE,
    344       base::Bind(&AppListMainView::QueryChanged,
    345                  weak_ptr_factory_.GetWeakPtr(),
    346                  search_box_view_));
    347 }
    348 
    349 }  // namespace app_list
    350