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

Unified Diff: observatory_pub_packages/observe/src/observable_list.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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 side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/observe/src/observable_list.dart
===================================================================
--- observatory_pub_packages/observe/src/observable_list.dart (revision 0)
+++ observatory_pub_packages/observe/src/observable_list.dart (working copy)
@@ -0,0 +1,304 @@
+// 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) {
+ _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);
+ }
+ }
+}
« no previous file with comments | « observatory_pub_packages/observe/src/observable_box.dart ('k') | observatory_pub_packages/observe/src/observable_map.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698