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
35namespace app_list {
36
37namespace {
38
39// Border padding space around the bubble contents.
40const int kPadding = 1;
41
42// The maximum allowed time to wait for icon loading in milliseconds.
43const 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.
49class 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
76class 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
109AppListMainView::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
143void 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
163AppListMainView::~AppListMainView() {
164  pending_icon_loaders_.clear();
165}
166
167void 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
183void 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
194void AppListMainView::Close() {
195  icon_loading_wait_timer_.Stop();
196  contents_view_->CancelDrag();
197}
198
199void AppListMainView::Prerender() {
200  contents_view_->Prerender();
201}
202
203void 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
217void 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
226void 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
233void AppListMainView::SetDragAndDropHostOfCurrentAppList(
234    ApplicationDragAndDropHost* drag_and_drop_host) {
235  contents_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
236}
237
238bool AppListMainView::ShouldCenterWindow() const {
239  return delegate_->ShouldCenterWindow();
240}
241
242PaginationModel* AppListMainView::GetAppsPaginationModel() {
243  return contents_view_->apps_container_view()
244      ->apps_grid_view()
245      ->pagination_model();
246}
247
248void 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
278void AppListMainView::OnIconLoadingWaitTimer() {
279  GetWidget()->Show();
280}
281
282void 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
294void 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
302void 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
310void 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
316void AppListMainView::CancelDragInActiveFolder() {
317  contents_view_->apps_container_view()
318      ->app_list_folder_view()
319      ->items_grid_view()
320      ->EndDrag(true);
321}
322
323void 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
333void 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
339void 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