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.
5 #include "ui/app_list/app_list_model.h"
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"
16 AppListModel::AppListModel()
17 : top_level_item_list_(new AppListItemList
),
18 search_box_(new SearchBoxModel
),
19 results_(new SearchResults
),
20 status_(STATUS_NORMAL
),
21 state_(INVALID_STATE
),
22 folders_enabled_(false),
23 custom_launcher_page_enabled_(true),
24 search_engine_is_google_(false) {
25 top_level_item_list_
->AddObserver(this);
28 AppListModel::~AppListModel() { top_level_item_list_
->RemoveObserver(this); }
30 void AppListModel::AddObserver(AppListModelObserver
* observer
) {
31 observers_
.AddObserver(observer
);
34 void AppListModel::RemoveObserver(AppListModelObserver
* observer
) {
35 observers_
.RemoveObserver(observer
);
38 void AppListModel::SetStatus(Status status
) {
39 if (status_
== status
)
43 FOR_EACH_OBSERVER(AppListModelObserver
,
45 OnAppListModelStatusChanged());
48 void AppListModel::SetState(State state
) {
52 State old_state
= state_
;
56 FOR_EACH_OBSERVER(AppListModelObserver
,
58 OnAppListModelStateChanged(old_state
, state_
));
61 AppListItem
* AppListModel::FindItem(const std::string
& id
) {
62 AppListItem
* item
= top_level_item_list_
->FindItem(id
);
65 for (size_t i
= 0; i
< top_level_item_list_
->item_count(); ++i
) {
66 AppListItem
* child_item
=
67 top_level_item_list_
->item_at(i
)->FindChildItem(id
);
74 AppListFolderItem
* AppListModel::FindFolderItem(const std::string
& id
) {
75 AppListItem
* item
= top_level_item_list_
->FindItem(id
);
76 if (item
&& item
->GetItemType() == AppListFolderItem::kItemType
)
77 return static_cast<AppListFolderItem
*>(item
);
82 AppListItem
* AppListModel::AddItem(scoped_ptr
<AppListItem
> item
) {
83 DCHECK(!item
->IsInFolder());
84 DCHECK(!top_level_item_list()->FindItem(item
->id()));
85 return AddItemToItemListAndNotify(item
.Pass());
88 AppListItem
* AppListModel::AddItemToFolder(scoped_ptr
<AppListItem
> item
,
89 const std::string
& folder_id
) {
90 if (folder_id
.empty())
91 return AddItem(item
.Pass());
92 DVLOG(2) << "AddItemToFolder: " << item
->id() << ": " << folder_id
;
93 CHECK_NE(folder_id
, item
->folder_id());
94 DCHECK_NE(AppListFolderItem::kItemType
, item
->GetItemType());
95 AppListFolderItem
* dest_folder
= FindOrCreateFolderItem(folder_id
);
98 DCHECK(!dest_folder
->item_list()->FindItem(item
->id()))
99 << "Already in folder: " << dest_folder
->id();
100 return AddItemToFolderItemAndNotify(dest_folder
, item
.Pass());
103 const std::string
AppListModel::MergeItems(const std::string
& target_item_id
,
104 const std::string
& source_item_id
) {
105 if (!folders_enabled()) {
106 LOG(ERROR
) << "MergeItems called with folders disabled.";
109 DVLOG(2) << "MergeItems: " << source_item_id
<< " -> " << target_item_id
;
111 if (target_item_id
== source_item_id
) {
112 LOG(WARNING
) << "MergeItems tried to drop item onto itself ("
113 << source_item_id
<< " -> " << target_item_id
<< ").";
117 // Find the target item.
118 AppListItem
* target_item
= top_level_item_list_
->FindItem(target_item_id
);
120 LOG(ERROR
) << "MergeItems: Target no longer exists.";
124 AppListItem
* source_item
= FindItem(source_item_id
);
126 LOG(ERROR
) << "MergeItems: Source no longer exists.";
130 // If the target item is a folder, just add the source item to it.
131 if (target_item
->GetItemType() == AppListFolderItem::kItemType
) {
132 AppListFolderItem
* target_folder
=
133 static_cast<AppListFolderItem
*>(target_item
);
134 if (target_folder
->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM
) {
135 LOG(WARNING
) << "MergeItems called with OEM folder as target";
138 scoped_ptr
<AppListItem
> source_item_ptr
= RemoveItem(source_item
);
139 source_item_ptr
->set_position(
140 target_folder
->item_list()->CreatePositionBefore(
141 syncer::StringOrdinal()));
142 AddItemToFolderItemAndNotify(target_folder
, source_item_ptr
.Pass());
143 return target_folder
->id();
146 // Otherwise remove the source item and target item from their current
147 // location, they will become owned by the new folder.
148 scoped_ptr
<AppListItem
> source_item_ptr
= RemoveItem(source_item
);
149 CHECK(source_item_ptr
);
150 // Note: This would fail if |target_item_id == source_item_id|, except we
151 // checked that they are distinct at the top of this method.
152 scoped_ptr
<AppListItem
> target_item_ptr
=
153 top_level_item_list_
->RemoveItem(target_item_id
);
154 CHECK(target_item_ptr
);
156 // Create a new folder in the same location as the target item.
157 std::string new_folder_id
= AppListFolderItem::GenerateId();
158 DVLOG(2) << "Creating folder for merge: " << new_folder_id
;
159 scoped_ptr
<AppListItem
> new_folder_ptr(new AppListFolderItem(
160 new_folder_id
, AppListFolderItem::FOLDER_TYPE_NORMAL
));
161 new_folder_ptr
->set_position(target_item_ptr
->position());
162 AppListFolderItem
* new_folder
= static_cast<AppListFolderItem
*>(
163 AddItemToItemListAndNotify(new_folder_ptr
.Pass()));
165 // Add the items to the new folder.
166 target_item_ptr
->set_position(
167 new_folder
->item_list()->CreatePositionBefore(
168 syncer::StringOrdinal()));
169 AddItemToFolderItemAndNotify(new_folder
, target_item_ptr
.Pass());
170 source_item_ptr
->set_position(
171 new_folder
->item_list()->CreatePositionBefore(
172 syncer::StringOrdinal()));
173 AddItemToFolderItemAndNotify(new_folder
, source_item_ptr
.Pass());
175 return new_folder
->id();
178 void AppListModel::MoveItemToFolder(AppListItem
* item
,
179 const std::string
& folder_id
) {
180 DVLOG(2) << "MoveItemToFolder: " << folder_id
181 << " <- " << item
->ToDebugString();
182 if (item
->folder_id() == folder_id
)
184 AppListFolderItem
* dest_folder
= FindOrCreateFolderItem(folder_id
);
185 scoped_ptr
<AppListItem
> item_ptr
= RemoveItem(item
);
187 CHECK(!item
->IsInFolder());
188 AddItemToFolderItemAndNotify(dest_folder
, item_ptr
.Pass());
190 AddItemToItemListAndNotifyUpdate(item_ptr
.Pass());
194 bool AppListModel::MoveItemToFolderAt(AppListItem
* item
,
195 const std::string
& folder_id
,
196 syncer::StringOrdinal position
) {
197 DVLOG(2) << "MoveItemToFolderAt: " << folder_id
198 << "[" << position
.ToDebugString() << "]"
199 << " <- " << item
->ToDebugString();
200 if (item
->folder_id() == folder_id
)
202 AppListFolderItem
* src_folder
= FindOrCreateFolderItem(item
->folder_id());
204 src_folder
->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM
) {
205 LOG(WARNING
) << "MoveItemToFolderAt called with OEM folder as source";
208 AppListFolderItem
* dest_folder
= FindOrCreateFolderItem(folder_id
);
209 scoped_ptr
<AppListItem
> item_ptr
= RemoveItem(item
);
211 item_ptr
->set_position(
212 dest_folder
->item_list()->CreatePositionBefore(position
));
213 AddItemToFolderItemAndNotify(dest_folder
, item_ptr
.Pass());
215 item_ptr
->set_position(
216 top_level_item_list_
->CreatePositionBefore(position
));
217 AddItemToItemListAndNotifyUpdate(item_ptr
.Pass());
222 void AppListModel::SetItemPosition(AppListItem
* item
,
223 const syncer::StringOrdinal
& new_position
) {
224 if (!item
->IsInFolder()) {
225 top_level_item_list_
->SetItemPosition(item
, new_position
);
226 // Note: this will trigger OnListItemMoved which will signal observers.
227 // (This is done this way because some View code still moves items within
228 // the item list directly).
231 AppListFolderItem
* folder
= FindFolderItem(item
->folder_id());
233 folder
->item_list()->SetItemPosition(item
, new_position
);
234 FOR_EACH_OBSERVER(AppListModelObserver
,
236 OnAppListItemUpdated(item
));
239 void AppListModel::SetItemName(AppListItem
* item
, const std::string
& name
) {
241 DVLOG(2) << "AppListModel::SetItemName: " << item
->ToDebugString();
242 FOR_EACH_OBSERVER(AppListModelObserver
,
244 OnAppListItemUpdated(item
));
247 void AppListModel::SetItemNameAndShortName(AppListItem
* item
,
248 const std::string
& name
,
249 const std::string
& short_name
) {
250 item
->SetNameAndShortName(name
, short_name
);
251 DVLOG(2) << "AppListModel::SetItemNameAndShortName: "
252 << item
->ToDebugString();
253 FOR_EACH_OBSERVER(AppListModelObserver
,
255 OnAppListItemUpdated(item
));
258 void AppListModel::DeleteItem(const std::string
& id
) {
259 AppListItem
* item
= FindItem(id
);
262 if (!item
->IsInFolder()) {
263 DCHECK_EQ(0u, item
->ChildItemCount())
264 << "Invalid call to DeleteItem for item with children: " << id
;
265 FOR_EACH_OBSERVER(AppListModelObserver
,
267 OnAppListItemWillBeDeleted(item
));
268 top_level_item_list_
->DeleteItem(id
);
269 FOR_EACH_OBSERVER(AppListModelObserver
, observers_
, OnAppListItemDeleted());
272 AppListFolderItem
* folder
= FindFolderItem(item
->folder_id());
273 DCHECK(folder
) << "Folder not found for item: " << item
->ToDebugString();
274 scoped_ptr
<AppListItem
> child_item
= RemoveItemFromFolder(folder
, item
);
275 DCHECK_EQ(item
, child_item
.get());
276 FOR_EACH_OBSERVER(AppListModelObserver
,
278 OnAppListItemWillBeDeleted(item
));
279 child_item
.reset(); // Deletes item.
280 FOR_EACH_OBSERVER(AppListModelObserver
, observers_
, OnAppListItemDeleted());
283 void AppListModel::DeleteUninstalledItem(const std::string
& id
) {
284 AppListItem
* item
= FindItem(id
);
287 const std::string folder_id
= item
->folder_id();
290 // crbug.com/368111: Upon uninstall of 2nd-to-last folder item, reparent last
291 // item to top; this will remove the folder.
292 AppListFolderItem
* folder
= FindFolderItem(folder_id
);
293 if (folder
&& folder
->ChildItemCount() == 1u) {
294 AppListItem
* last_item
= folder
->item_list()->item_at(0);
295 MoveItemToFolderAt(last_item
, "", folder
->position());
299 void AppListModel::NotifyExtensionPreferenceChanged() {
300 for (size_t i
= 0; i
< top_level_item_list_
->item_count(); ++i
)
301 top_level_item_list_
->item_at(i
)->OnExtensionPreferenceChanged();
304 void AppListModel::SetFoldersEnabled(bool folders_enabled
) {
305 folders_enabled_
= folders_enabled
;
308 // Remove child items from folders.
309 std::vector
<std::string
> folder_ids
;
310 for (size_t i
= 0; i
< top_level_item_list_
->item_count(); ++i
) {
311 AppListItem
* item
= top_level_item_list_
->item_at(i
);
312 if (item
->GetItemType() != AppListFolderItem::kItemType
)
314 AppListFolderItem
* folder
= static_cast<AppListFolderItem
*>(item
);
315 if (folder
->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM
)
316 continue; // Do not remove OEM folders.
317 while (folder
->item_list()->item_count()) {
318 scoped_ptr
<AppListItem
> child
= folder
->item_list()->RemoveItemAt(0);
319 child
->set_folder_id("");
320 AddItemToItemListAndNotifyUpdate(child
.Pass());
322 folder_ids
.push_back(folder
->id());
325 for (size_t i
= 0; i
< folder_ids
.size(); ++i
)
326 DeleteItem(folder_ids
[i
]);
329 void AppListModel::SetCustomLauncherPageEnabled(bool enabled
) {
330 custom_launcher_page_enabled_
= enabled
;
331 FOR_EACH_OBSERVER(AppListModelObserver
, observers_
,
332 OnCustomLauncherPageEnabledStateChanged(enabled
));
335 std::vector
<SearchResult
*> AppListModel::FilterSearchResultsByDisplayType(
336 SearchResults
* results
,
337 SearchResult::DisplayType display_type
,
338 size_t max_results
) {
339 std::vector
<SearchResult
*> matches
;
340 for (size_t i
= 0; i
< results
->item_count(); ++i
) {
341 SearchResult
* item
= results
->GetItemAt(i
);
342 if (item
->display_type() == display_type
) {
343 matches
.push_back(item
);
344 if (matches
.size() == max_results
)
351 void AppListModel::PushCustomLauncherPageSubpage() {
352 custom_launcher_page_subpage_depth_
++;
355 bool AppListModel::PopCustomLauncherPageSubpage() {
356 if (custom_launcher_page_subpage_depth_
== 0)
359 --custom_launcher_page_subpage_depth_
;
363 void AppListModel::ClearCustomLauncherPageSubpages() {
364 custom_launcher_page_subpage_depth_
= 0;
367 void AppListModel::SetSearchEngineIsGoogle(bool is_google
) {
368 search_engine_is_google_
= is_google
;
369 FOR_EACH_OBSERVER(AppListModelObserver
, observers_
,
370 OnSearchEngineIsGoogleChanged(is_google
));
375 void AppListModel::OnListItemMoved(size_t from_index
,
378 FOR_EACH_OBSERVER(AppListModelObserver
,
380 OnAppListItemUpdated(item
));
383 AppListFolderItem
* AppListModel::FindOrCreateFolderItem(
384 const std::string
& folder_id
) {
385 if (folder_id
.empty())
388 AppListFolderItem
* dest_folder
= FindFolderItem(folder_id
);
392 if (!folders_enabled()) {
393 LOG(ERROR
) << "Attempt to create folder item when disabled: " << folder_id
;
397 DVLOG(2) << "Creating new folder: " << folder_id
;
398 scoped_ptr
<AppListFolderItem
> new_folder(
399 new AppListFolderItem(folder_id
, AppListFolderItem::FOLDER_TYPE_NORMAL
));
400 new_folder
->set_position(
401 top_level_item_list_
->CreatePositionBefore(syncer::StringOrdinal()));
402 AppListItem
* new_folder_item
= AddItemToItemListAndNotify(new_folder
.Pass());
403 return static_cast<AppListFolderItem
*>(new_folder_item
);
406 AppListItem
* AppListModel::AddItemToItemListAndNotify(
407 scoped_ptr
<AppListItem
> item_ptr
) {
408 DCHECK(!item_ptr
->IsInFolder());
409 AppListItem
* item
= top_level_item_list_
->AddItem(item_ptr
.Pass());
410 FOR_EACH_OBSERVER(AppListModelObserver
,
412 OnAppListItemAdded(item
));
416 AppListItem
* AppListModel::AddItemToItemListAndNotifyUpdate(
417 scoped_ptr
<AppListItem
> item_ptr
) {
418 DCHECK(!item_ptr
->IsInFolder());
419 AppListItem
* item
= top_level_item_list_
->AddItem(item_ptr
.Pass());
420 FOR_EACH_OBSERVER(AppListModelObserver
,
422 OnAppListItemUpdated(item
));
426 AppListItem
* AppListModel::AddItemToFolderItemAndNotify(
427 AppListFolderItem
* folder
,
428 scoped_ptr
<AppListItem
> item_ptr
) {
429 CHECK_NE(folder
->id(), item_ptr
->folder_id());
430 AppListItem
* item
= folder
->item_list()->AddItem(item_ptr
.Pass());
431 item
->set_folder_id(folder
->id());
432 FOR_EACH_OBSERVER(AppListModelObserver
,
434 OnAppListItemUpdated(item
));
438 scoped_ptr
<AppListItem
> AppListModel::RemoveItem(AppListItem
* item
) {
439 if (!item
->IsInFolder())
440 return top_level_item_list_
->RemoveItem(item
->id());
442 AppListFolderItem
* folder
= FindFolderItem(item
->folder_id());
443 return RemoveItemFromFolder(folder
, item
);
446 scoped_ptr
<AppListItem
> AppListModel::RemoveItemFromFolder(
447 AppListFolderItem
* folder
,
449 std::string folder_id
= folder
->id();
450 CHECK_EQ(item
->folder_id(), folder_id
);
451 scoped_ptr
<AppListItem
> result
= folder
->item_list()->RemoveItem(item
->id());
452 result
->set_folder_id("");
453 if (folder
->item_list()->item_count() == 0) {
454 DVLOG(2) << "Deleting empty folder: " << folder
->ToDebugString();
455 DeleteItem(folder_id
);
457 return result
.Pass();
460 } // namespace app_list