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: 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 60 virtual ~IconLoader() { 61 item_->RemoveObserver(this); 62 } 63 64 private: 65 // AppListItemObserver overrides: 66 virtual void ItemIconChanged() OVERRIDE { 67 owner_->OnItemIconLoaded(this); 68 // Note that IconLoader is released here. 69 } 70 virtual void ItemNameChanged() OVERRIDE {} 71 virtual void ItemHighlightedChanged() OVERRIDE {} 72 virtual void ItemIsInstallingChanged() OVERRIDE {} 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 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 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 129 AppListMainView::~AppListMainView() { 130 pending_icon_loaders_.clear(); 131 } 132 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 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 156 void AppListMainView::Close() { 157 icon_loading_wait_timer_.Stop(); 158 contents_view_->CancelDrag(); 159 } 160 161 void AppListMainView::Prerender() { 162 contents_view_->Prerender(); 163 } 164 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 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 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 195 void AppListMainView::SetDragAndDropHostOfCurrentAppList( 196 ApplicationDragAndDropHost* drag_and_drop_host) { 197 contents_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 198 } 199 200 bool AppListMainView::ShouldCenterWindow() const { 201 return delegate_->ShouldCenterWindow(); 202 } 203 204 PaginationModel* AppListMainView::GetAppsPaginationModel() { 205 return contents_view_->apps_container_view() 206 ->apps_grid_view() 207 ->pagination_model(); 208 } 209 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 240 void AppListMainView::OnIconLoadingWaitTimer() { 241 GetWidget()->Show(); 242 } 243 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 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 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 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 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 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 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