app_list_model.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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
15namespace app_list {
16
17AppListModel::AppListModel()
18    : top_level_item_list_(new AppListItemList),
19      search_box_(new SearchBoxModel),
20      results_(new SearchResults),
21      status_(STATUS_NORMAL) {
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  DCHECK(!item->IsInFolder() || item->folder_id() == folder_id);
78  DCHECK(item->GetItemType() != AppListFolderItem::kItemType);
79  AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id);
80  DCHECK(!dest_folder->item_list()->FindItem(item->id()))
81      << "Already in folder: " << dest_folder->id();
82  return AddItemToFolderItemAndNotify(dest_folder, item.Pass());
83}
84
85const std::string AppListModel::MergeItems(const std::string& target_item_id,
86                                           const std::string& source_item_id) {
87  DVLOG(2) << "MergeItems: " << source_item_id << " -> " << target_item_id;
88  // Find the target item.
89  AppListItem* target_item = FindItem(target_item_id);
90  if (!target_item) {
91    LOG(ERROR) << "MergeItems: Target no longer exists.";
92    return "";
93  }
94  CHECK(target_item->folder_id().empty());
95
96  AppListItem* source_item = FindItem(source_item_id);
97  if (!source_item) {
98    LOG(ERROR) << "MergeItems: Source no longer exists.";
99    return "";
100  }
101
102  // If the target item is a folder, just add the source item to it.
103  if (target_item->GetItemType() == AppListFolderItem::kItemType) {
104    AppListFolderItem* target_folder =
105        static_cast<AppListFolderItem*>(target_item);
106    if (target_folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) {
107      LOG(WARNING) << "MergeItems called with OEM folder as target";
108      return "";
109    }
110    scoped_ptr<AppListItem> source_item_ptr = RemoveItem(source_item);
111    source_item_ptr->set_position(
112        target_folder->item_list()->CreatePositionBefore(
113            syncer::StringOrdinal()));
114    AddItemToFolderItemAndNotify(target_folder, source_item_ptr.Pass());
115    return target_folder->id();
116  }
117
118  // Otherwise remove the source item and target item from their current
119  // location, they will become owned by the new folder.
120  scoped_ptr<AppListItem> source_item_ptr = RemoveItem(source_item);
121  CHECK(source_item_ptr);
122  scoped_ptr<AppListItem> target_item_ptr =
123      top_level_item_list_->RemoveItem(target_item_id);
124  CHECK(target_item_ptr);
125
126  // Create a new folder in the same location as the target item.
127  std::string new_folder_id = AppListFolderItem::GenerateId();
128  DVLOG(2) << "Creating folder for merge: " << new_folder_id;
129  scoped_ptr<AppListItem> new_folder_ptr(new AppListFolderItem(
130      new_folder_id, AppListFolderItem::FOLDER_TYPE_NORMAL));
131  new_folder_ptr->set_position(target_item_ptr->position());
132  AppListFolderItem* new_folder = static_cast<AppListFolderItem*>(
133      AddItemToItemListAndNotify(new_folder_ptr.Pass()));
134
135  // Add the items to the new folder.
136  target_item_ptr->set_position(
137      new_folder->item_list()->CreatePositionBefore(
138          syncer::StringOrdinal()));
139  AddItemToFolderItemAndNotify(new_folder, target_item_ptr.Pass());
140  source_item_ptr->set_position(
141      new_folder->item_list()->CreatePositionBefore(
142          syncer::StringOrdinal()));
143  AddItemToFolderItemAndNotify(new_folder, source_item_ptr.Pass());
144
145  return new_folder->id();
146}
147
148void AppListModel::MoveItemToFolder(AppListItem* item,
149                                    const std::string& folder_id) {
150  DVLOG(2) << "MoveItemToFolder: " << folder_id
151           << " <- " << item->ToDebugString();
152  if (item->folder_id() == folder_id)
153    return;
154  AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id);
155  scoped_ptr<AppListItem> item_ptr = RemoveItem(item);
156  if (dest_folder)
157    AddItemToFolderItemAndNotify(dest_folder, item_ptr.Pass());
158  else
159    AddItemToItemListAndNotifyUpdate(item_ptr.Pass());
160}
161
162bool AppListModel::MoveItemToFolderAt(AppListItem* item,
163                                      const std::string& folder_id,
164                                      syncer::StringOrdinal position) {
165  DVLOG(2) << "MoveItemToFolderAt: " << folder_id
166           << "[" << position.ToDebugString() << "]"
167           << " <- " << item->ToDebugString();
168  if (item->folder_id() == folder_id)
169    return false;
170  AppListFolderItem* src_folder = FindOrCreateFolderItem(item->folder_id());
171  if (src_folder &&
172      src_folder->folder_type() == AppListFolderItem::FOLDER_TYPE_OEM) {
173    LOG(WARNING) << "MoveItemToFolderAt called with OEM folder as source";
174    return false;
175  }
176  AppListFolderItem* dest_folder = FindOrCreateFolderItem(folder_id);
177  scoped_ptr<AppListItem> item_ptr = RemoveItem(item);
178  if (dest_folder) {
179    item_ptr->set_position(
180        dest_folder->item_list()->CreatePositionBefore(position));
181    AddItemToFolderItemAndNotify(dest_folder, item_ptr.Pass());
182  } else {
183    item_ptr->set_position(
184        top_level_item_list_->CreatePositionBefore(position));
185    AddItemToItemListAndNotifyUpdate(item_ptr.Pass());
186  }
187  return true;
188}
189
190void AppListModel::SetItemPosition(AppListItem* item,
191                                   const syncer::StringOrdinal& new_position) {
192  if (!item->IsInFolder()) {
193    top_level_item_list_->SetItemPosition(item, new_position);
194    // Note: this will trigger OnListItemMoved which will signal observers.
195    // (This is done this way because some View code still moves items within
196    // the item list directly).
197    return;
198  }
199  AppListFolderItem* folder = FindFolderItem(item->folder_id());
200  DCHECK(folder);
201  folder->item_list()->SetItemPosition(item, new_position);
202  FOR_EACH_OBSERVER(AppListModelObserver,
203                    observers_,
204                    OnAppListItemUpdated(item));
205}
206
207void AppListModel::SetItemName(AppListItem* item, const std::string& name) {
208  item->SetName(name);
209  DVLOG(2) << "AppListModel::SetItemName: " << item->ToDebugString();
210  FOR_EACH_OBSERVER(AppListModelObserver,
211                    observers_,
212                    OnAppListItemUpdated(item));
213}
214
215void AppListModel::SetItemNameAndShortName(AppListItem* item,
216                                           const std::string& name,
217                                           const std::string& short_name) {
218  item->SetNameAndShortName(name, short_name);
219  DVLOG(2) << "AppListModel::SetItemNameAndShortName: "
220           << item->ToDebugString();
221  FOR_EACH_OBSERVER(AppListModelObserver,
222                    observers_,
223                    OnAppListItemUpdated(item));
224}
225
226void AppListModel::DeleteItem(const std::string& id) {
227  AppListItem* item = FindItem(id);
228  if (!item)
229    return;
230  if (!item->IsInFolder()) {
231    DCHECK_EQ(0u, item->ChildItemCount())
232        << "Invalid call to DeleteItem for item with children: " << id;
233    FOR_EACH_OBSERVER(AppListModelObserver,
234                      observers_,
235                      OnAppListItemWillBeDeleted(item));
236    top_level_item_list_->DeleteItem(id);
237    FOR_EACH_OBSERVER(AppListModelObserver, observers_, OnAppListItemDeleted());
238    return;
239  }
240  AppListFolderItem* folder = FindFolderItem(item->folder_id());
241  DCHECK(folder) << "Folder not found for item: " << item->ToDebugString();
242  scoped_ptr<AppListItem> child_item = RemoveItemFromFolder(folder, item);
243  DCHECK_EQ(item, child_item.get());
244  FOR_EACH_OBSERVER(AppListModelObserver,
245                    observers_,
246                    OnAppListItemWillBeDeleted(item));
247  child_item.reset();  // Deletes item.
248  FOR_EACH_OBSERVER(AppListModelObserver, observers_, OnAppListItemDeleted());
249}
250
251void AppListModel::NotifyExtensionPreferenceChanged() {
252  for (size_t i = 0; i < top_level_item_list_->item_count(); ++i)
253    top_level_item_list_->item_at(i)->OnExtensionPreferenceChanged();
254}
255
256// Private methods
257
258void AppListModel::OnListItemMoved(size_t from_index,
259                                   size_t to_index,
260                                   AppListItem* item) {
261  FOR_EACH_OBSERVER(AppListModelObserver,
262                    observers_,
263                    OnAppListItemUpdated(item));
264}
265
266AppListFolderItem* AppListModel::FindOrCreateFolderItem(
267    const std::string& folder_id) {
268  if (folder_id.empty())
269    return NULL;
270
271  AppListFolderItem* dest_folder = FindFolderItem(folder_id);
272  if (dest_folder)
273    return dest_folder;
274
275  DVLOG(2) << "Creating new folder: " << folder_id;
276  scoped_ptr<AppListFolderItem> new_folder(
277      new AppListFolderItem(folder_id, AppListFolderItem::FOLDER_TYPE_NORMAL));
278  new_folder->set_position(
279      top_level_item_list_->CreatePositionBefore(syncer::StringOrdinal()));
280  AppListItem* new_folder_item =
281      AddItemToItemListAndNotify(new_folder.PassAs<AppListItem>());
282  return static_cast<AppListFolderItem*>(new_folder_item);
283}
284
285AppListItem* AppListModel::AddItemToItemListAndNotify(
286    scoped_ptr<AppListItem> item_ptr) {
287  DCHECK(!item_ptr->IsInFolder());
288  AppListItem* item = top_level_item_list_->AddItem(item_ptr.Pass());
289  FOR_EACH_OBSERVER(AppListModelObserver,
290                    observers_,
291                    OnAppListItemAdded(item));
292  return item;
293}
294
295AppListItem* AppListModel::AddItemToItemListAndNotifyUpdate(
296    scoped_ptr<AppListItem> item_ptr) {
297  DCHECK(!item_ptr->IsInFolder());
298  AppListItem* item = top_level_item_list_->AddItem(item_ptr.Pass());
299  FOR_EACH_OBSERVER(AppListModelObserver,
300                    observers_,
301                    OnAppListItemUpdated(item));
302  return item;
303}
304
305AppListItem* AppListModel::AddItemToFolderItemAndNotify(
306    AppListFolderItem* folder,
307    scoped_ptr<AppListItem> item_ptr) {
308  AppListItem* item = folder->item_list()->AddItem(item_ptr.Pass());
309  item->set_folder_id(folder->id());
310  FOR_EACH_OBSERVER(AppListModelObserver,
311                    observers_,
312                    OnAppListItemUpdated(item));
313  return item;
314}
315
316scoped_ptr<AppListItem> AppListModel::RemoveItem(AppListItem* item) {
317  if (!item->IsInFolder())
318    return top_level_item_list_->RemoveItem(item->id());
319
320  AppListFolderItem* folder = FindFolderItem(item->folder_id());
321  return RemoveItemFromFolder(folder, item);
322}
323
324scoped_ptr<AppListItem> AppListModel::RemoveItemFromFolder(
325    AppListFolderItem* folder,
326    AppListItem* item) {
327  std::string folder_id = folder->id();
328  DCHECK_EQ(item->folder_id(), folder_id);
329  scoped_ptr<AppListItem> result = folder->item_list()->RemoveItem(item->id());
330  result->set_folder_id("");
331  if (folder->item_list()->item_count() == 0) {
332    DVLOG(2) << "Deleting empty folder: " << folder->ToDebugString();
333    DeleteItem(folder_id);
334  }
335  return result.Pass();
336}
337
338}  // namespace app_list
339