Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(131)

Side by Side Diff: pkg/mdv_observe/lib/src/observable_list.dart

Issue 17552019: Reorganize mdv and observe packages (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of mdv_observe;
6
7 /**
8 * Represents an observable list of model values. If any items are added,
9 * removed, or replaced, then observers that are listening to [changes]
10 * will be notified.
11 */
12 // TODO(jmesserly): remove implements List<E> once we can extend ListBase<E>
13 class ObservableList<E> extends _ListBaseWorkaround with ObservableMixin
14 implements List<E> {
15 List<ListChangeRecord> _records;
16
17 static const _LENGTH = const Symbol('length');
18
19 /** The inner [List<E>] with the actual storage. */
20 final List<E> _list;
21
22 /**
23 * Creates an observable list of the given [length].
24 *
25 * If no [length] argument is supplied an extendable list of
26 * length 0 is created.
27 *
28 * If a [length] argument is supplied, a fixed size list of that
29 * length is created.
30 */
31 ObservableList([int length])
32 : _list = length != null ? new List<E>(length) : <E>[];
33
34 /**
35 * Creates an observable list with the elements of [other]. The order in
36 * the list will be the order provided by the iterator of [other].
37 */
38 factory ObservableList.from(Iterable<E> other) =>
39 new ObservableList<E>()..addAll(other);
40
41 // TODO(jmesserly): remove once we have mirrors
42 getValueWorkaround(key) => key == _LENGTH ? length : null;
43
44 setValueWorkaround(key, value) {
45 if (key == _LENGTH) length = value;
46 }
47
48 int get length => _list.length;
49
50 set length(int value) {
51 int len = _list.length;
52 if (len == value) return;
53
54 // Produce notifications if needed
55 if (hasObservers) {
56 if (value < len) {
57 // Remove items, then adjust length. Note the reverse order.
58 _recordChange(new ListChangeRecord(value, removedCount: len - value));
59 } else {
60 // Adjust length then add items
61 _recordChange(new ListChangeRecord(len, addedCount: value - len));
62 }
63 }
64
65 _list.length = value;
66 }
67
68 E operator [](int index) => _list[index];
69
70 void operator []=(int index, E value) {
71 var oldValue = _list[index];
72 if (hasObservers) {
73 _recordChange(new ListChangeRecord(index, addedCount: 1,
74 removedCount: 1));
75 }
76 _list[index] = value;
77 }
78
79 // The following methods are here so that we can provide nice change events.
80
81 void setAll(int index, Iterable<E> iterable) {
82 if (iterable is! List && iterable is! Set) {
83 iterable = iterable.toList();
84 }
85 var len = iterable.length;
86 _list.setAll(index, iterable);
87 if (hasObservers && len > 0) {
88 _recordChange(
89 new ListChangeRecord(index, addedCount: len, removedCount: len));
90 }
91 }
92
93 void add(E value) {
94 int len = _list.length;
95 if (hasObservers) {
96 _recordChange(new ListChangeRecord(len, addedCount: 1));
97 }
98
99 _list.add(value);
100 }
101
102 void addAll(Iterable<E> iterable) {
103 int len = _list.length;
104 _list.addAll(iterable);
105 int added = _list.length - len;
106 if (hasObservers && added > 0) {
107 _recordChange(new ListChangeRecord(len, addedCount: added));
108 }
109 }
110
111 bool remove(Object element) {
112 for (int i = 0; i < this.length; i++) {
113 if (this[i] == element) {
114 removeRange(i, 1);
115 return true;
116 }
117 }
118 return false;
119 }
120
121 void removeRange(int start, int end) {
122 _rangeCheck(start, end);
123 int length = end - start;
124 _list.setRange(start, this.length - length, this, end);
125
126 int len = _list.length;
127 _list.length -= length;
128 if (hasObservers && length > 0) {
129 _recordChange(new ListChangeRecord(start, removedCount: length));
130 }
131 }
132
133 void insertAll(int index, Iterable<E> iterable) {
134 if (index < 0 || index > length) {
135 throw new RangeError.range(index, 0, length);
136 }
137 // TODO(floitsch): we can probably detect more cases.
138 if (iterable is! List && iterable is! Set) {
139 iterable = iterable.toList();
140 }
141 int insertionLength = iterable.length;
142 // There might be errors after the length change, in which case the list
143 // will end up being modified but the operation not complete. Unless we
144 // always go through a "toList" we can't really avoid that.
145 int len = _list.length;
146 _list.length += insertionLength;
147
148 _list.setRange(index + insertionLength, this.length, this, index);
149 _list.setAll(index, iterable);
150
151 if (hasObservers && insertionLength > 0) {
152 _recordChange(new ListChangeRecord(index, addedCount: insertionLength));
153 }
154 }
155
156 void insert(int index, E element) {
157 if (index < 0 || index > length) {
158 throw new RangeError.range(index, 0, length);
159 }
160 if (index == length) {
161 add(element);
162 return;
163 }
164 // We are modifying the length just below the is-check. Without the check
165 // Array.copy could throw an exception, leaving the list in a bad state
166 // (with a length that has been increased, but without a new element).
167 if (index is! int) throw new ArgumentError(index);
168 _list.length++;
169 _list.setRange(index + 1, length, this, index);
170 if (hasObservers) {
171 _recordChange(new ListChangeRecord(index, addedCount: 1));
172 }
173 _list[index] = element;
174 }
175
176
177 E removeAt(int index) {
178 E result = this[index];
179 removeRange(index, index + 1);
180 return result;
181 }
182
183 void _rangeCheck(int start, int end) {
184 if (start < 0 || start > this.length) {
185 throw new RangeError.range(start, 0, this.length);
186 }
187 if (end < start || end > this.length) {
188 throw new RangeError.range(end, start, this.length);
189 }
190 }
191
192 void _recordChange(ListChangeRecord record) {
193 if (_records == null) {
194 _records = [];
195 queueChangeRecords(_summarizeRecords);
196 }
197 _records.add(record);
198 }
199
200 /**
201 * We need to summarize change records. Consumers of these records want to
202 * apply the batch sequentially, and ensure that they can find inserted
203 * items by looking at that position in the list. This property does not
204 * hold in our record-as-you-go records. Consider:
205 *
206 * var model = toObservable(['a', 'b']);
207 * model.removeAt(1);
208 * model.insertAll(0, ['c', 'd', 'e']);
209 * model.removeRange(1, 3);
210 * model.insert(1, 'f');
211 *
212 * Here, we inserted some records and then removed some of them.
213 * If someone processed these records naively, they would "play back" the
214 * insert incorrectly, because those items will be shifted.
215 *
216 * We summarize changes using a straightforward technique:
217 * Simulate the moves and use the final item positions to synthesize a
218 * new list of changes records. This has the advantage of not depending
219 * on the actual *values*, so we don't need to perform N^2 edit
220 */
221 // TODO(jmesserly): there's probably something smarter here, but this
222 // algorithm is pretty simple. It has complexity equivalent to the original
223 // list modifications.
224 // One simple idea: we can simply update the index map as we do the operations
225 // to the list, then produce the records at the end.
226 void _summarizeRecords() {
227 int oldLength = length;
228 for (var r in _records) {
229 oldLength += r.removedCount - r.addedCount;
230 }
231
232 if (length != oldLength) {
233 notifyPropertyChange(_LENGTH, oldLength, length);
234 }
235
236 if (_records.length == 1) {
237 notifyChange(_records[0]);
238 _records = null;
239 return;
240 }
241
242 var items = [];
243 for (int i = 0; i < oldLength; i++) items.add(i);
244 for (var r in _records) {
245 items.removeRange(r.index, r.index + r.removedCount);
246
247 // Represent inserts with -1.
248 items.insertAll(r.index, new List.filled(r.addedCount, -1));
249 }
250 assert(items.length == length);
251
252 _records = null;
253
254 int index = 0;
255 int offset = 0;
256 while (index < items.length) {
257 // Skip unchanged items.
258 while (index < items.length && items[index] == index + offset) {
259 index++;
260 }
261
262 // Find inserts
263 int startIndex = index;
264 while (index < items.length && items[index] == -1) {
265 index++;
266 }
267
268 int added = index - startIndex;
269
270 // Use the delta between our actual and expected position to determine
271 // how much was removed.
272 int actualItem = index < items.length ? items[index] : oldLength;
273 int expectedItem = startIndex + offset;
274
275 int removed = actualItem - expectedItem;
276
277 if (added > 0 || removed > 0) {
278 notifyChange(new ListChangeRecord(startIndex, addedCount: added,
279 removedCount: removed));
280 }
281
282 offset += removed - added;
283 }
284 }
285 }
286
287 // TODO(jmesserly): bogus type to workaround spurious VM bug with generic base
288 // class and mixins.
289 abstract class _ListBaseWorkaround extends ListBase<dynamic> {}
OLDNEW
« no previous file with comments | « pkg/mdv_observe/lib/src/observable_box.dart ('k') | pkg/mdv_observe/lib/src/observable_map.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698