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_item_view.h"
23 #include "ui/app_list/views/apps_container_view.h"
24 #include "ui/app_list/views/apps_grid_view.h"
25 #include "ui/app_list/views/contents_switcher_view.h"
26 #include "ui/app_list/views/contents_view.h"
27 #include "ui/app_list/views/search_box_view.h"
28 #include "ui/views/controls/textfield/textfield.h"
29 #include "ui/views/layout/box_layout.h"
30 #include "ui/views/widget/widget.h"
31
32 namespace app_list {
33
34 namespace {
35
36 // Inner padding space in pixels of bubble contents.
37 const int kInnerPadding = 1;
38
39 // The maximum allowed time to wait for icon loading in milliseconds.
40 const int kMaxIconLoadingWaitTimeInMs = 50;
41
42 } // namespace
43
44 ////////////////////////////////////////////////////////////////////////////////
45 // AppListMainView::IconLoader
46
47 class AppListMainView::IconLoader : public AppListItemObserver {
48 public:
IconLoader(AppListMainView * owner,AppListItem * item,float scale)49 IconLoader(AppListMainView* owner,
50 AppListItem* item,
51 float scale)
52 : owner_(owner),
53 item_(item) {
54 item_->AddObserver(this);
55
56 // Triggers icon loading for given |scale_factor|.
57 item_->icon().GetRepresentation(scale);
58 }
59
~IconLoader()60 virtual ~IconLoader() {
61 item_->RemoveObserver(this);
62 }
63
64 private:
65 // AppListItemObserver overrides:
ItemIconChanged()66 virtual void ItemIconChanged() OVERRIDE {
67 owner_->OnItemIconLoaded(this);
68 // Note that IconLoader is released here.
69 }
ItemNameChanged()70 virtual void ItemNameChanged() OVERRIDE {}
ItemHighlightedChanged()71 virtual void ItemHighlightedChanged() OVERRIDE {}
ItemIsInstallingChanged()72 virtual void ItemIsInstallingChanged() OVERRIDE {}
ItemPercentDownloadedChanged()73 virtual void ItemPercentDownloadedChanged() OVERRIDE {}
74
75 AppListMainView* owner_;
76 AppListItem* item_;
77
78 DISALLOW_COPY_AND_ASSIGN(IconLoader);
79 };
80
81 ////////////////////////////////////////////////////////////////////////////////
82 // AppListMainView:
83
AppListMainView(AppListViewDelegate * delegate,int initial_apps_page,gfx::NativeView parent)84 AppListMainView::AppListMainView(AppListViewDelegate* delegate,
85 int initial_apps_page,
86 gfx::NativeView parent)
87 : delegate_(delegate),
88 model_(delegate->GetModel()),
89 search_box_view_(NULL),
90 contents_view_(NULL),
91 contents_switcher_view_(NULL),
92 weak_ptr_factory_(this) {
93 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
94 kInnerPadding,
95 kInnerPadding,
96 kInnerPadding));
97
98 search_box_view_ = new SearchBoxView(this, delegate);
99 AddChildView(search_box_view_);
100 AddContentsViews();
101
102 // Switch the apps grid view to the specified page.
103 app_list::PaginationModel* pagination_model = GetAppsPaginationModel();
104 if (pagination_model->is_valid_page(initial_apps_page))
105 pagination_model->SelectPage(initial_apps_page, false);
106
107 // Starts icon loading early.
108 PreloadIcons(parent);
109 }
110
AddContentsViews()111 void AppListMainView::AddContentsViews() {
112 contents_view_ = new ContentsView(this);
113 if (app_list::switches::IsExperimentalAppListEnabled()) {
114 contents_switcher_view_ = new ContentsSwitcherView(contents_view_);
115 contents_view_->set_contents_switcher_view(contents_switcher_view_);
116 }
117 contents_view_->InitNamedPages(model_, delegate_);
118 AddChildView(contents_view_);
119 if (contents_switcher_view_)
120 AddChildView(contents_switcher_view_);
121
122 search_box_view_->set_contents_view(contents_view_);
123
124 contents_view_->SetPaintToLayer(true);
125 contents_view_->SetFillsBoundsOpaquely(false);
126 contents_view_->layer()->SetMasksToBounds(true);
127 }
128
~AppListMainView()129 AppListMainView::~AppListMainView() {
130 pending_icon_loaders_.clear();
131 }
132
ShowAppListWhenReady()133 void AppListMainView::ShowAppListWhenReady() {
134 if (pending_icon_loaders_.empty()) {
135 icon_loading_wait_timer_.Stop();
136 GetWidget()->Show();
137 return;
138 }
139
140 if (icon_loading_wait_timer_.IsRunning())
141 return;
142
143 icon_loading_wait_timer_.Start(
144 FROM_HERE,
145 base::TimeDelta::FromMilliseconds(kMaxIconLoadingWaitTimeInMs),
146 this, &AppListMainView::OnIconLoadingWaitTimer);
147 }
148
ResetForShow()149 void AppListMainView::ResetForShow() {
150 contents_view_->apps_container_view()->ResetForShowApps();
151 // We clear the search when hiding so when app list appears it is not showing
152 // search results.
153 search_box_view_->ClearSearch();
154 }
155
Close()156 void AppListMainView::Close() {
157 icon_loading_wait_timer_.Stop();
158 contents_view_->CancelDrag();
159 }
160
Prerender()161 void AppListMainView::Prerender() {
162 contents_view_->Prerender();
163 }
164
ModelChanged()165 void AppListMainView::ModelChanged() {
166 pending_icon_loaders_.clear();
167 model_ = delegate_->GetModel();
168 search_box_view_->ModelChanged();
169 delete contents_view_;
170 contents_view_ = NULL;
171 if (contents_switcher_view_) {
172 delete contents_switcher_view_;
173 contents_switcher_view_ = NULL;
174 }
175 AddContentsViews();
176 Layout();
177 }
178
UpdateSearchBoxVisibility()179 void AppListMainView::UpdateSearchBoxVisibility() {
180 bool visible =
181 !contents_view_->IsNamedPageActive(ContentsView::NAMED_PAGE_START) ||
182 contents_view_->IsShowingSearchResults();
183 search_box_view_->SetVisible(visible);
184 if (visible && GetWidget()->IsVisible())
185 search_box_view_->search_box()->RequestFocus();
186 }
187
OnStartPageSearchTextfieldChanged(const base::string16 & new_contents)188 void AppListMainView::OnStartPageSearchTextfieldChanged(
189 const base::string16& new_contents) {
190 search_box_view_->SetVisible(true);
191 search_box_view_->search_box()->SetText(new_contents);
192 search_box_view_->search_box()->RequestFocus();
193 }
194
SetDragAndDropHostOfCurrentAppList(ApplicationDragAndDropHost * drag_and_drop_host)195 void AppListMainView::SetDragAndDropHostOfCurrentAppList(
196 ApplicationDragAndDropHost* drag_and_drop_host) {
197 contents_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
198 }
199
ShouldCenterWindow() const200 bool AppListMainView::ShouldCenterWindow() const {
201 return delegate_->ShouldCenterWindow();
202 }
203
GetAppsPaginationModel()204 PaginationModel* AppListMainView::GetAppsPaginationModel() {
205 return contents_view_->apps_container_view()
206 ->apps_grid_view()
207 ->pagination_model();
208 }
209
PreloadIcons(gfx::NativeView parent)210 void AppListMainView::PreloadIcons(gfx::NativeView parent) {
211 float scale_factor = 1.0f;
212 if (parent)
213 scale_factor = ui::GetScaleFactorForNativeView(parent);
214
215 // The PaginationModel could have -1 as the initial selected page and
216 // assumes first page (i.e. index 0) will be used in this case.
217 const int selected_page =
218 std::max(0, GetAppsPaginationModel()->selected_page());
219
220 const AppsGridView* const apps_grid_view =
221 contents_view_->apps_container_view()->apps_grid_view();
222 const int tiles_per_page =
223 apps_grid_view->cols() * apps_grid_view->rows_per_page();
224
225 const int start_model_index = selected_page * tiles_per_page;
226 const int end_model_index =
227 std::min(static_cast<int>(model_->top_level_item_list()->item_count()),
228 start_model_index + tiles_per_page);
229
230 pending_icon_loaders_.clear();
231 for (int i = start_model_index; i < end_model_index; ++i) {
232 AppListItem* item = model_->top_level_item_list()->item_at(i);
233 if (item->icon().HasRepresentation(scale_factor))
234 continue;
235
236 pending_icon_loaders_.push_back(new IconLoader(this, item, scale_factor));
237 }
238 }
239
OnIconLoadingWaitTimer()240 void AppListMainView::OnIconLoadingWaitTimer() {
241 GetWidget()->Show();
242 }
243
OnItemIconLoaded(IconLoader * loader)244 void AppListMainView::OnItemIconLoaded(IconLoader* loader) {
245 ScopedVector<IconLoader>::iterator it = std::find(
246 pending_icon_loaders_.begin(), pending_icon_loaders_.end(), loader);
247 DCHECK(it != pending_icon_loaders_.end());
248 pending_icon_loaders_.erase(it);
249
250 if (pending_icon_loaders_.empty() && icon_loading_wait_timer_.IsRunning()) {
251 icon_loading_wait_timer_.Stop();
252 GetWidget()->Show();
253 }
254 }
255
ChildVisibilityChanged(views::View * child)256 void AppListMainView::ChildVisibilityChanged(views::View* child) {
257 // Repaint the AppListView's background which will repaint the background for
258 // the search box.
259 if (child == search_box_view_ && parent())
260 parent()->SchedulePaint();
261 }
262
ActivateApp(AppListItem * item,int event_flags)263 void AppListMainView::ActivateApp(AppListItem* item, int event_flags) {
264 // TODO(jennyz): Activate the folder via AppListModel notification.
265 if (item->GetItemType() == AppListFolderItem::kItemType)
266 contents_view_->ShowFolderContent(static_cast<AppListFolderItem*>(item));
267 else
268 item->Activate(event_flags);
269 }
270
GetShortcutPathForApp(const std::string & app_id,const base::Callback<void (const base::FilePath &)> & callback)271 void AppListMainView::GetShortcutPathForApp(
272 const std::string& app_id,
273 const base::Callback<void(const base::FilePath&)>& callback) {
274 delegate_->GetShortcutPathForApp(app_id, callback);
275 }
276
QueryChanged(SearchBoxView * sender)277 void AppListMainView::QueryChanged(SearchBoxView* sender) {
278 base::string16 query;
279 base::TrimWhitespace(model_->search_box()->text(), base::TRIM_ALL, &query);
280 bool should_show_search = !query.empty();
281 contents_view_->ShowSearchResults(should_show_search);
282 UpdateSearchBoxVisibility();
283
284 if (should_show_search)
285 delegate_->StartSearch();
286 else
287 delegate_->StopSearch();
288 }
289
OnResultInstalled(SearchResult * result)290 void AppListMainView::OnResultInstalled(SearchResult* result) {
291 // Clears the search to show the apps grid. The last installed app
292 // should be highlighted and made visible already.
293 search_box_view_->ClearSearch();
294 }
295
OnResultUninstalled(SearchResult * result)296 void AppListMainView::OnResultUninstalled(SearchResult* result) {
297 // Resubmit the query via a posted task so that all observers for the
298 // uninstall notification are notified.
299 base::MessageLoop::current()->PostTask(
300 FROM_HERE,
301 base::Bind(&AppListMainView::QueryChanged,
302 weak_ptr_factory_.GetWeakPtr(),
303 search_box_view_));
304 }
305
306 } // namespace app_list
307