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