OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library observe.src.observable_list; | 5 library observe.src.observable_list; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection' show ListBase, UnmodifiableListView; | 8 import 'dart:collection' show ListBase, UnmodifiableListView; |
9 import 'package:observe/observe.dart'; | 9 import 'package:observe/observe.dart'; |
10 import 'list_diff.dart' show projectListSplices, calcSplices; | 10 import 'list_diff.dart' show projectListSplices, calcSplices; |
11 | 11 |
12 /// Represents an observable list of model values. If any items are added, | 12 /// Represents an observable list of model values. If any items are added, |
13 /// removed, or replaced, then observers that are listening to [changes] | 13 /// removed, or replaced, then observers that are listening to [changes] |
14 /// will be notified. | 14 /// will be notified. |
15 class ObservableList<E> extends ListBase<E> with ChangeNotifier { | 15 class ObservableList<E> extends ListBase<E> with ChangeNotifier { |
16 List<ListChangeRecord> _listRecords; | 16 List<ListChangeRecord> _listRecords; |
17 | 17 |
18 StreamController _listChanges; | 18 StreamController<List<ListChangeRecord>> _listChanges; |
19 | 19 |
20 /// The inner [List<E>] with the actual storage. | 20 /// The inner [List<E>] with the actual storage. |
21 final List<E> _list; | 21 final List<E> _list; |
22 | 22 |
23 /// Creates an observable list of the given [length]. | 23 /// Creates an observable list of the given [length]. |
24 /// | 24 /// |
25 /// If no [length] argument is supplied an extendable list of | 25 /// If no [length] argument is supplied an extendable list of |
26 /// length 0 is created. | 26 /// length 0 is created. |
27 /// | 27 /// |
28 /// If a [length] argument is supplied, a fixed size list of that | 28 /// If a [length] argument is supplied, a fixed size list of that |
(...skipping 22 matching lines...) Expand all Loading... |
51 /// The change records will be summarized so they can be "played back", using | 51 /// The change records will be summarized so they can be "played back", using |
52 /// the final list positions to figure out which item was added: | 52 /// the final list positions to figure out which item was added: |
53 /// | 53 /// |
54 /// #<ListChangeRecord index: 0, removed: [], addedCount: 2> | 54 /// #<ListChangeRecord index: 0, removed: [], addedCount: 2> |
55 /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0> | 55 /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0> |
56 /// | 56 /// |
57 /// [deliverChanges] can be called to force synchronous delivery. | 57 /// [deliverChanges] can be called to force synchronous delivery. |
58 Stream<List<ListChangeRecord>> get listChanges { | 58 Stream<List<ListChangeRecord>> get listChanges { |
59 if (_listChanges == null) { | 59 if (_listChanges == null) { |
60 // TODO(jmesserly): split observed/unobserved notions? | 60 // TODO(jmesserly): split observed/unobserved notions? |
61 _listChanges = new StreamController.broadcast(sync: true, | 61 _listChanges = new StreamController.broadcast(sync: true, onCancel: () { |
62 onCancel: () { _listChanges = null; }); | 62 _listChanges = null; |
| 63 }); |
63 } | 64 } |
64 return _listChanges.stream; | 65 return _listChanges.stream; |
65 } | 66 } |
66 | 67 |
67 bool get hasListObservers => | 68 bool get hasListObservers => _listChanges != null && _listChanges.hasListener; |
68 _listChanges != null && _listChanges.hasListener; | |
69 | 69 |
70 @reflectable int get length => _list.length; | 70 @reflectable int get length => _list.length; |
71 | 71 |
72 @reflectable set length(int value) { | 72 @reflectable set length(int value) { |
73 int len = _list.length; | 73 int len = _list.length; |
74 if (len == value) return; | 74 if (len == value) return; |
75 | 75 |
76 // Produce notifications if needed | 76 // Produce notifications if needed |
77 _notifyChangeLength(len, value); | 77 _notifyChangeLength(len, value); |
78 if (hasListObservers) { | 78 if (hasListObservers) { |
79 if (value < len) { | 79 if (value < len) { |
80 _recordChange(new ListChangeRecord(this, value, | 80 _recordChange(new ListChangeRecord(this, value, |
81 removed: _list.getRange(value, len).toList())); | 81 removed: _list.getRange(value, len).toList())); |
82 } else { | 82 } else { |
83 _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); | 83 _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); |
84 } | 84 } |
85 } | 85 } |
86 | 86 |
87 _list.length = value; | 87 _list.length = value; |
88 } | 88 } |
89 | 89 |
90 @reflectable E operator [](int index) => _list[index]; | 90 @reflectable E operator [](int index) => _list[index]; |
91 | 91 |
92 @reflectable void operator []=(int index, E value) { | 92 @reflectable void operator []=(int index, E value) { |
93 var oldValue = _list[index]; | 93 var oldValue = _list[index]; |
94 if (hasListObservers && oldValue != value) { | 94 if (hasListObservers && oldValue != value) { |
95 _recordChange(new ListChangeRecord(this, index, addedCount: 1, | 95 _recordChange(new ListChangeRecord(this, index, |
96 removed: [oldValue])); | 96 addedCount: 1, removed: [oldValue])); |
97 } | 97 } |
98 _list[index] = value; | 98 _list[index] = value; |
99 } | 99 } |
100 | 100 |
101 // Forwarders so we can reflect on the properties. | 101 // Forwarders so we can reflect on the properties. |
102 @reflectable bool get isEmpty => super.isEmpty; | 102 @reflectable bool get isEmpty => super.isEmpty; |
103 @reflectable bool get isNotEmpty => super.isNotEmpty; | 103 @reflectable bool get isNotEmpty => super.isNotEmpty; |
104 | 104 |
105 // TODO(jmesserly): should we support first/last/single? They're kind of | 105 // TODO(jmesserly): should we support first/last/single? They're kind of |
106 // dangerous to use in a path because they throw exceptions. Also we'd need | 106 // dangerous to use in a path because they throw exceptions. Also we'd need |
107 // to produce property change notifications which seems to conflict with our | 107 // to produce property change notifications which seems to conflict with our |
108 // existing list notifications. | 108 // existing list notifications. |
109 | 109 |
110 // The following methods are here so that we can provide nice change events. | 110 // The following methods are here so that we can provide nice change events. |
111 | 111 |
112 void setAll(int index, Iterable<E> iterable) { | 112 void setAll(int index, Iterable<E> iterable) { |
113 if (iterable is! List && iterable is! Set) { | 113 if (iterable is! List && iterable is! Set) { |
114 iterable = iterable.toList(); | 114 iterable = iterable.toList(); |
115 } | 115 } |
116 var len = iterable.length; | 116 var length = iterable.length; |
117 if (hasListObservers && len > 0) { | 117 if (hasListObservers && length > 0) { |
118 _recordChange(new ListChangeRecord(this, index, addedCount: len, | 118 _recordChange(new ListChangeRecord(this, index, |
119 removed: _list.getRange(index, len).toList())); | 119 addedCount: length, removed: _list.sublist(index, length))); |
120 } | 120 } |
121 _list.setAll(index, iterable); | 121 _list.setAll(index, iterable); |
122 } | 122 } |
123 | 123 |
124 void add(E value) { | 124 void add(E value) { |
125 int len = _list.length; | 125 int len = _list.length; |
126 _notifyChangeLength(len, len + 1); | 126 _notifyChangeLength(len, len + 1); |
127 if (hasListObservers) { | 127 if (hasListObservers) { |
128 _recordChange(new ListChangeRecord(this, len, addedCount: 1)); | 128 _recordChange(new ListChangeRecord(this, len, addedCount: 1)); |
129 } | 129 } |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
181 // always go through a "toList" we can't really avoid that. | 181 // always go through a "toList" we can't really avoid that. |
182 int len = _list.length; | 182 int len = _list.length; |
183 _list.length += insertionLength; | 183 _list.length += insertionLength; |
184 | 184 |
185 _list.setRange(index + insertionLength, this.length, this, index); | 185 _list.setRange(index + insertionLength, this.length, this, index); |
186 _list.setAll(index, iterable); | 186 _list.setAll(index, iterable); |
187 | 187 |
188 _notifyChangeLength(len, _list.length); | 188 _notifyChangeLength(len, _list.length); |
189 | 189 |
190 if (hasListObservers && insertionLength > 0) { | 190 if (hasListObservers && insertionLength > 0) { |
191 _recordChange(new ListChangeRecord(this, index, | 191 _recordChange( |
192 addedCount: insertionLength)); | 192 new ListChangeRecord(this, index, addedCount: insertionLength)); |
193 } | 193 } |
194 } | 194 } |
195 | 195 |
196 void insert(int index, E element) { | 196 void insert(int index, E element) { |
197 if (index < 0 || index > length) { | 197 if (index < 0 || index > length) { |
198 throw new RangeError.range(index, 0, length); | 198 throw new RangeError.range(index, 0, length); |
199 } | 199 } |
200 if (index == length) { | 200 if (index == length) { |
201 add(element); | 201 add(element); |
202 return; | 202 return; |
203 } | 203 } |
204 // We are modifying the length just below the is-check. Without the check | 204 // We are modifying the length just below the is-check. Without the check |
205 // Array.copy could throw an exception, leaving the list in a bad state | 205 // Array.copy could throw an exception, leaving the list in a bad state |
206 // (with a length that has been increased, but without a new element). | 206 // (with a length that has been increased, but without a new element). |
207 if (index is! int) throw new ArgumentError(index); | 207 if (index is! int) throw new ArgumentError(index); |
208 _list.length++; | 208 _list.length++; |
209 _list.setRange(index + 1, length, this, index); | 209 _list.setRange(index + 1, length, this, index); |
210 | 210 |
211 _notifyChangeLength(_list.length - 1, _list.length); | 211 _notifyChangeLength(_list.length - 1, _list.length); |
212 if (hasListObservers) { | 212 if (hasListObservers) { |
213 _recordChange(new ListChangeRecord(this, index, addedCount: 1)); | 213 _recordChange(new ListChangeRecord(this, index, addedCount: 1)); |
214 } | 214 } |
215 _list[index] = element; | 215 _list[index] = element; |
216 } | 216 } |
217 | 217 |
218 | |
219 E removeAt(int index) { | 218 E removeAt(int index) { |
220 E result = this[index]; | 219 E result = this[index]; |
221 removeRange(index, index + 1); | 220 removeRange(index, index + 1); |
222 return result; | 221 return result; |
223 } | 222 } |
224 | 223 |
225 void _rangeCheck(int start, int end) { | 224 void _rangeCheck(int start, int end) { |
226 if (start < 0 || start > this.length) { | 225 if (start < 0 || start > this.length) { |
227 throw new RangeError.range(start, 0, this.length); | 226 throw new RangeError.range(start, 0, this.length); |
228 } | 227 } |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
274 /// This is not needed for change records produced by [ObservableList] itself, | 273 /// This is not needed for change records produced by [ObservableList] itself, |
275 /// but it can be used if the list instance was replaced by another list. | 274 /// but it can be used if the list instance was replaced by another list. |
276 /// | 275 /// |
277 /// The minimal set of splices can be synthesized given the previous state and | 276 /// The minimal set of splices can be synthesized given the previous state and |
278 /// final state of a list. The basic approach is to calculate the edit | 277 /// final state of a list. The basic approach is to calculate the edit |
279 /// distance matrix and choose the shortest path through it. | 278 /// distance matrix and choose the shortest path through it. |
280 /// | 279 /// |
281 /// Complexity is `O(l * p)` where `l` is the length of the current list and | 280 /// Complexity is `O(l * p)` where `l` is the length of the current list and |
282 /// `p` is the length of the old list. | 281 /// `p` is the length of the old list. |
283 static List<ListChangeRecord> calculateChangeRecords( | 282 static List<ListChangeRecord> calculateChangeRecords( |
284 List<Object> oldValue, List<Object> newValue) => | 283 List<Object> oldValue, List<Object> newValue) => |
285 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); | 284 calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); |
286 | 285 |
287 /// Updates the [previous] list using the change [records]. For added items, | 286 /// Updates the [previous] list using the change [records]. For added items, |
288 /// the [current] list is used to find the current value. | 287 /// the [current] list is used to find the current value. |
289 static void applyChangeRecords(List<Object> previous, List<Object> current, | 288 static void applyChangeRecords(List<Object> previous, List<Object> current, |
290 List<ListChangeRecord> changeRecords) { | 289 List<ListChangeRecord> changeRecords) { |
291 | |
292 if (identical(previous, current)) { | 290 if (identical(previous, current)) { |
293 throw new ArgumentError("can't use same list for previous and current"); | 291 throw new ArgumentError("can't use same list for previous and current"); |
294 } | 292 } |
295 | 293 |
296 for (var change in changeRecords) { | 294 for (var change in changeRecords) { |
297 int addEnd = change.index + change.addedCount; | 295 int addEnd = change.index + change.addedCount; |
298 int removeEnd = change.index + change.removed.length; | 296 int removeEnd = change.index + change.removed.length; |
299 | 297 |
300 var addedItems = current.getRange(change.index, addEnd); | 298 var addedItems = current.getRange(change.index, addEnd); |
301 previous.replaceRange(change.index, removeEnd, addedItems); | 299 previous.replaceRange(change.index, removeEnd, addedItems); |
302 } | 300 } |
303 } | 301 } |
304 } | 302 } |
OLD | NEW |