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
14namespace app_list {
15
16AppListModel::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
25AppListModel::~AppListModel() { top_level_item_list_->RemoveObserver(this); }
26
27void AppListModel::AddObserver(AppListModelObserver* observer) {
28  observers_.AddObserver(observer);
29}
30
31void AppListModel::RemoveObserver(AppListModelObserver* observer) {
32  observers_.RemoveObserver(observer);
33}
34
35void 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
45AppListItem* 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
58AppListFolderItem* 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
66AppListItem* 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
72AppListItem* 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
87const 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
154void 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
170bool 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
198void 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
215void 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
223void 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
234void 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
259void 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
264void 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
289std::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
307void 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
315AppListFolderItem* 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
339AppListItem* 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
349AppListItem* 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
359AppListItem* 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
371scoped_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
379scoped_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