Index: packages/observe/lib/src/observable_list.dart |
diff --git a/packages/observe/lib/src/observable_list.dart b/packages/observe/lib/src/observable_list.dart |
deleted file mode 100644 |
index 9d09b38d560b43844f98b40f7d2ea2ff44a28cdd..0000000000000000000000000000000000000000 |
--- a/packages/observe/lib/src/observable_list.dart |
+++ /dev/null |
@@ -1,304 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library observe.src.observable_list; |
- |
-import 'dart:async'; |
-import 'dart:collection' show ListBase, UnmodifiableListView; |
-import 'package:observe/observe.dart'; |
-import 'list_diff.dart' show projectListSplices, calcSplices; |
- |
-/// Represents an observable list of model values. If any items are added, |
-/// removed, or replaced, then observers that are listening to [changes] |
-/// will be notified. |
-class ObservableList<E> extends ListBase<E> with ChangeNotifier { |
- List<ListChangeRecord> _listRecords; |
- |
- StreamController _listChanges; |
- |
- /// The inner [List<E>] with the actual storage. |
- final List<E> _list; |
- |
- /// Creates an observable list of the given [length]. |
- /// |
- /// If no [length] argument is supplied an extendable list of |
- /// length 0 is created. |
- /// |
- /// If a [length] argument is supplied, a fixed size list of that |
- /// length is created. |
- ObservableList([int length]) |
- : _list = length != null ? new List<E>(length) : <E>[]; |
- |
- /// Creates an observable list with the elements of [other]. The order in |
- /// the list will be the order provided by the iterator of [other]. |
- factory ObservableList.from(Iterable<E> other) => |
- new ObservableList<E>()..addAll(other); |
- |
- /// The stream of summarized list changes, delivered asynchronously. |
- /// |
- /// Each list change record contains information about an individual mutation. |
- /// The records are projected so they can be applied sequentially. For |
- /// example, this set of mutations: |
- /// |
- /// var model = new ObservableList.from(['a', 'b']); |
- /// model.listChanges.listen((records) => records.forEach(print)); |
- /// model.removeAt(1); |
- /// model.insertAll(0, ['c', 'd', 'e']); |
- /// model.removeRange(1, 3); |
- /// model.insert(1, 'f'); |
- /// |
- /// The change records will be summarized so they can be "played back", using |
- /// the final list positions to figure out which item was added: |
- /// |
- /// #<ListChangeRecord index: 0, removed: [], addedCount: 2> |
- /// #<ListChangeRecord index: 3, removed: [b], addedCount: 0> |
- /// |
- /// [deliverChanges] can be called to force synchronous delivery. |
- Stream<List<ListChangeRecord>> get listChanges { |
- if (_listChanges == null) { |
- // TODO(jmesserly): split observed/unobserved notions? |
- _listChanges = new StreamController.broadcast(sync: true, |
- onCancel: () { _listChanges = null; }); |
- } |
- return _listChanges.stream; |
- } |
- |
- bool get hasListObservers => |
- _listChanges != null && _listChanges.hasListener; |
- |
- @reflectable int get length => _list.length; |
- |
- @reflectable set length(int value) { |
- int len = _list.length; |
- if (len == value) return; |
- |
- // Produce notifications if needed |
- _notifyChangeLength(len, value); |
- if (hasListObservers) { |
- if (value < len) { |
- _recordChange(new ListChangeRecord(this, value, |
- removed: _list.getRange(value, len).toList())); |
- } else { |
- _recordChange(new ListChangeRecord(this, len, addedCount: value - len)); |
- } |
- } |
- |
- _list.length = value; |
- } |
- |
- @reflectable E operator [](int index) => _list[index]; |
- |
- @reflectable void operator []=(int index, E value) { |
- var oldValue = _list[index]; |
- if (hasListObservers && oldValue != value) { |
- _recordChange(new ListChangeRecord(this, index, addedCount: 1, |
- removed: [oldValue])); |
- } |
- _list[index] = value; |
- } |
- |
- // Forwarders so we can reflect on the properties. |
- @reflectable bool get isEmpty => super.isEmpty; |
- @reflectable bool get isNotEmpty => super.isNotEmpty; |
- |
- // TODO(jmesserly): should we support first/last/single? They're kind of |
- // dangerous to use in a path because they throw exceptions. Also we'd need |
- // to produce property change notifications which seems to conflict with our |
- // existing list notifications. |
- |
- // The following methods are here so that we can provide nice change events. |
- |
- void setAll(int index, Iterable<E> iterable) { |
- if (iterable is! List && iterable is! Set) { |
- iterable = iterable.toList(); |
- } |
- var len = iterable.length; |
- if (hasListObservers && len > 0) { |
- _recordChange(new ListChangeRecord(this, index, addedCount: len, |
- removed: _list.getRange(index, len).toList())); |
- } |
- _list.setAll(index, iterable); |
- } |
- |
- void add(E value) { |
- int len = _list.length; |
- _notifyChangeLength(len, len + 1); |
- if (hasListObservers) { |
- _recordChange(new ListChangeRecord(this, len, addedCount: 1)); |
- } |
- |
- _list.add(value); |
- } |
- |
- void addAll(Iterable<E> iterable) { |
- int len = _list.length; |
- _list.addAll(iterable); |
- |
- _notifyChangeLength(len, _list.length); |
- |
- int added = _list.length - len; |
- if (hasListObservers && added > 0) { |
- _recordChange(new ListChangeRecord(this, len, addedCount: added)); |
- } |
- } |
- |
- bool remove(Object element) { |
- for (int i = 0; i < this.length; i++) { |
- if (this[i] == element) { |
- removeRange(i, i + 1); |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- void removeRange(int start, int end) { |
- _rangeCheck(start, end); |
- int rangeLength = end - start; |
- int len = _list.length; |
- |
- _notifyChangeLength(len, len - rangeLength); |
- if (hasListObservers && rangeLength > 0) { |
- _recordChange(new ListChangeRecord(this, start, |
- removed: _list.getRange(start, end).toList())); |
- } |
- |
- _list.removeRange(start, end); |
- } |
- |
- void insertAll(int index, Iterable<E> iterable) { |
- if (index < 0 || index > length) { |
- throw new RangeError.range(index, 0, length); |
- } |
- // TODO(floitsch): we can probably detect more cases. |
- if (iterable is! List && iterable is! Set) { |
- iterable = iterable.toList(); |
- } |
- int insertionLength = iterable.length; |
- // There might be errors after the length change, in which case the list |
- // will end up being modified but the operation not complete. Unless we |
- // always go through a "toList" we can't really avoid that. |
- int len = _list.length; |
- _list.length += insertionLength; |
- |
- _list.setRange(index + insertionLength, this.length, this, index); |
- _list.setAll(index, iterable); |
- |
- _notifyChangeLength(len, _list.length); |
- |
- if (hasListObservers && insertionLength > 0) { |
- _recordChange(new ListChangeRecord(this, index, |
- addedCount: insertionLength)); |
- } |
- } |
- |
- void insert(int index, E element) { |
- if (index < 0 || index > length) { |
- throw new RangeError.range(index, 0, length); |
- } |
- if (index == length) { |
- add(element); |
- return; |
- } |
- // We are modifying the length just below the is-check. Without the check |
- // Array.copy could throw an exception, leaving the list in a bad state |
- // (with a length that has been increased, but without a new element). |
- if (index is! int) throw new ArgumentError(index); |
- _list.length++; |
- _list.setRange(index + 1, length, this, index); |
- |
- _notifyChangeLength(_list.length - 1, _list.length); |
- if (hasListObservers) { |
- _recordChange(new ListChangeRecord(this, index, addedCount: 1)); |
- } |
- _list[index] = element; |
- } |
- |
- |
- E removeAt(int index) { |
- E result = this[index]; |
- removeRange(index, index + 1); |
- return result; |
- } |
- |
- void _rangeCheck(int start, int end) { |
- if (start < 0 || start > this.length) { |
- throw new RangeError.range(start, 0, this.length); |
- } |
- if (end < start || end > this.length) { |
- throw new RangeError.range(end, start, this.length); |
- } |
- } |
- |
- void _recordChange(ListChangeRecord record) { |
- if (!hasListObservers) return; |
- |
- if (_listRecords == null) { |
- _listRecords = []; |
- scheduleMicrotask(deliverListChanges); |
- } |
- _listRecords.add(record); |
- } |
- |
- void _notifyChangeLength(int oldValue, int newValue) { |
- notifyPropertyChange(#length, oldValue, newValue); |
- notifyPropertyChange(#isEmpty, oldValue == 0, newValue == 0); |
- notifyPropertyChange(#isNotEmpty, oldValue != 0, newValue != 0); |
- } |
- |
- /// Deprecated. Name had a typo, use [discardListChanges] instead. |
- @deprecated |
- void discardListChages() => discardListChanges(); |
- |
- void discardListChanges() { |
- // Leave _listRecords set so we don't schedule another delivery. |
- if (_listRecords != null) _listRecords = []; |
- } |
- |
- bool deliverListChanges() { |
- if (_listRecords == null) return false; |
- var records = projectListSplices(this, _listRecords); |
- _listRecords = null; |
- |
- if (hasListObservers && !records.isEmpty) { |
- _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records)); |
- return true; |
- } |
- return false; |
- } |
- |
- /// Calculates the changes to the list, if lacking individual splice mutation |
- /// information. |
- /// |
- /// This is not needed for change records produced by [ObservableList] itself, |
- /// but it can be used if the list instance was replaced by another list. |
- /// |
- /// The minimal set of splices can be synthesized given the previous state and |
- /// final state of a list. The basic approach is to calculate the edit |
- /// distance matrix and choose the shortest path through it. |
- /// |
- /// Complexity is `O(l * p)` where `l` is the length of the current list and |
- /// `p` is the length of the old list. |
- static List<ListChangeRecord> calculateChangeRecords( |
- List<Object> oldValue, List<Object> newValue) => |
- calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length); |
- |
- /// Updates the [previous] list using the change [records]. For added items, |
- /// the [current] list is used to find the current value. |
- static void applyChangeRecords(List<Object> previous, List<Object> current, |
- List<ListChangeRecord> changeRecords) { |
- |
- if (identical(previous, current)) { |
- throw new ArgumentError("can't use same list for previous and current"); |
- } |
- |
- for (var change in changeRecords) { |
- int addEnd = change.index + change.addedCount; |
- int removeEnd = change.index + change.removed.length; |
- |
- var addedItems = current.getRange(change.index, addEnd); |
- previous.replaceRange(change.index, removeEnd, addedItems); |
- } |
- } |
-} |