1// Copyright 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 "ash/shelf/shelf_model.h"
6
7#include <algorithm>
8
9#include "ash/ash_switches.h"
10#include "ash/shelf/shelf_model_observer.h"
11
12namespace ash {
13
14namespace {
15
16int ShelfItemTypeToWeight(ShelfItemType type) {
17  switch (type) {
18    case TYPE_APP_LIST:
19      // TODO(skuhne): If the app list item becomes movable again, this need
20      // to be a fallthrough.
21      return 0;
22    case TYPE_BROWSER_SHORTCUT:
23    case TYPE_APP_SHORTCUT:
24      return 1;
25    case TYPE_WINDOWED_APP:
26    case TYPE_PLATFORM_APP:
27      return 2;
28    case TYPE_DIALOG:
29      return 3;
30    case TYPE_APP_PANEL:
31      return 4;
32    case TYPE_UNDEFINED:
33      NOTREACHED() << "ShelfItemType must be set";
34      return -1;
35  }
36
37  NOTREACHED() << "Invalid type " << type;
38  return 1;
39}
40
41bool CompareByWeight(const ShelfItem& a, const ShelfItem& b) {
42  return ShelfItemTypeToWeight(a.type) < ShelfItemTypeToWeight(b.type);
43}
44
45}  // namespace
46
47ShelfModel::ShelfModel() : next_id_(1), status_(STATUS_NORMAL) {
48}
49
50ShelfModel::~ShelfModel() {
51}
52
53int ShelfModel::Add(const ShelfItem& item) {
54  return AddAt(items_.size(), item);
55}
56
57int ShelfModel::AddAt(int index, const ShelfItem& item) {
58  index = ValidateInsertionIndex(item.type, index);
59  items_.insert(items_.begin() + index, item);
60  items_[index].id = next_id_++;
61  FOR_EACH_OBSERVER(ShelfModelObserver, observers_, ShelfItemAdded(index));
62  return index;
63}
64
65void ShelfModel::RemoveItemAt(int index) {
66  DCHECK(index >= 0 && index < item_count());
67  // The app list and browser shortcut can't be removed.
68  DCHECK(items_[index].type != TYPE_APP_LIST &&
69         items_[index].type != TYPE_BROWSER_SHORTCUT);
70  ShelfID id = items_[index].id;
71  items_.erase(items_.begin() + index);
72  FOR_EACH_OBSERVER(ShelfModelObserver, observers_,
73                    ShelfItemRemoved(index, id));
74}
75
76void ShelfModel::Move(int index, int target_index) {
77  if (index == target_index)
78    return;
79  // TODO: this needs to enforce valid ranges.
80  ShelfItem item(items_[index]);
81  items_.erase(items_.begin() + index);
82  items_.insert(items_.begin() + target_index, item);
83  FOR_EACH_OBSERVER(ShelfModelObserver, observers_,
84                    ShelfItemMoved(index, target_index));
85}
86
87void ShelfModel::Set(int index, const ShelfItem& item) {
88  DCHECK(index >= 0 && index < item_count());
89  int new_index = item.type == items_[index].type ?
90      index : ValidateInsertionIndex(item.type, index);
91
92  ShelfItem old_item(items_[index]);
93  items_[index] = item;
94  items_[index].id = old_item.id;
95  FOR_EACH_OBSERVER(ShelfModelObserver, observers_,
96                    ShelfItemChanged(index, old_item));
97
98  // If the type changes confirm that the item is still in the right order.
99  if (new_index != index) {
100    // The move function works by removing one item and then inserting it at the
101    // new location. However - by removing the item first the order will change
102    // so that our target index needs to be corrected.
103    // TODO(skuhne): Moving this into the Move function breaks lots of unit
104    // tests. So several functions were already using this incorrectly.
105    // That needs to be cleaned up.
106    if (index < new_index)
107      new_index--;
108
109    Move(index, new_index);
110  }
111}
112
113int ShelfModel::ItemIndexByID(ShelfID id) const {
114  ShelfItems::const_iterator i = ItemByID(id);
115  return i == items_.end() ? -1 : static_cast<int>(i - items_.begin());
116}
117
118int ShelfModel::GetItemIndexForType(ShelfItemType type) {
119  for (size_t i = 0; i < items_.size(); ++i) {
120    if (items_[i].type == type)
121      return i;
122  }
123  return -1;
124}
125
126ShelfItems::const_iterator ShelfModel::ItemByID(int id) const {
127  for (ShelfItems::const_iterator i = items_.begin();
128       i != items_.end(); ++i) {
129    if (i->id == id)
130      return i;
131  }
132  return items_.end();
133}
134
135int ShelfModel::FirstRunningAppIndex() const {
136  // Since lower_bound only checks weights against each other, we do not need
137  // to explicitly change different running application types.
138  DCHECK_EQ(ShelfItemTypeToWeight(TYPE_WINDOWED_APP),
139            ShelfItemTypeToWeight(TYPE_PLATFORM_APP));
140  ShelfItem weight_dummy;
141  weight_dummy.type = TYPE_WINDOWED_APP;
142  return std::lower_bound(items_.begin(), items_.end(), weight_dummy,
143                          CompareByWeight) - items_.begin();
144}
145
146int ShelfModel::FirstPanelIndex() const {
147  ShelfItem weight_dummy;
148  weight_dummy.type = TYPE_APP_PANEL;
149  return std::lower_bound(items_.begin(), items_.end(), weight_dummy,
150                          CompareByWeight) - items_.begin();
151}
152
153void ShelfModel::SetStatus(Status status) {
154  if (status_ == status)
155    return;
156
157  status_ = status;
158  FOR_EACH_OBSERVER(ShelfModelObserver, observers_, ShelfStatusChanged());
159}
160
161void ShelfModel::AddObserver(ShelfModelObserver* observer) {
162  observers_.AddObserver(observer);
163}
164
165void ShelfModel::RemoveObserver(ShelfModelObserver* observer) {
166  observers_.RemoveObserver(observer);
167}
168
169int ShelfModel::ValidateInsertionIndex(ShelfItemType type, int index) const {
170  DCHECK(index >= 0 && index <= item_count() + 1);
171
172  // Clamp |index| to the allowed range for the type as determined by |weight|.
173  ShelfItem weight_dummy;
174  weight_dummy.type = type;
175  index = std::max(std::lower_bound(items_.begin(), items_.end(), weight_dummy,
176                                    CompareByWeight) - items_.begin(),
177                   static_cast<ShelfItems::difference_type>(index));
178  index = std::min(std::upper_bound(items_.begin(), items_.end(), weight_dummy,
179                                    CompareByWeight) - items_.begin(),
180                   static_cast<ShelfItems::difference_type>(index));
181
182  return index;
183}
184
185}  // namespace ash
186