1 // Copyright (c) 2012 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/app_list_model.h" 6 7 #include <string> 8 9 #include "ui/app_list/app_list_folder_item.h" 10 #include "ui/app_list/app_list_item.h" 11 #include "ui/app_list/app_list_model_observer.h" 12 #include "ui/app_list/search_box_model.h" 13 14 namespace app_list { 15 16 AppListModel::AppListModel() 17 : top_level_item_list_(new AppListItemList), 18 search_box_(new SearchBoxModel), 19 results_(new SearchResults), 20 status_(STATUS_NORMAL), 21 folders_enabled_(false) { 22 top_level_item_list_->AddObserver(this); 23 } 24 25 AppListModel::~AppListModel() { top_level_item_list_->RemoveObserver(this); } 26 27 void AppListModel::AddObserver(AppListModelObserver* observer) { 28 observers_.AddObserver(observer); 29 } 30 31 void AppListModel::RemoveObserver(AppListModelObserver* observer) { 32 observers_.RemoveObserver(observer); 33 } 34 35 void AppListModel::SetStatus(Status status) { 36 if (status_ == status) 37 return; 38 39 status_ = status; 40 FOR_EACH_OBSERVER(AppListModelObserver, 41 observers_, 42 OnAppListModelStatusChanged()); 43 } 44 45 AppListItem* AppListModel::FindItem(const std::string& id) { 46 AppListItem* item = top_level_item_list_->FindItem(id); 47 if (item) 48 return item; 49 for (size_t i = 0; i < top_level_item_list_->item_count(); ++i) { 50 AppListItem* child_item = 51 top_level_item_list_->item_at(i)->FindChildItem(id); 52 if (child_item) 53 return child_item; 54 } 55 return NULL; 56 } 57 58 AppListFolderItem* AppListModel::FindFolderItem(const std::string& id) { 59 AppListItem* item = top_level_item_list_->FindItem(id); 60 if (item && item->GetItemType() == AppListFolderItem::kItemType) 61 return static_cast<AppListFolderItem*>(item); 62 DCHECK(!item); 63 return NULL; 64 } 65 66 AppListItem* AppListModel::AddItem(scoped_ptr<AppListItem> item) { 67 DCHECK(!item->IsInFolder()); 68 DCHECK(!top_level_item_list()->FindItem(item->id())); 69 return AddItemToItemListAndNotify(item.Pass()); 70 } 71 72 AppListItem* AppListModel::AddItemToFolder(scoped_ptr<AppListItem> item, 73 const std::string& folder_id) { 74 if (folder_id.empty()) 75 return AddItem(item.Pass()); 76 DVLOG(2) << "AddItemToFolder: " << item->id() << ": " << folder_id; 77 CHECK_NE(folder_id, item->folder_id()); 78 DCHECK_NE(AppListFolderItem::kItemType, item->GetItemType()); 79 AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id); 80 if (!dest_folder) 81 return NULL; 82 DCHECK(!dest_folder->item_list()->FindItem(item->id())) 83 << "Already in folder: " << dest_folder->id(); 84 return AddItemToFolderItemAndNotify(dest_folder, item.Pass()); 85 } 86 87 const std::string AppListModel::MergeItems(const std::string& target_item_id, 88 const std::string& source_item_id) { 89 if (!folders_enabled()) { 90 LOG(ERROR) << "MergeItems called with folders disabled."; 91 return ""; 92 } 93 DVLOG(2) << "MergeItems: " << source_item_id << " -> " << target_item_id; 94 // Find the target item. 95 AppListItem* target_item = FindItem(target_item_id); 96 if (!target_item) { 97 LOG(ERROR) << "MergeItems: Target no longer exists."; 98 return ""; 99 } 100 CHECK(target_item->folder_id().empty()); 101 102 AppListItem* source_item = FindItem(source_item_id); 103 if (!source_item) { 104 LOG(ERROR) << "MergeItems: Source no longer exists."; 105 return ""; 106 } 107 108 // If the target item is a folder, just add the source item to it. 109 if (target_item->GetItemType() == AppListFolderItem::kItemType) { 110 AppListFolderItem* target_folder = 111 static_cast<AppListFolderItem*>(target_item); 112 if (target_folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) { 113 LOG(WARNING) << "MergeItems called with OEM folder as target"; 114 return ""; 115 } 116 scoped_ptr<AppListItem> source_item_ptr = RemoveItem(source_item); 117 source_item_ptr->set_position( 118 target_folder->item_list()->CreatePositionBefore( 119 syncer::StringOrdinal())); 120 AddItemToFolderItemAndNotify(target_folder, source_item_ptr.Pass()); 121 return target_folder->id(); 122 } 123 124 // Otherwise remove the source item and target item from their current 125 // location, they will become owned by the new folder. 126 scoped_ptr<AppListItem> source_item_ptr = RemoveItem(source_item); 127 CHECK(source_item_ptr); 128 scoped_ptr<AppListItem> target_item_ptr = 129 top_level_item_list_->RemoveItem(target_item_id); 130 CHECK(target_item_ptr); 131 132 // Create a new folder in the same location as the target item. 133 std::string new_folder_id = AppListFolderItem::GenerateId(); 134 DVLOG(2) << "Creating folder for merge: " << new_folder_id; 135 scoped_ptr<AppListItem> new_folder_ptr(new AppListFolderItem( 136 new_folder_id, AppListFolderItem::FOLDER_TYPE_NORMAL)); 137 new_folder_ptr->set_position(target_item_ptr->position()); 138 AppListFolderItem* new_folder = static_cast<AppListFolderItem*>( 139 AddItemToItemListAndNotify(new_folder_ptr.Pass())); 140 141 // Add the items to the new folder. 142 target_item_ptr->set_position( 143 new_folder->item_list()->CreatePositionBefore( 144 syncer::StringOrdinal())); 145 AddItemToFolderItemAndNotify(new_folder, target_item_ptr.Pass()); 146 source_item_ptr->set_position( 147 new_folder->item_list()->CreatePositionBefore( 148 syncer::StringOrdinal())); 149 AddItemToFolderItemAndNotify(new_folder, source_item_ptr.Pass()); 150 151 return new_folder->id(); 152 } 153 154 void AppListModel::MoveItemToFolder(AppListItem* item, 155 const std::string& folder_id) { 156 DVLOG(2) << "MoveItemToFolder: " << folder_id 157 << " <- " << item->ToDebugString(); 158 if (item->folder_id() == folder_id) 159 return; 160 AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id); 161 scoped_ptr<AppListItem> item_ptr = RemoveItem(item); 162 if (dest_folder) { 163 CHECK(!item->IsInFolder()); 164 AddItemToFolderItemAndNotify(dest_folder, item_ptr.Pass()); 165 } else { 166 AddItemToItemListAndNotifyUpdate(item_ptr.Pass()); 167 } 168 } 169 170 bool AppListModel::MoveItemToFolderAt(AppListItem* item, 171 const std::string& folder_id, 172 syncer::StringOrdinal position) { 173 DVLOG(2) << "MoveItemToFolderAt: " << folder_id 174 << "[" << position.ToDebugString() << "]" 175 << " <- " << item->ToDebugString(); 176 if (item->folder_id() == folder_id) 177 return false; 178 AppListFolderItem* src_folder = FindOrCreateFolderItem(item->folder_id()); 179 if (src_folder && 180 src_folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) { 181 LOG(WARNING) << "MoveItemToFolderAt called with OEM folder as source"; 182 return false; 183 } 184 AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id); 185 scoped_ptr<AppListItem> item_ptr = RemoveItem(item); 186 if (dest_folder) { 187 item_ptr->set_position( 188 dest_folder->item_list()->CreatePositionBefore(position)); 189 AddItemToFolderItemAndNotify(dest_folder, item_ptr.Pass()); 190 } else { 191 item_ptr->set_position( 192 top_level_item_list_->CreatePositionBefore(position)); 193 AddItemToItemListAndNotifyUpdate(item_ptr.Pass()); 194 } 195 return true; 196 } 197 198 void AppListModel::SetItemPosition(AppListItem* item, 199 const syncer::StringOrdinal& new_position) { 200 if (!item->IsInFolder()) { 201 top_level_item_list_->SetItemPosition(item, new_position); 202 // Note: this will trigger OnListItemMoved which will signal observers. 203 // (This is done this way because some View code still moves items within 204 // the item list directly). 205 return; 206 } 207 AppListFolderItem* folder = FindFolderItem(item->folder_id()); 208 DCHECK(folder); 209 folder->item_list()->SetItemPosition(item, new_position); 210 FOR_EACH_OBSERVER(AppListModelObserver, 211 observers_, 212 OnAppListItemUpdated(item)); 213 } 214 215 void AppListModel::SetItemName(AppListItem* item, const std::string& name) { 216 item->SetName(name); 217 DVLOG(2) << "AppListModel::SetItemName: " << item->ToDebugString(); 218 FOR_EACH_OBSERVER(AppListModelObserver, 219 observers_, 220 OnAppListItemUpdated(item)); 221 } 222 223 void AppListModel::SetItemNameAndShortName(AppListItem* item, 224 const std::string& name, 225 const std::string& short_name) { 226 item->SetNameAndShortName(name, short_name); 227 DVLOG(2) << "AppListModel::SetItemNameAndShortName: " 228 << item->ToDebugString(); 229 FOR_EACH_OBSERVER(AppListModelObserver, 230 observers_, 231 OnAppListItemUpdated(item)); 232 } 233 234 void AppListModel::DeleteItem(const std::string& id) { 235 AppListItem* item = FindItem(id); 236 if (!item) 237 return; 238 if (!item->IsInFolder()) { 239 DCHECK_EQ(0u, item->ChildItemCount()) 240 << "Invalid call to DeleteItem for item with children: " << id; 241 FOR_EACH_OBSERVER(AppListModelObserver, 242 observers_, 243 OnAppListItemWillBeDeleted(item)); 244 top_level_item_list_->DeleteItem(id); 245 FOR_EACH_OBSERVER(AppListModelObserver, observers_, OnAppListItemDeleted()); 246 return; 247 } 248 AppListFolderItem* folder = FindFolderItem(item->folder_id()); 249 DCHECK(folder) << "Folder not found for item: " << item->ToDebugString(); 250 scoped_ptr<AppListItem> child_item = RemoveItemFromFolder(folder, item); 251 DCHECK_EQ(item, child_item.get()); 252 FOR_EACH_OBSERVER(AppListModelObserver, 253 observers_, 254 OnAppListItemWillBeDeleted(item)); 255 child_item.reset(); // Deletes item. 256 FOR_EACH_OBSERVER(AppListModelObserver, observers_, OnAppListItemDeleted()); 257 } 258 259 void AppListModel::NotifyExtensionPreferenceChanged() { 260 for (size_t i = 0; i < top_level_item_list_->item_count(); ++i) 261 top_level_item_list_->item_at(i)->OnExtensionPreferenceChanged(); 262 } 263 264 void AppListModel::SetFoldersEnabled(bool folders_enabled) { 265 folders_enabled_ = folders_enabled; 266 if (folders_enabled) 267 return; 268 // Remove child items from folders. 269 std::vector<std::string> folder_ids; 270 for (size_t i = 0; i < top_level_item_list_->item_count(); ++i) { 271 AppListItem* item = top_level_item_list_->item_at(i); 272 if (item->GetItemType() != AppListFolderItem::kItemType) 273 continue; 274 AppListFolderItem* folder = static_cast<AppListFolderItem*>(item); 275 if (folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) 276 continue; // Do not remove OEM folders. 277 while (folder->item_list()->item_count()) { 278 scoped_ptr<AppListItem> child = folder->item_list()->RemoveItemAt(0); 279 child->set_folder_id(""); 280 AddItemToItemListAndNotifyUpdate(child.Pass()); 281 } 282 folder_ids.push_back(folder->id()); 283 } 284 // Delete folders. 285 for (size_t i = 0; i < folder_ids.size(); ++i) 286 DeleteItem(folder_ids[i]); 287 } 288 289 std::vector<SearchResult*> AppListModel::FilterSearchResultsByDisplayType( 290 SearchResults* results, 291 SearchResult::DisplayType display_type, 292 size_t max_results) { 293 std::vector<SearchResult*> matches; 294 for (size_t i = 0; i < results->item_count(); ++i) { 295 SearchResult* item = results->GetItemAt(i); 296 if (item->display_type() == display_type) { 297 matches.push_back(item); 298 if (matches.size() == max_results) 299 break; 300 } 301 } 302 return matches; 303 } 304 305 // Private methods 306 307 void AppListModel::OnListItemMoved(size_t from_index, 308 size_t to_index, 309 AppListItem* item) { 310 FOR_EACH_OBSERVER(AppListModelObserver, 311 observers_, 312 OnAppListItemUpdated(item)); 313 } 314 315 AppListFolderItem* AppListModel::FindOrCreateFolderItem( 316 const std::string& folder_id) { 317 if (folder_id.empty()) 318 return NULL; 319 320 AppListFolderItem* dest_folder = FindFolderItem(folder_id); 321 if (dest_folder) 322 return dest_folder; 323 324 if (!folders_enabled()) { 325 LOG(ERROR) << "Attempt to create folder item when disabled: " << folder_id; 326 return NULL; 327 } 328 329 DVLOG(2) << "Creating new folder: " << folder_id; 330 scoped_ptr<AppListFolderItem> new_folder( 331 new AppListFolderItem(folder_id, AppListFolderItem::FOLDER_TYPE_NORMAL)); 332 new_folder->set_position( 333 top_level_item_list_->CreatePositionBefore(syncer::StringOrdinal())); 334 AppListItem* new_folder_item = 335 AddItemToItemListAndNotify(new_folder.PassAs<AppListItem>()); 336 return static_cast<AppListFolderItem*>(new_folder_item); 337 } 338 339 AppListItem* AppListModel::AddItemToItemListAndNotify( 340 scoped_ptr<AppListItem> item_ptr) { 341 DCHECK(!item_ptr->IsInFolder()); 342 AppListItem* item = top_level_item_list_->AddItem(item_ptr.Pass()); 343 FOR_EACH_OBSERVER(AppListModelObserver, 344 observers_, 345 OnAppListItemAdded(item)); 346 return item; 347 } 348 349 AppListItem* AppListModel::AddItemToItemListAndNotifyUpdate( 350 scoped_ptr<AppListItem> item_ptr) { 351 DCHECK(!item_ptr->IsInFolder()); 352 AppListItem* item = top_level_item_list_->AddItem(item_ptr.Pass()); 353 FOR_EACH_OBSERVER(AppListModelObserver, 354 observers_, 355 OnAppListItemUpdated(item)); 356 return item; 357 } 358 359 AppListItem* AppListModel::AddItemToFolderItemAndNotify( 360 AppListFolderItem* folder, 361 scoped_ptr<AppListItem> item_ptr) { 362 CHECK_NE(folder->id(), item_ptr->folder_id()); 363 AppListItem* item = folder->item_list()->AddItem(item_ptr.Pass()); 364 item->set_folder_id(folder->id()); 365 FOR_EACH_OBSERVER(AppListModelObserver, 366 observers_, 367 OnAppListItemUpdated(item)); 368 return item; 369 } 370 371 scoped_ptr<AppListItem> AppListModel::RemoveItem(AppListItem* item) { 372 if (!item->IsInFolder()) 373 return top_level_item_list_->RemoveItem(item->id()); 374 375 AppListFolderItem* folder = FindFolderItem(item->folder_id()); 376 return RemoveItemFromFolder(folder, item); 377 } 378 379 scoped_ptr<AppListItem> AppListModel::RemoveItemFromFolder( 380 AppListFolderItem* folder, 381 AppListItem* item) { 382 std::string folder_id = folder->id(); 383 CHECK_EQ(item->folder_id(), folder_id); 384 scoped_ptr<AppListItem> result = folder->item_list()->RemoveItem(item->id()); 385 result->set_folder_id(""); 386 if (folder->item_list()->item_count() == 0) { 387 DVLOG(2) << "Deleting empty folder: " << folder->ToDebugString(); 388 DeleteItem(folder_id); 389 } 390 return result.Pass(); 391 } 392 393 } // namespace app_list 394