1// Copyright (c) 2013 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_item_list.h"
6
7#include "ui/app_list/app_list_item.h"
8
9namespace app_list {
10
11AppListItemList::AppListItemList() {
12}
13
14AppListItemList::~AppListItemList() {
15}
16
17void AppListItemList::AddObserver(AppListItemListObserver* observer) {
18  observers_.AddObserver(observer);
19}
20
21void AppListItemList::RemoveObserver(AppListItemListObserver* observer) {
22  DCHECK(observers_.HasObserver(observer));
23  observers_.RemoveObserver(observer);
24}
25
26AppListItem* AppListItemList::FindItem(const std::string& id) {
27  for (size_t i = 0; i < app_list_items_.size(); ++i) {
28    AppListItem* item = app_list_items_[i];
29    if (item->id() == id)
30      return item;
31  }
32  return NULL;
33}
34
35bool AppListItemList::FindItemIndex(const std::string& id, size_t* index) {
36  for (size_t i = 0; i < app_list_items_.size(); ++i) {
37    AppListItem* item = app_list_items_[i];
38    if (item->id() == id) {
39      *index = i;
40      return true;
41    }
42  }
43  return false;
44}
45
46void AppListItemList::MoveItem(size_t from_index, size_t to_index) {
47  DCHECK_LT(from_index, item_count());
48  DCHECK_LT(to_index, item_count());
49  if (from_index == to_index)
50    return;
51
52  AppListItem* target_item = app_list_items_[from_index];
53  DVLOG(2) << "MoveItem: " << from_index << " -> " << to_index << " ["
54           << target_item->position().ToDebugString() << "]";
55
56  // Remove the target item
57  app_list_items_.weak_erase(app_list_items_.begin() + from_index);
58
59  // Update the position
60  AppListItem* prev = to_index > 0 ? app_list_items_[to_index - 1] : NULL;
61  AppListItem* next =
62      to_index < item_count() ? app_list_items_[to_index] : NULL;
63  CHECK_NE(prev, next);
64  syncer::StringOrdinal new_position;
65  if (!prev) {
66    new_position = next->position().CreateBefore();
67  } else if (!next) {
68    new_position = prev->position().CreateAfter();
69  } else {
70    // It is possible that items were added with the same ordinal. To
71    // successfully move the item we need to fix this. We do not try to fix this
72    // when an item is added in order to avoid possible edge cases with sync.
73    if (prev->position().Equals(next->position()))
74      FixItemPosition(to_index);
75    new_position = prev->position().CreateBetween(next->position());
76  }
77  target_item->set_position(new_position);
78
79  DVLOG(2) << "Move: "
80           << " Prev: " << (prev ? prev->position().ToDebugString() : "(none)")
81           << " Next: " << (next ? next->position().ToDebugString() : "(none)")
82           << " -> " << new_position.ToDebugString();
83
84  // Insert the item and notify observers.
85  app_list_items_.insert(app_list_items_.begin() + to_index, target_item);
86  FOR_EACH_OBSERVER(AppListItemListObserver,
87                    observers_,
88                    OnListItemMoved(from_index, to_index, target_item));
89}
90
91void AppListItemList::SetItemPosition(AppListItem* item,
92                                      syncer::StringOrdinal new_position) {
93  DCHECK(item);
94  size_t from_index;
95  if (!FindItemIndex(item->id(), &from_index)) {
96    LOG(ERROR) << "SetItemPosition: Not in list: " << item->id().substr(0, 8);
97    return;
98  }
99  DCHECK(app_list_items_[from_index] == item);
100  if (!new_position.IsValid()) {
101    size_t last_index = app_list_items_.size() - 1;
102    if (from_index == last_index)
103      return;  // Already last item, do nothing.
104    new_position = app_list_items_[last_index]->position().CreateAfter();
105  }
106  // First check if the order would remain the same, in which case just update
107  // the position.
108  size_t to_index = GetItemSortOrderIndex(new_position, item->id());
109  if (to_index == from_index) {
110    DVLOG(2) << "SetItemPosition: No change: " << item->id().substr(0, 8);
111    item->set_position(new_position);
112    return;
113  }
114  // Remove the item and get the updated to index.
115  app_list_items_.weak_erase(app_list_items_.begin() + from_index);
116  to_index = GetItemSortOrderIndex(new_position, item->id());
117  DVLOG(2) << "SetItemPosition: " << item->id().substr(0, 8) << " -> "
118           << new_position.ToDebugString() << " From: " << from_index
119           << " To: " << to_index;
120  item->set_position(new_position);
121  app_list_items_.insert(app_list_items_.begin() + to_index, item);
122  FOR_EACH_OBSERVER(AppListItemListObserver,
123                    observers_,
124                    OnListItemMoved(from_index, to_index, item));
125}
126
127// AppListItemList private
128
129syncer::StringOrdinal AppListItemList::CreatePositionBefore(
130    const syncer::StringOrdinal& position) {
131  if (app_list_items_.empty())
132    return syncer::StringOrdinal::CreateInitialOrdinal();
133
134  size_t nitems = app_list_items_.size();
135  size_t index;
136  if (!position.IsValid()) {
137    index = nitems;
138  } else {
139    for (index = 0; index < nitems; ++index) {
140      if (!app_list_items_[index]->position().LessThan(position))
141        break;
142    }
143  }
144  if (index == 0)
145    return app_list_items_[0]->position().CreateBefore();
146  if (index == nitems)
147    return app_list_items_[nitems - 1]->position().CreateAfter();
148  return app_list_items_[index - 1]->position().CreateBetween(
149      app_list_items_[index]->position());
150}
151
152AppListItem* AppListItemList::AddItem(scoped_ptr<AppListItem> item_ptr) {
153  AppListItem* item = item_ptr.get();
154  CHECK(std::find(app_list_items_.begin(), app_list_items_.end(), item)
155        == app_list_items_.end());
156  EnsureValidItemPosition(item);
157  size_t index = GetItemSortOrderIndex(item->position(), item->id());
158  app_list_items_.insert(app_list_items_.begin() + index, item_ptr.release());
159  FOR_EACH_OBSERVER(AppListItemListObserver,
160                    observers_,
161                    OnListItemAdded(index, item));
162  return item;
163}
164
165void AppListItemList::DeleteItem(const std::string& id) {
166  scoped_ptr<AppListItem> item = RemoveItem(id);
167  // |item| will be deleted on destruction.
168}
169
170scoped_ptr<AppListItem> AppListItemList::RemoveItem(const std::string& id) {
171  size_t index;
172  if (!FindItemIndex(id, &index))
173    LOG(FATAL) << "RemoveItem: Not found: " << id;
174  return RemoveItemAt(index);
175}
176
177scoped_ptr<AppListItem> AppListItemList::RemoveItemAt(size_t index) {
178  CHECK_LT(index, item_count());
179  AppListItem* item = app_list_items_[index];
180  app_list_items_.weak_erase(app_list_items_.begin() + index);
181  FOR_EACH_OBSERVER(AppListItemListObserver,
182                    observers_,
183                    OnListItemRemoved(index, item));
184  return make_scoped_ptr<AppListItem>(item);
185}
186
187void AppListItemList::DeleteItemAt(size_t index) {
188  scoped_ptr<AppListItem> item = RemoveItemAt(index);
189  // |item| will be deleted on destruction.
190}
191
192void AppListItemList::EnsureValidItemPosition(AppListItem* item) {
193  syncer::StringOrdinal position = item->position();
194  if (position.IsValid())
195    return;
196  size_t nitems = app_list_items_.size();
197  if (nitems == 0) {
198    position = syncer::StringOrdinal::CreateInitialOrdinal();
199  } else {
200    position = app_list_items_[nitems - 1]->position().CreateAfter();
201  }
202  item->set_position(position);
203}
204
205size_t AppListItemList::GetItemSortOrderIndex(
206    const syncer::StringOrdinal& position,
207    const std::string& id) {
208  DCHECK(position.IsValid());
209  for (size_t index = 0; index < app_list_items_.size(); ++index) {
210    if (position.LessThan(app_list_items_[index]->position()) ||
211        (position.Equals(app_list_items_[index]->position()) &&
212         (id < app_list_items_[index]->id()))) {
213      return index;
214    }
215  }
216  return app_list_items_.size();
217}
218
219void AppListItemList::FixItemPosition(size_t index) {
220  DVLOG(1) << "FixItemPosition: " << index;
221  size_t nitems = item_count();
222  DCHECK_LT(index, nitems);
223  DCHECK_GT(index, 0u);
224  // Update the position of |index| and any necessary subsequent items.
225  // First, find the next item that has a different position.
226  AppListItem* prev = app_list_items_[index - 1];
227  size_t last_index = index + 1;
228  for (; last_index < nitems; ++last_index) {
229    if (!app_list_items_[last_index]->position().Equals(prev->position()))
230      break;
231  }
232  AppListItem* last = last_index < nitems ? app_list_items_[last_index] : NULL;
233  for (size_t i = index; i < last_index; ++i) {
234    AppListItem* cur = app_list_items_[i];
235    if (last)
236      cur->set_position(prev->position().CreateBetween(last->position()));
237    else
238      cur->set_position(prev->position().CreateAfter());
239    prev = cur;
240  }
241  FOR_EACH_OBSERVER(AppListItemListObserver,
242                    observers_,
243                    OnListItemMoved(index, index, app_list_items_[index]));
244}
245
246}  // namespace app_list
247