Return-Path: X-Original-To: apmail-incubator-flex-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-flex-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0D1F6DAB3 for ; Mon, 6 Aug 2012 21:27:51 +0000 (UTC) Received: (qmail 60799 invoked by uid 500); 6 Aug 2012 21:27:50 -0000 Delivered-To: apmail-incubator-flex-commits-archive@incubator.apache.org Received: (qmail 60762 invoked by uid 500); 6 Aug 2012 21:27:50 -0000 Mailing-List: contact flex-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: flex-dev@incubator.apache.org Delivered-To: mailing list flex-commits@incubator.apache.org Received: (qmail 60699 invoked by uid 99); 6 Aug 2012 21:27:50 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 06 Aug 2012 21:27:50 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 06 Aug 2012 21:27:47 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 7E4972388C65; Mon, 6 Aug 2012 21:26:16 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1370028 [23/43] - in /incubator/flex/whiteboard/cframpton/adobe.next: ./ frameworks/ frameworks/projects/advancedgrids/src/mx/collections/ frameworks/projects/advancedgrids/src/mx/controls/ frameworks/projects/airframework/src/mx/managers/... Date: Mon, 06 Aug 2012 21:26:02 -0000 To: flex-commits@incubator.apache.org From: cframpton@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120806212616.7E4972388C65@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as URL: http://svn.apache.org/viewvc/incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as?rev=1370028&view=auto ============================================================================== --- incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as (added) +++ incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as Mon Aug 6 21:25:54 2012 @@ -0,0 +1,1570 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package spark.collections +{ +import flash.events.Event; +import flash.events.EventDispatcher; + +import mx.collections.IList; +import mx.events.CollectionEvent; +import mx.events.CollectionEventKind; +import mx.events.PropertyChangeEvent; +import mx.events.PropertyChangeEventKind; +import mx.resources.IResourceManager; +import mx.resources.ResourceManager; + +import spark.events.TreeDataAdapterChangeEvent; +import spark.events.TreeDataAdapterChangeEventKind; + +[ExcludeClass] + +/** + * Exposes a tree defined by an ITreeDataAdapter as an IList to be + * displayed with Spark data containers like Tree and DataGrid. + * Initially, the list only contains the tree's root nodes. + * The openNode() method inserts a node's children into the list + * after the parent, and the closeNode() method removes a node's + * children from the list. + * + *

The openNodes property can be used to access and set + * the current list of open nodes.

+ * + *

Note: TreeList only implements the read-only methods of the IList + * interface. + * Any modifications to the tree structure should be done via the tree adapter. + * TreeList will handle TreeDataAdapterChangeEvents dispatched from the tree.

+ * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ +public class TreeList extends EventDispatcher implements IList +{ + include "../core/Version.as"; + + //-------------------------------------------------------------------------- + // + // Class Methods + // + //-------------------------------------------------------------------------- + + /** + * Converts a vector of objects to an array. + */ + private static function vectorToArray(vec:Vector.):Array + { + var vecLength:int = vec.length; + var arr:Array = new Array(vecLength); + + for (var i:int = 0; i < length; i++) + { + arr[i] = vec[i]; + } + + return arr; + } + + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @param tree The tree exposed by this TreeList. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function TreeList(tree:ITreeDataAdapter = null) + { + super(); + this.tree = tree; + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * Representation of the currently exposed parts of the tree. + * Also contains the currently open nodes (whether or not they are exposed). + */ + private var exposedTree:ExposedTree; + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + /** + * @private + */ + private function dispatchChangeEvent(type:String):void + { + if (hasEventListener(type)) + dispatchEvent(new Event(type)); + } + + /** + * @private + */ + private function dispatchCollectionChangeEvent(kind:String = null, location:int = -1, + oldLocation:int = -1, items:Array = null):void + { + const event:CollectionEvent = new CollectionEvent( + CollectionEvent.COLLECTION_CHANGE, + false, false, kind, + location, oldLocation, items); + dispatchEvent(event); + } + + //---------------------------------- + // openNodes + //---------------------------------- + + /** + * In TreeList, any manipulation of the openNodes vector should be done + * through _openNodes because of the defensive copy made by the getter. + * Subclasses that use openNodes can only manipulate the *values*. + */ + private var _openNodes:Vector. = new Vector.(); + + [Bindable("openNodesChanged")] + + /** + * Returns the set of open nodes. + * Setting this property will refresh the view and cause a + * CollectionEventKind.REFRESH CollectionEvent + * to be dispatched. + * If this property is set to null, getting it will still return an empty vector. + * + * @default An empty Vector. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function get openNodes():Vector. + { + return _openNodes.concat(); + } + + /** + * @private + */ + public function set openNodes(value:Vector.):void + { + if (_openNodes == value) + return; + + const valueCopy:Vector. = (value) ? value.concat() : new []; + + _openNodes = valueCopy; + + refresh(); + dispatchChangeEvent("openNodesChanged"); + } + + //---------------------------------- + // tree + //---------------------------------- + + private var _tree:ITreeDataAdapter; + + /** + * The tree adapter exposed by this TreeList. + * Setting this property will clear the set of open nodes, populate + * the view with the root nodes of the new tree adapter, and dispatch a + * CollectionEventKind.RESET CollectionEvent. + * + * @default null + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function get tree():ITreeDataAdapter + { + return _tree; + } + + /** + * @private + */ + public function set tree(value:ITreeDataAdapter):void + { + if (_tree == value) + return; + + if (_tree) + { + _tree.removeEventListener(TreeDataAdapterChangeEvent.TREE_DATA_ADAPTER_CHANGE, + treeDataAdapterChangeHandler); + } + + _tree = value; + if (_tree) + { + _tree.addEventListener(TreeDataAdapterChangeEvent.TREE_DATA_ADAPTER_CHANGE, + treeDataAdapterChangeHandler); + } + + reset(); + } + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Clears the nodes and opens the specified open nodes. + */ + private function refresh():void + { + if (!exposedTree) + { + exposedTree = new ExposedTree(); + } + + exposedTree.tree = _tree; + + for each (var openNode:Object in _openNodes) + { + exposedTree.open(openNode); + } + + dispatchCollectionChangeEvent(CollectionEventKind.REFRESH); + } + + + /** + * Clears the nodes and open nodes. + * If there's a new adapter, adds the new roots to the list. + */ + private function reset():void + { + if (!exposedTree) + exposedTree = new ExposedTree(); + + exposedTree.tree = tree; + _openNodes.length = 0; + + dispatchCollectionChangeEvent(CollectionEventKind.RESET); + } + + /** + * Add all of the specified parent node's children and open descendants to + * this list and mark the node as "open". + * The node's descendants are inserted after the specified node. + * If the node is not exposed, then this method only adds the node to the + * set of open nodes. + * If the specified node is already open, then this method does nothing. + * + *

Calling this method can cause a CollectionEventKind.ADD + * CollectionEvent to be dispatched. + * Note that the CollectionEvent's items is an empty array with + * length equal to the number of nodes that have been added

+ * + * @param node An ITreeDataAdapter node. + * + * FIXME (klin): Probably don't need return value. + * @return true If the node is exposed, a branch, and opened successfully. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function openNode(node:Object):Boolean + { + if (!tree || !node || tree.isLeafNode(node) || _openNodes.indexOf(node) != -1) + return false; + + _openNodes.push(node); + + var nodeIndex:int = exposedTree.getNodeFlatIndex(node); + var numExposedNodes:int = exposedTree.open(node); + var isNodeExposed:Boolean = nodeIndex != -1; + + // Only dispatch a collection event if there were nodes added. + if (isNodeExposed && numExposedNodes > 0) + { + var emptyArr:Array = new Array(numExposedNodes); + dispatchCollectionChangeEvent(CollectionEventKind.ADD, nodeIndex + 1, -1, emptyArr); + return true; + } + + return isNodeExposed; + } + + /** + * Remove all of the specified node's descendants from this list and mark the + * node as "closed". + * The node's descendants are assumed to follow the node itself in this IList. + * If the node is not exposed, then this method only removes the node from + * the set of open nodes. + * If the specified node is not currently open, then this method does nothing. + * + *

Calling this method can cause a CollectionEventKind.REMOVE + * CollectionEvent to be dispatched. + * Note that the CollectionEvent's items is an empty array with + * length equal to the number of nodes that have been removed.

+ * + * @param node An ITreeDataAdapter node. + * + * FIXME (klin): Probably don't need return value. + * @return true if the node is exposed, a branch and was closed. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function closeNode(node:Object):Boolean + { + if (!tree || !node || tree.isLeafNode(node)) + return false; + + var openIndex:int = _openNodes.indexOf(node); + if (openIndex == -1) + return false; + + _openNodes[openIndex] = _openNodes[_openNodes.length - 1]; + _openNodes.pop(); + + var nodeIndex:int = exposedTree.getNodeFlatIndex(node); + var numHiddenNodes:int = exposedTree.close(node); + var isNodeExposed:Boolean = nodeIndex != -1; + + // Only dispatch a collection event if there were nodes removed. + if (isNodeExposed && numHiddenNodes > 0) + { + var emptyArr:Array = new Array(numHiddenNodes); + dispatchCollectionChangeEvent(CollectionEventKind.REMOVE, nodeIndex + 1, -1, emptyArr); + return true; + } + + return isNodeExposed; + } + + /** + * True, if the specified node is currently open. + * The node may not be exposed. + * + * @param node An ITreeDataAdapter node. + * + * @return true, if the node is open. The node need not be exposed to be + * marked open. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function isNodeOpen(node:Object):Boolean + { + if (!tree || !node) + return false; + + return _openNodes.indexOf(node) != -1; + } + + /** + * Returns true only if this node's parent and ancestors are all opened. + * + * @param node An ITreeDataAdapter node. + * + * @return true, if the node's parent and ancestors are all opened or the node + * is a root node. + */ + public function isNodeExposed(node:Object):Boolean + { + if (!tree || !node) + return false; + + var nodeIndex:int = this.getItemIndex(node); + return nodeIndex != -1; + } + + /** + * This function replaces any old children of the provided node and + * replaces them with its new children. + * This function may dispatch a remove CollectionEvent followed by an add + * CollectionEvent. + * + * @param node An ITreeDataAdapter node. + */ + public function invalidateNode(node:Object):void + { + if (!tree || !node) + return; + + // FIXME (klin): need to do more here... + if (isNodeOpen(node)) + { + closeNode(node); + openNode(node); + } + } + + /** + * Handles change events from the tree. + */ + private function treeDataAdapterChangeHandler(event:TreeDataAdapterChangeEvent):void + { + switch (event.kind) + { + case TreeDataAdapterChangeEventKind.ADD: + { + treeDataAdapterAddHandler(event); + break; + } + + case TreeDataAdapterChangeEventKind.REMOVE: + { + treeDataAdapterRemoveHandler(event); + break; + } + + case TreeDataAdapterChangeEventKind.RESET: + { + reset(); + break; + } + } + } + + /** + * @private + */ + private function treeDataAdapterAddHandler(event:TreeDataAdapterChangeEvent):void + { + if (!tree) + return; + + const parent:Object = event.parent; + const nodes:Vector. = event.nodes; + const index:int = event.index; + const count:int = (nodes) ? nodes.length : 0; + + if (count <= 0 || index < 0 || index > (tree.getNodeChildrenCount(parent) - count)) + return; + + exposedTree.insertNodesAt(parent, index, count); + + if (!parent || (isNodeExposed(parent) && isNodeOpen(parent))) + { + dispatchCollectionChangeEvent(CollectionEventKind.ADD, + exposedTree.getNodeFlatIndex(nodes[0]), + -1, + vectorToArray(nodes)) + } + } + + /** + * @private + */ + private function treeDataAdapterRemoveHandler(event:TreeDataAdapterChangeEvent):void + { + if (!tree) + return; + + const parent:Object = event.parent; + const nodes:Vector. = event.nodes; + const index:int = event.index; + const count:int = (nodes) ? nodes.length : 0; + + if (count <= 0 || index < 0 || index >= (tree.getNodeChildrenCount(parent) + count)) + return; + + var removeCount:int = exposedTree.removeNodesAt(parent, index, count); + + if (!parent || (isNodeExposed(parent) && isNodeOpen(parent))) + { + // Must use (parent, index) position to get the flat index because + // the node has been removed. + // This should still work if index == tree.getNodeChildrenCount(parent). + var removeIndex:int = exposedTree.getFlatIndex(parent, index); + var emptyArr:Array = new Array(removeCount); + dispatchCollectionChangeEvent(CollectionEventKind.REMOVE, + removeIndex, + -1, + emptyArr) + } + } + + //-------------------------------------------------------------------------- + // + // IList Implementation (read-only) + // + //-------------------------------------------------------------------------- + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function get length():int + { + if (!tree) + return 0; + + return exposedTree.length; + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function getItemAt(index:int, prefetch:int=0):Object + { + if (!tree) + return null; + + if (index < 0 || index >= length) + { + var message:String = ResourceManager.getInstance().getString( + "collections", "outOfBounds", [ index ]); + throw new RangeError(message); + } + + return exposedTree.getNodeAt(index); + } + + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function getItemIndex(item:Object):int + { + if (!tree || !item) + return -1; + + return exposedTree.getNodeFlatIndex(item); + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function itemUpdated(item:Object, property:Object=null, oldValue:Object=null, newValue:Object=null):void + { + const pce:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); + pce.kind = PropertyChangeEventKind.UPDATE; + pce.source = item; + pce.property = property; + pce.oldValue = oldValue; + pce.newValue = newValue; + dispatchCollectionChangeEvent(CollectionEventKind.UPDATE, -1, -1, [pce]); + } + + /** + * @override + * + * Note: It is not recommended that components which use TreeList call toArray + * because of performance reasons. + * + * Returns an Array that is populated in the same order as the IList + * implementation. + * This method can throw an ItemPendingError. + * + * @return The array. + * + * @throws mx.collections.errors.ItemPendingError If the data is not yet completely loaded + * from a remote location. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function toArray():Array + { + const length:int = this.length; + if (!tree || length == 0) + return []; + + var arr:Array = new Array(length); + for (var i:int = 0; i < length; i++) + { + arr[i] = getItemAt(i); + } + + return arr; + } + + /** + * @override + * + * This method is not supported in TreeList. + * Mutation should be done through the tree adapter. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function addItem(item:Object):void + { + // addItem not supported in TreeList + } + + /** + * @override + * + * This method is not supported in TreeList. + * Mutation should be done through the tree adapter. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function addItemAt(item:Object, index:int):void + { + // addItemAt not supported in TreeList + } + + /** + * @override + * + * This method is not supported in TreeList. + * Mutation should be done through the tree adapter. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function removeAll():void + { + // removeAll is not supported in TreeList + } + + /** + * @override + * + * This method is not supported in TreeList. + * Mutation should be done through the tree adapter. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function removeItemAt(index:int):Object + { + // removeItemAt is not supported in TreeList + return null; + } + + /** + * @override + * + * This method is not supported in TreeList. + * Mutation should be done through the tree adapter. + * + * @langversion 3.0 + * @playerversion Flash 10 + * @playerversion AIR 2.0 + * @productversion Flex 4.5 + */ + public function setItemAt(item:Object, index:int):Object + { + // setItemAt is not supported in TreeList + return null; + } +} +} + +//-------------------------------------------------------------------------- +// +// TreeNode +// +//-------------------------------------------------------------------------- + +/** + * A sparse representation of a tree adapter node and its children. + * Stores the tree adapter node, whether the node is open, the number of exposed descendants, and + * a sparse list of possibly open children. + * If the node is open, numExposedDescendants should be set to the number of descendants exposed + * by this node. + * If the node is closed, numExposedDescendants is 0. + * The children vector can be sparsely populated by TreeNode representations of the tree adapter + * node's children. + * Typically, the children vector will only contain nodes that are opened. + * The pseudo-root (root of roots) is represented by a TreeNode with a null node property. + * + *

In our implementation, all exposed and open nodes are represented with the tree of TreeNodes. + * The exposed nodes may be calculated by walking the tree of TreeNodes. + * Exposed nodes are defined as nodes where its ancestors and parent are all open. + * Thus, some open nodes represented within the tree of TreeNodes may not be exposed.

+ */ +class TreeNode +{ + /** + * Constructor. + */ + public function TreeNode(node:Object = null, siblingIndex:int = -1, + isOpen:Boolean = false, numExposedDescendants:int = 0) + { + this.node = node; + this.siblingIndex = siblingIndex; + this.isOpen = isOpen; + this.numExposedDescendants = numExposedDescendants; + } + + public var node:Object; + public var siblingIndex:int; // index relative to its siblings, from tree.getNodeChildIndex(). + public var children:Vector. = new Vector.(); // possible open children + public var isOpen:Boolean; + public var numExposedDescendants:int; + + /** + * Inserts a child TreeNode into this TreeNode's children vector + * in ascending sibling index order. + */ + public function addTreeNode(node:TreeNode):void + { + var children:Vector. = this.children; + var childrenLength:int = children.length; + + for (var j:int = 0; j < childrenLength; j++) + { + if (children[j].siblingIndex > node.siblingIndex) + { + children.splice(j, 0, node); + return; + } + } + + children.splice(childrenLength, 0, node); + } + + /** + * Returns the TreeNode corresponding to the specified node. + */ + public function findTreeNode(node:Object):TreeNode + { + for each (var tn:TreeNode in children) + { + if (tn.node == node) + return tn; + } + + return null; + } +} + +import spark.collections.ITreeDataAdapter; + +//-------------------------------------------------------------------------- +// +// ExposedTree +// +//-------------------------------------------------------------------------- + +/** + * A sparse representation of the exposed part of the tree. + * Also keeps track of open nodes even if they are not exposed. + * Enables basic operations like expanding/collapsing subtrees and handling tree adapter changes. + * In our implementation, all exposed and open nodes are represented in this list. + * Exposed nodes are defined as nodes where its ancestors and parent are all open. + * Thus, some open nodes represented within the ExposedTree may not be exposed. + * + * The ExposedTree also can calculate the position of a node in the flattened tree (a node's "flat + * index") and vice versa. + * ExposedTree.getNodeFlatIndex() calculates the flat index of a provided node. + * An ExposedTreeIterator is used to find a node based on its index. + * The iterator is used to optimize for the case when getItemAt() is called with consecutive + * indices (e.g. [1, 2, 3, 4, 5]). + */ +final class ExposedTree +{ + + /** + * Constructor. + */ + public function ExposedTree(tree:ITreeDataAdapter = null) + { + this.tree = tree; + reset(); + } + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + /** + * The pseudo-root of the tree. + * This invisible root is just for convenience when working with trees with multiple roots. + */ + private var root:TreeNode; + + //---------------------------------- + // tree + //---------------------------------- + + private var _tree:ITreeDataAdapter; + + /** + * The underlying tree this ExposedTree represents. + */ + public function get tree():ITreeDataAdapter + { + return _tree; + } + + /** + * @private + */ + public function set tree(value:ITreeDataAdapter):void + { + _tree = value; + reset(); + } + + //---------------------------------- + // length + //---------------------------------- + + /** + * The number of currently exposed nodes. + */ + public function get length():int + { + return root.numExposedDescendants; + } + + //-------------------------------------------------------------------------- + // + // Utility Methods + // + //-------------------------------------------------------------------------- + + private var iterator:ExposedTreeIterator; + private var lastNode:Object; + private var lastNodeFlatIndex:int = -1; + + /** + * Clears bookmarks used by the ExposedTree to speed up getNodeAt() and getNodeFlatIndex(). + */ + private function clearBookmarks():void + { + // iterator used by getNodeAt(). + iterator = null; + + // (node, index) pair used by getNodeFlatIndex(). + lastNode = null; + lastNodeFlatIndex = -1; + } + + /** + * Resets the ExposedTree. + * Creates a new pseudo-root and exposes the roots of the tree adapter. + */ + private function reset():void + { + if (!root) + { + root = new TreeNode(null, 0, true, 0); + } + else + { + root.children = new Vector.(); + root.numExposedDescendants = 0; + } + + if (tree) + root.numExposedDescendants = tree.getNodeChildrenCount(null); + + clearBookmarks(); + } + + /** + * Returns path to specified node. + * (ex. [root, p0, p1, p2, ..., node]) + */ + private function nodeToTreePath(node:Object):Vector. + { + if (!tree || !node) + return null; + + var path:Vector. = new Vector.(); + var p:Object = tree.getNodeParent(node); + + path.push(node); + while (p) + { + path.push(p); + p = tree.getNodeParent(p); + } + + return path.reverse(); + } + + /** + * Returns a vector of TreeNodes corresponding to the path from the root TreeNode + * to the parent corresponding to the specified node (e.g. [root, ..., node's parentTN]). + * If any TreeNode does not exist along the way, this method returns null. + * + *

If includeNode is true, the TreeNode representing the specified node is also included + * (e.g. [root, ..., node's parentTN, node's TN]), but if the node's TreeNode does not exist, + * this method will return null.

+ * + *

If carvePath is true, TreeNodes are created when not found along the way including the + * node's TreeNode (e.g. [root, ..., tn.parentTN, tn]). + * When carvePath is true, this method should not return null as long as the node is in + * the tree.

+ */ + private function nodeToTreeNodePath(node:Object, includeNode:Boolean = false, + carvePath:Boolean = false):Vector. + { + if (!tree) + return null; + + if (!node) + return (includeNode) ? new [root] : new []; + + var path:Vector. = nodeToTreePath(node); + if (!path) + return null; + + // The path always includes "node". While we're building the TreeNode path, + // we only include and create a TreeNode for that "node" if carvePath is true. + var pathLength:int = (includeNode) ? path.length : path.length - 1; + var treeNodePath:Vector. = new Vector.(pathLength + 1); + var tn:TreeNode = root; + treeNodePath[0] = tn; + + for (var i:int = 0; i < pathLength; i++) + { + var pn:TreeNode = tn; + var n:Object = path[i]; + tn = tn.findTreeNode(n); + + if (!tn) + { + if (carvePath) + { + tn = new TreeNode(n, tree.getNodeChildIndex(n)); + pn.addTreeNode(tn); + } + else + { + // This node is not exposed, so there is no TreeNode path. + return null; + } + } + + treeNodePath[i+1] = tn; + } + + return treeNodePath; + } + + /** + * Updates each TreeNode's numExposedDescendants property along the provided path by adding + * the specified delta. + * The deepest node is always updated, and we walk up the tree updating open + * ancestors until we find an ancestor that is closed. + */ + private function updateTreeNodePath(treeNodePath:Vector., delta:int):void + { + if (!treeNodePath) + return; + + var pathLength:int = treeNodePath.length; + var tn:TreeNode = treeNodePath[pathLength - 1]; + + // The deepest node always needs to be updated because + // it is assumed to have changed. + tn.numExposedDescendants += delta; + + for (var i:int = pathLength - 2; i >= 0; i--) + { + tn = treeNodePath[i]; + if (!tn.isOpen) + break; + tn.numExposedDescendants += delta; + } + } + + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** + * Returns the node with the specified flat index. + * Optimized for sequential calls (e.g. 5, 6, 7, 8, ...). + */ + public function getNodeAt(flatIndex:int):Object + { + if (!tree || flatIndex < 0 || flatIndex >= length) + return null; + + if (!iterator) + iterator = new ExposedTreeIterator(root, this, tree); + + if (iterator.previousIndex == flatIndex) + return iterator.previousNode; + + iterator.seek(flatIndex); // no-op if iterator is already there. + return iterator.next(); + } + + /** + * Returns the list index in the flattened tree of the specified position + * in the tree. + * This method also happens to "work" if siblingIndex >= tree.getNodeChildrenCount(parent), + * which is useful in cases where the original node at siblingIndex was removed from the tree. + */ + public function getFlatIndex(parent:Object, siblingIndex:int):int + { + // path to parent TreeNode + var treeNodePath:Vector. = nodeToTreeNodePath(parent, true); + if (!treeNodePath) + return -1; + + // index = -1 for pseudo-root node ("root" property). + // for each node in the tree path: + // index = (index + 1) + siblingIndex + + // SUM(openChild's numExposedDescendants if openChild's index is < siblingIndex) + var index:int = -1; + var pathLength:int = treeNodePath.length; + + for (var i:int = 0; i < pathLength; i++) + { + // find the siblingIndex of the next node in the path. + // add that to the index + // add 1 for the current node in the path. + var j:int = i + 1; + var childSiblingIndex:int = (j < pathLength) ? + tree.getNodeChildIndex(treeNodePath[j].node) : + siblingIndex; + index += 1 + childSiblingIndex; + + // add exposed descendants for any open siblings whose index < childSiblingIndex + var pn:TreeNode = treeNodePath[i]; + var pnChildren:Vector. = pn.children; + for each (var tn:TreeNode in pnChildren) + { + var tnSiblingIndex:int = tn.siblingIndex; + + if (tn.isOpen && (tnSiblingIndex < childSiblingIndex)) + index += tn.numExposedDescendants; + else if (tnSiblingIndex >= childSiblingIndex) + break; + } + } + + return index; + } + + /** + * Returns the list index in the flattened tree of the specified node. + * This just uses getFlatIndex(). + */ + public function getNodeFlatIndex(node:Object):int + { + if (!tree || !node) + return -1; + + // Use bookmarks to optimize this. + if (lastNode && lastNode == node) + return lastNodeFlatIndex; + else if (iterator && iterator.previousNode == node) + return iterator.previousIndex; + + var index:int = getFlatIndex(tree.getNodeParent(node), tree.getNodeChildIndex(node)); + if (index != -1) + { + lastNode = node; + lastNodeFlatIndex = index; + } + return index; + } + + + /** + * Opens the specified node including any subtrees. + * Returns the number of descendants that have been exposed. + */ + public function open(node:Object):int + { + if (!tree || !node) + return 0; + + var treeNodePath:Vector. = nodeToTreeNodePath(node, true, true); + if (!treeNodePath) + return 0; + + // The last TreeNode in the path represents the node that is being opened. + var tn:TreeNode = treeNodePath[treeNodePath.length - 1]; + var count:int = tree.getNodeChildrenCount(tn.node); + var children:Vector. = tn.children; + for each (var child:TreeNode in children) + { + count += child.numExposedDescendants; + } + tn.isOpen = true; + updateTreeNodePath(treeNodePath, count); + clearBookmarks(); + + return tn.numExposedDescendants; + } + + /** + * Closes the specified node including any subtrees. + * Returns the number of descendants that have been hidden. + */ + public function close(node:Object):int + { + if (!tree || !node) + return 0; + + var treeNodePath:Vector. = nodeToTreeNodePath(node, true, true); + if (!treeNodePath) + return 0; + + // The last TreeNode in the path represents the node that is being closed. + var tn:TreeNode = treeNodePath[treeNodePath.length - 1]; + var numRemovedExposedNodes:int = tn.numExposedDescendants; + tn.isOpen = false; + updateTreeNodePath(treeNodePath, -numRemovedExposedNodes); + clearBookmarks(); + + return numRemovedExposedNodes; + } + + /** + * Updates the ExposedTree to include the block of inserted nodes. + * The insertIndex refers to the sibling-relative index where the insertion occurred. + * It is not a list index. + */ + public function insertNodesAt(parent:Object, insertIndex:int, count:int):void + { + var treeNodePath:Vector. = nodeToTreeNodePath(parent, true); + if (!treeNodePath) + return; + + // update sibling indices in children. + var parentTN:TreeNode = treeNodePath[treeNodePath.length - 1]; + var children:Vector. = parentTN.children; + var childrenLength:int = children.length; + for (var i:int = 0; i < childrenLength; i++) + { + var childTN:TreeNode = children[i]; + if (childTN.siblingIndex >= insertIndex) + childTN.siblingIndex += count; + } + + // Update the parent and ancestors' numExposedDescendants + updateTreeNodePath(treeNodePath, count); + clearBookmarks(); + } + + /** + * Updates the ExposedTree to remove the block of inserted nodes and their subtrees. + * The removeIndex refers to the sibling-relative index where the removal occurred. + * It is not a list index. + * Returns the actual number of descendant nodes removed. + */ + public function removeNodesAt(parent:Object, removeIndex:int, count:int):int + { + var treeNodePath:Vector. = nodeToTreeNodePath(parent, true); + if (!treeNodePath) + return 0; + + // remove any children TreeNodes in the specified range, and update the indices of + // children after the removed ones. + var parentTN:TreeNode = treeNodePath[treeNodePath.length - 1]; + var removeEndIndex:int = removeIndex + count; + var children:Vector. = parentTN.children; + var childrenLength:int = children.length; + var spliceStart:int = -1; + var spliceCount:int = 0; + var totalRemovedNodes:int = 0; + for (var i:int = 0; i < childrenLength; i++) + { + var childTN:TreeNode = children[i]; + var siblingIndex:int = childTN.siblingIndex; + if (siblingIndex >= removeIndex && siblingIndex < removeEndIndex) + { + if (spliceStart < 0) + spliceStart = i; + spliceCount++; + totalRemovedNodes += childTN.numExposedDescendants; + } + else if (siblingIndex >= removeEndIndex) + { + childTN.siblingIndex -= count; + } + } + children.splice(spliceStart, spliceCount); + totalRemovedNodes += count; + + // Update the parent and ancestors' numExposedDescendants + updateTreeNodePath(treeNodePath, -totalRemovedNodes); + clearBookmarks(); + return totalRemovedNodes; + } +} + +//-------------------------------------------------------------------------- +// +// ExposedTreePosition +// +//-------------------------------------------------------------------------- + +/** + * Represents a position in the tree. + * Each position is represented by an open parent and index to child. + */ +final class ExposedTreePosition +{ + public var parent:TreeNode; + public var nextChildIndex:int = 0; + + /** + * Index used to find the next open child. + * This is the index into parent.children. Not tree.getNodeChildIndex(). + */ + public var nextExposedTreeChildIndex:int = 0; + + /** + * Returns the next open TreeNode in parent.children whose index is + * >= nextExposedTreeChildIndex. + */ + public function get nextExposedTreeChild():TreeNode + { + var children:Vector. = parent.children; + var childrenLength:int = children.length; + + for (var i:int = nextExposedTreeChildIndex; i < childrenLength; i++) + { + var child:TreeNode = children[i]; + if (child.isOpen) + { + nextExposedTreeChildIndex = i; + return child; + } + } + return null; + } +} + +//-------------------------------------------------------------------------- +// +// ExposedTreeIterator +// +//-------------------------------------------------------------------------- + +/** + * Iterator used to efficiently do an inorder traversal of an ExposedTree + * (see ExposedTree.getNodeAt()). + * The traversal starts at the first root node in the tree (index == 0). + * When next() is called, the node at the current index is returned and iterator moves + * to the next node. + */ +final class ExposedTreeIterator +{ + + /** + * Constructor. + */ + public function ExposedTreeIterator(root:TreeNode, + exposedTree:ExposedTree, + tree:ITreeDataAdapter) + { + this.rootTreeNode = root; + this.exposedTree = exposedTree; + this.tree = tree; + reset(); + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + private var rootTreeNode:TreeNode; + private var exposedTree:ExposedTree; + private var tree:ITreeDataAdapter; + + /** + * When next() is called, the node at the current index is returned and + * the current index is incremented. + * When calling TreeList.getItemAt(index), index == currentFlatIndex - 1 because the iterator + * considers TreeList.root (the "pseudo-root") to be the node at index 0. + */ + private var currentFlatIndex:int; + + /** + * Stack of positions in the tree. + * Each position is represented by an open parent and index to child. + * As the iterator advances, it returns the child and increments the index. + * When no more children are left to visit for a position, the position gets popped off + * the stack. + * If the current child is an open node, the iterator pushes a new position + * using that child onto the stack. + */ + private var positionStack:Vector.; + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + private var _previousNode:Object; + + /** + * The last object returned by next(). + */ + public function get previousNode():Object + { + return _previousNode; + } + + /** + * The flat index of previousNode. + */ + public function get previousIndex():int + { + return currentFlatIndex - 2; + } + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Returns true if the next tree adapter node exists. + */ + public function hasNext():Boolean + { + // exposedTree.length doesn't include the pseudo-root, but our "nextFlatIndex" does include + // pseudo-root (at index 0). So, we subtract 1. + return currentFlatIndex - 1 < exposedTree.length; + } + + /** + * Returns the next node in the traversal. + */ + public function next():Object + { + var currentPosition:ExposedTreePosition = positionStack[positionStack.length - 1]; + var currentNode:Object = tree.getNodeChildAt(currentPosition.parent.node, + currentPosition.nextChildIndex); + + _previousNode = currentNode; + currentFlatIndex++; + + if (!hasNext()) + return currentNode; + + var nextOpenChild:TreeNode = currentPosition.nextExposedTreeChild; + currentPosition.nextChildIndex++; + + // If no more children to visit, then pop back up. + var treeNumChildren:int = tree.getNodeChildrenCount(currentPosition.parent.node); + if (currentPosition.nextChildIndex == treeNumChildren) + { + positionStack.pop(); + currentPosition = null; + } + + // If we are currently at an open child, then push the open child. + if (nextOpenChild && currentNode == nextOpenChild.node) + { + // Current position may be null if we just popped it above. In that case, + // we don't need to update. + if (currentPosition) + currentPosition.nextExposedTreeChildIndex++; + + // We push only if there are more children to visit. + if (tree.getNodeChildrenCount(nextOpenChild.node) > 0) + { + var nextPosition:ExposedTreePosition = new ExposedTreePosition(); + nextPosition.parent = nextOpenChild; + positionStack.push(nextPosition); + } + } + + return currentNode; + } + + /** + * Resets the iterator to point at the first root node in the tree. + * Thus, the iterator always starts at currentFlatIndex==1 because the iterator + * considers TreeList.root (the "pseudo-root") to be the node at index 0. + */ + private function reset():void + { + positionStack = new Vector.(); + var rootPosition:ExposedTreePosition = new ExposedTreePosition(); + rootPosition.parent = this.rootTreeNode; + positionStack.push(rootPosition); + currentFlatIndex = 1; + } + + /** + * Moves the iterator such that to point to the node at the specified index. + * This is equivalent to populating the positionStack such that + * currentFlatIndex = index + 1. + */ + public function seek(index:int):void + { + // Adjust index. Iterator's definition of "flat index" is offset by 1 from the actual list + // index such that list index == iterator index - 1 because the ExposedTree's "root" is + // considered by the iterator to be at position 0. + var targetFlatIndex:int = index + 1; + + // Already there, so do nothing. + if (targetFlatIndex == this.currentFlatIndex) + return; + + reset(); + + seekToPosition(targetFlatIndex); + } + + /** + * Helper method used by seek to position the iterator at the target index. + * This method recursively pushes positions until the positionStack is populated + * with the correct positions such that next() will return the node at the target index. + */ + private function seekToPosition(targetIndex:int):void + { + var currentPosition:ExposedTreePosition = positionStack[positionStack.length - 1]; + + var openChildren:Vector. = currentPosition.parent.children; + var openChildrenLength:int = openChildren.length; + var lastChild:TreeNode; + + // Simple case for no open children. + if (openChildrenLength == 0) + { + currentPosition.nextChildIndex = targetIndex - currentFlatIndex; + currentFlatIndex = targetIndex; + return; + } + + // We iterate over the open children. + for (var i:int = 0; i < openChildrenLength; i++) + { + // "child" is the current open node in the ExposedTree. "lastChild" is the previous. + // At this point, currentFlatIndex is the flat index of "lastChild". + var child:TreeNode = openChildren[i]; + + if (child.isOpen) + { + // Move currentFlatIndex to point to this open child. + currentFlatIndex += child.siblingIndex - + (lastChild ? lastChild.siblingIndex + 1 : 0); + + if (targetIndex <= currentFlatIndex) + { + // Case 1: If the target index is between the lastChild and child (including + // child), then calculate the correct index and return. + currentPosition.nextChildIndex = child.siblingIndex - + (currentFlatIndex - targetIndex); + currentFlatIndex = targetIndex; + return; + } + else + { + // Advance position and currentFlatIndex to point to "child". + currentPosition.nextChildIndex = child.siblingIndex + 1; + currentPosition.nextExposedTreeChildIndex = i + 1; + currentFlatIndex++; + + // If the target index is within the subtree of "child". + if (targetIndex < (currentFlatIndex + child.numExposedDescendants)) + { + // If we're done with currentPosition, clear it off the stack before continuing. + if (currentPosition.nextChildIndex == tree.getNodeChildrenCount(currentPosition.parent.node)) + { + positionStack.pop(); + currentPosition = null; + } + + // Go into "child" subtree. + var nextPosition:ExposedTreePosition = new ExposedTreePosition(); + nextPosition.parent = child; + positionStack.push(nextPosition); + + // Call recursively. + seekToPosition(targetIndex); + return; + } + else + { + currentFlatIndex += child.numExposedDescendants; + } + } + + lastChild = child; + } + + } + + // The target index is in the current position, but beyond the last open child. + currentPosition.nextChildIndex += targetIndex - currentFlatIndex; + currentFlatIndex = targetIndex; + } +} \ No newline at end of file Propchange: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/TreeList.as ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as URL: http://svn.apache.org/viewvc/incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as?rev=1370028&view=auto ============================================================================== --- incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as (added) +++ incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as Mon Aug 6 21:25:54 2012 @@ -0,0 +1,466 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package spark.collections +{ +import flash.events.Event; +import flash.events.EventDispatcher; + +import mx.resources.IResourceManager; +import mx.resources.ResourceManager; + +import spark.events.TreeDataAdapterChangeEvent; +import spark.events.TreeDataAdapterChangeEventKind; + +//-------------------------------------- +// Events +//-------------------------------------- + +/** + * Dispatched when the IMutableTreeDataAdapter has been updated in some way. + * + * @eventType spark.events.TreeDataAdapterChangeEvent.TREE_DATA_ADAPTER_CHANGE + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ +[Event(name="treeDataAdapterChange", type="spark.events.TreeDataAdapterChangeEvent")] + +//-------------------------------------- +// Other metadata +//-------------------------------------- + +[DefaultProperty("rootNodes")] + +/** + * The XMLTreeAdapter class is an ITreeAdapter implementation that adapts + * XML objects to be used in Flex components. + * + *

XML with no child tags is treated as a leaf. + * The only exception is if it has the "isBranch" attribute set to "true".

+ * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ +public class XMLTreeDataAdapter extends EventDispatcher implements IMutableTreeDataAdapter +{ + include "../core/Version.as"; + + //-------------------------------------------------------------------------- + // + // Constructor + // + //-------------------------------------------------------------------------- + + /** + * Constructor. + * + * @param rootNodes An XMLList of the root nodes. By default, this is set to an empty list. + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function XMLTreeDataAdapter(rootNodes:XMLList = null) + { + this.rootNodes = rootNodes; + } + + //-------------------------------------------------------------------------- + // + // Variables + // + //-------------------------------------------------------------------------- + + /** + * Used for accessing localized Error messages. + */ + private var resourceManager:IResourceManager = + ResourceManager.getInstance(); + + //-------------------------------------------------------------------------- + // + // Properties + // + //-------------------------------------------------------------------------- + + //---------------------------------- + // rootNodes + //---------------------------------- + + private var _rootNodes:XMLList; + + [Bindable("rootNodesChanged")] + + /** + * An XMLList containing the root nodes. + * The adapter does not track changes to the provided XMLList. + * If the list changes, this property will need to be set again. + * + * @default an empty XMLList + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function get rootNodes():XMLList + { + return _rootNodes.copy(); + } + + /** + * @private + */ + public function set rootNodes(value:XMLList):void + { + // Defensively copy the incoming value; tolerate null value + const valueCopy:XMLList = (value) ? value.copy() : new XMLList(); + + _rootNodes = valueCopy; + + dispatchTreeAdapterChangeEvent(TreeDataAdapterChangeEventKind.RESET); + dispatchChangeEvent("rootNodesChanged"); + } + + //-------------------------------------------------------------------------- + // + // Methods + // + //-------------------------------------------------------------------------- + + /** + * Helper function to dispatch change events only + * when someone's listening. + */ + private function dispatchChangeEvent(type:String):void + { + if (hasEventListener(type)) + dispatchEvent(new Event(type)); + } + + /** + * Helper function to dispatch TreeAdapterChangeEvents. + * Checks to see if anyone is listening first. + */ + private function dispatchTreeAdapterChangeEvent( + kind:String, + nodes:Vector. = null, + index:int = -1, + parent:Object = null):void + { + if (hasEventListener(TreeDataAdapterChangeEvent.TREE_DATA_ADAPTER_CHANGE)) + { + var e:TreeDataAdapterChangeEvent = + new TreeDataAdapterChangeEvent( + TreeDataAdapterChangeEvent.TREE_DATA_ADAPTER_CHANGE, + false, false, + kind, nodes, + index, parent); + dispatchEvent(e); + } + } + + /** + * Throws a RangeError if the index is invalid. + */ + private function checkIndex(index:int, list:XMLList):void + { + if (index < 0 || index >= list.length()) + { + var message:String = resourceManager.getString( + "collections", "outOfBounds", [ index ]); + throw new RangeError(message); + } + } + + //-------------------------------------------------------------------------- + // + // ITreeAdapter Methods + // + //-------------------------------------------------------------------------- + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function getNodeParent(node:Object):Object + { + if (!node || !(node is XML) || _rootNodes.contains(node)) + return null; + + return node.parent(); + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function isLeafNode(node:Object):Boolean + { + if (!node || !(node is XML)) + return false; + + // FIXME (klin): Should we generalize this? (e.g. "branchAttr"?) + var branchFlag:XMLList = node.@isBranch; + if (branchFlag.length() == 1) + { + //check flag and return + if (branchFlag[0] == "true") + return false; + else (branchFlag[0] == "false") + return true; + } + + return node.children().length() == 0; + } + + /** + * Checks to see if a node is a branch. + * A node is a branch if it is non-null and isLeafNode(node) is false. + * Passing in null always returns true. + */ + private function isBranchNode(node:Object):Boolean + { + // null node always returns true, because null means the "parent" of the root nodes. + if (!node) + return true; + + if (node && (node is XML) && !isLeafNode(node)) + return true; + + return false; + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function getNodeChildrenCount(parent:Object):int + { + if (!isBranchNode(parent)) + return 0; + + var children:XMLList = parent ? parent.children() : _rootNodes; + return children.length(); + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function getNodeChildIndex(node:Object):int + { + if (!node || !(node is XML)) + return -1; + + // check if this node is a root node. + var numRoots:int = _rootNodes.length(); + for (var i:int = 0; i < numRoots; i++) + { + if (_rootNodes[i] == node) + return i; + } + + return node.childIndex(); + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function getNodeChildAt(parent:Object, index:int):Object + { + if (!isBranchNode(parent)) + return null; + + var children:XMLList = parent ? parent.children() : _rootNodes; + checkIndex(index, children); + + return children[index]; + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function getNodeDepth(node:Object):int + { + if (!node || !(node is XML)) + return -1; + + var p:Object = getNodeParent(node); + var depth:int = 0; + while (p) + { + depth += 1; + p = getNodeParent(p); + } + + return depth; + } + + //-------------------------------------------------------------------------- + // + // IMutableTreeAdapter Methods + // + //-------------------------------------------------------------------------- + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function addNodeChildrenAt(parent:Object, index:int, nodes:Vector.):void + { + if (!isBranchNode(parent)) + return; + + var children:XMLList = parent ? parent.children() : _rootNodes; + checkIndex(index, children); + + if (!nodes || nodes.length <= 0) + return; + + // insertItemsAt() returns false and does nothing if any of the items is not xml. + if (XMLListUtil.insertNodesAt(children, index, nodes)) + { + dispatchTreeAdapterChangeEvent(TreeDataAdapterChangeEventKind.ADD, + nodes.concat(), index, parent); + } + } + + /** + * @inheritDoc + * + * @langversion 3.0 + * @playerversion Flash 11 + * @playerversion AIR 3.0 + * @productversion Flex 5 + */ + public function removeNodeChildrenAt(parent:Object, index:int, count:int):Vector. + { + if (!isBranchNode(parent)) + return null; + + var children:XMLList = parent ? parent.children() : _rootNodes; + checkIndex(index, children); + + if (count <= 0) + return null; + + var removedNodes:Vector. = XMLListUtil.removeNodesAt(children, index, count); + dispatchTreeAdapterChangeEvent(TreeDataAdapterChangeEventKind.REMOVE, + removedNodes.concat(), index, parent); + return removedNodes; + } + +} +} + +//-------------------------------------------------------------------------- +// +// XMLListUtil +// +//-------------------------------------------------------------------------- + +/** + * A utility class for manipulating XMLLists. + */ +class XMLListUtil +{ + /** + * Inserts the xml nodes into the provided XMLList at the specified index. + * All provided nodes must be xml nodes. + * + * @return true if the operation was successful. + */ + public static function insertNodesAt(xmlList:XMLList, index:int, nodes:Vector.):Boolean + { + var insertList:XMLList = new XMLList(); + for each (var node:Object in nodes) + { + // all nodes must be of type XML or XMLList + if (!(node is XML) && !(node is XMLList && node.length() == 1)) + return false; + + insertList += node; + } + + if (index == 0) + xmlList[0] = insertList + xmlList[0]; + else + xmlList[index - 1] += insertList; + + return true; + } + + /** + * Removes count xml nodes starting from the specified index. + * + * @return vector of the removed nodes + */ + public static function removeNodesAt(xmlList:XMLList, index:int, count:int):Vector. + { + var removedXML:Vector. = new Vector.(); + const endIndex:int = index + count; + + if (index < 0 || endIndex > xmlList.length()) + return removedXML; + + for (var i:int = endIndex - 1; i >= index; i--) + { + removedXML.push(xmlList[i]); + delete xmlList[i]; + } + return removedXML; + } +} \ No newline at end of file Propchange: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as ------------------------------------------------------------------------------ svn:eol-style = native Propchange: incubator/flex/whiteboard/cframpton/adobe.next/frameworks/projects/spark/src/spark/collections/XMLTreeDataAdapter.as ------------------------------------------------------------------------------ svn:mime-type = text/plain