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:
SearchBoxContainerView(AppListMainView * host,SearchBoxView * search_box)51 SearchBoxContainerView(AppListMainView* host, SearchBoxView* search_box)
52 : host_(host), search_box_(search_box) {
53 SetLayoutManager(new views::FillLayout());
54 AddChildView(search_box);
55 }
~SearchBoxContainerView()56 virtual ~SearchBoxContainerView() {}
57
58 private:
59 // Overridden from views::View:
ChildVisibilityChanged(views::View * child)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:
IconLoader(AppListMainView * owner,AppListItem * item,float scale)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
~IconLoader()89 virtual ~IconLoader() {
90 item_->RemoveObserver(this);
91 }
92
93 private:
94 // AppListItemObserver overrides:
ItemIconChanged()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
AppListMainView(AppListViewDelegate * delegate,int initial_apps_page,gfx::NativeView parent)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
AddContentsViews()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
~AppListMainView()163 AppListMainView::~AppListMainView() {
164 pending_icon_loaders_.clear();
165 }
166
ShowAppListWhenReady()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
ResetForShow()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
Close()194 void AppListMainView::Close() {
195 icon_loading_wait_timer_.Stop();
196 contents_view_->CancelDrag();
197 }
198
Prerender()199 void AppListMainView::Prerender() {
200 contents_view_->Prerender();
201 }
202
ModelChanged()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
UpdateSearchBoxVisibility()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
OnStartPageSearchTextfieldChanged(const base::string16 & new_contents)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
SetDragAndDropHostOfCurrentAppList(ApplicationDragAndDropHost * drag_and_drop_host)233 void AppListMainView::SetDragAndDropHostOfCurrentAppList(
234 ApplicationDragAndDropHost* drag_and_drop_host) {
235 contents_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
236 }
237
ShouldCenterWindow() const238 bool AppListMainView::ShouldCenterWindow() const {
239 return delegate_->ShouldCenterWindow();
240 }
241
GetAppsPaginationModel()242 PaginationModel* AppListMainView::GetAppsPaginationModel() {
243 return contents_view_->apps_container_view()
244 ->apps_grid_view()
245 ->pagination_model();
246 }
247
PreloadIcons(gfx::NativeView parent)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
OnIconLoadingWaitTimer()278 void AppListMainView::OnIconLoadingWaitTimer() {
279 GetWidget()->Show();
280 }
281
OnItemIconLoaded(IconLoader * loader)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
NotifySearchBoxVisibilityChanged()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
ActivateApp(AppListItem * item,int event_flags)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
GetShortcutPathForApp(const std::string & app_id,const base::Callback<void (const base::FilePath &)> & callback)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
CancelDragInActiveFolder()316 void AppListMainView::CancelDragInActiveFolder() {
317 contents_view_->apps_container_view()
318 ->app_list_folder_view()
319 ->items_grid_view()
320 ->EndDrag(true);
321 }
322
QueryChanged(SearchBoxView * sender)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
OnResultInstalled(SearchResult * result)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
OnResultUninstalled(SearchResult * result)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