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