| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 mx.controls |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.display.Graphics; |
| import flash.display.Shape; |
| import flash.display.Sprite; |
| import flash.events.Event; |
| import flash.events.KeyboardEvent; |
| import flash.events.MouseEvent; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.ui.Keyboard; |
| import flash.utils.clearInterval; |
| import flash.utils.getTimer; |
| import flash.xml.XMLNode; |
| import mx.collections.ArrayCollection; |
| import mx.collections.CursorBookmark; |
| import mx.collections.ICollectionView; |
| import mx.collections.ItemResponder; |
| import mx.collections.IViewCursor; |
| import mx.collections.XMLListCollection; |
| import mx.collections.errors.ItemPendingError; |
| import mx.controls.listClasses.BaseListData; |
| import mx.controls.listClasses.IDropInListItemRenderer; |
| import mx.controls.listClasses.IListItemRenderer; |
| import mx.controls.listClasses.ListRowInfo; |
| import mx.controls.listClasses.ListBaseSelectionDataPending; |
| import mx.controls.treeClasses.DefaultDataDescriptor; |
| import mx.controls.treeClasses.HierarchicalCollectionView; |
| import mx.controls.treeClasses.HierarchicalViewCursor; |
| import mx.controls.treeClasses.ITreeDataDescriptor; |
| import mx.controls.treeClasses.ITreeDataDescriptor2; |
| import mx.controls.treeClasses.TreeItemRenderer; |
| import mx.controls.treeClasses.TreeListData; |
| import mx.core.ClassFactory; |
| import mx.core.EdgeMetrics; |
| import mx.core.EventPriority; |
| import mx.core.FlexSprite; |
| import mx.core.FlexShape; |
| import mx.core.IDataRenderer; |
| import mx.core.IFactory; |
| import mx.core.IFlexDisplayObject; |
| import mx.core.IIMESupport; |
| import mx.core.IInvalidating; |
| import mx.core.UIComponent; |
| import mx.core.UIComponentGlobals; |
| import mx.core.mx_internal; |
| import mx.effects.Tween; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| import mx.events.DragEvent; |
| import mx.events.FlexEvent; |
| import mx.events.ListEvent; |
| import mx.events.ListEventReason; |
| import mx.events.ScrollEvent; |
| import mx.events.TreeEvent; |
| import mx.managers.DragManager; |
| import mx.managers.ISystemManager; |
| import mx.managers.SystemManager; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when a branch is closed or collapsed. |
| * |
| * @eventType mx.events.TreeEvent.ITEM_CLOSE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemClose", type="mx.events.TreeEvent")] |
| |
| /** |
| * Dispatched when a branch is opened or expanded. |
| * |
| * @eventType mx.events.TreeEvent.ITEM_OPEN |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemOpen", type="mx.events.TreeEvent")] |
| |
| /** |
| * Dispatched when a branch open or close is initiated. |
| * |
| * @eventType mx.events.TreeEvent.ITEM_OPENING |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemOpening", type="mx.events.TreeEvent")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| include "../styles/metadata/PaddingStyles.as"; |
| |
| /** |
| * Colors for rows in an alternating pattern. |
| * Value can be an Array of two of more colors. |
| * Used only if the <code>backgroundColor</code> property is not specified. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Array of colors used in the Tree control, in descending order. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="depthColors", type="Array", arrayType="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Specifies the default icon for a leaf item. |
| * In MXML, you can use the following syntax to set this property: |
| * <code>defaultLeafIcon="@Embed(source='c.jpg');"</code> |
| * |
| * The default value is the "TreeNodeIcon" symbol in the Assets.swf file. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="defaultLeafIcon", type="Class", format="EmbeddedFile", inherit="no")] |
| |
| /** |
| * Specifies the icon that is displayed next to a parent item that is open so that its |
| * children are displayed. |
| * |
| * The default value is the "TreeDisclosureOpen" symbol in the Assets.swf file. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="disclosureOpenIcon", type="Class", format="EmbeddedFile", inherit="no")] |
| |
| /** |
| * Specifies the icon that is displayed next to a parent item that is closed so that its |
| * children are not displayed (the subtree is collapsed). |
| * |
| * The default value is the "TreeDisclosureClosed" symbol in the Assets.swf file. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="disclosureClosedIcon", type="Class", format="EmbeddedFile", inherit="no")] |
| |
| /** |
| * Specifies the folder open icon for a branch item of the tree. |
| * In MXML, you can use the following syntax to set this property: |
| * <code>folderOpenIcon="@Embed(source='a.jpg');"</code> |
| * |
| * The default value is the "TreeFolderOpen" symbol in the Assets.swf file. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="folderOpenIcon", type="Class", format="EmbeddedFile", inherit="no")] |
| |
| /** |
| * Specifies the folder closed icon for a branch item of the tree. |
| * In MXML, you can use the following syntax to set this property: |
| * <code>folderClosedIcon="@Embed(source='b.jpg');"</code> |
| * |
| * The default value is the "TreeFolderClosed" symbol in the Assets.swf file. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="folderClosedIcon", type="Class", format="EmbeddedFile", inherit="no")] |
| |
| /** |
| * Indentation for each tree level, in pixels. |
| * |
| * @default 17 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="indentation", type="Number", inherit="no")] |
| |
| /** |
| * Length of an open or close transition, in milliseconds. |
| * |
| * The default value for the Halo theme is <code>250</code>. |
| * The default value for the Spark theme is <code>0</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="openDuration", type="Number", format="Time", inherit="no")] |
| |
| /** |
| * Easing function to control component tweening. |
| * |
| * <p>The default value is <code>undefined</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="openEasingFunction", type="Function", inherit="no")] |
| |
| /** |
| * Color of the background when the user rolls over the link. |
| * |
| * The default value for the Halo theme is <code>0xB2E1FF</code>. |
| * The default value for the Spark theme is <code>0xCEDBEF</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="rollOverColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Color of the background when the user selects the link. |
| * |
| * The default value for the Halo theme is <code>0x7FCEFF</code>. |
| * The default value for the Spark theme is <code>0xA8C6EE</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="selectionColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Specifies the disabled color of a list item. |
| * |
| * @default 0xDDDDDD |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="selectionDisabledColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Reference to an <code>easingFunction</code> function used for controlling programmatic tweening. |
| * |
| * <p>The default value is <code>undefined</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="selectionEasingFunction", type="Function", inherit="no")] |
| |
| /** |
| * Color of the text when the user rolls over a row. |
| * |
| * The default value for the Halo theme is <code>0x2B333C</code>. |
| * The default value for the Spark theme is <code>0x000000</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Color of the text when the user selects a row. |
| * |
| * The default value for the Halo theme is <code>0x2B333C</code>. |
| * The default value for the Spark theme is <code>0x000000</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [AccessibilityClass(implementation="mx.accessibility.TreeAccImpl")] |
| |
| [DefaultBindingProperty(destination="dataProvider")] |
| |
| [DefaultProperty("dataProvider")] |
| |
| [DefaultTriggerEvent("change")] |
| |
| [IconFile("Tree.png")] |
| |
| [RequiresDataBinding(true)] |
| |
| /** |
| * The Tree control lets a user view hierarchical data arranged as an expandable tree. |
| * Each item in a tree can be a leaf or a branch. |
| * A leaf item is an end point in the tree. |
| * A branch item can contain leaf or branch items, or it can be empty. |
| * |
| * <p>By default, a leaf is represented by a text label next to a file icon. |
| * A branch is represented by a text label next to a folder icon, with a |
| * disclosure triangle that a user can open to expose children.</p> |
| * |
| * <p>The Tree class uses an ITreeDataDescriptor or ITreeDataDescriptor2 object to parse and |
| * manipulate the data provider. |
| * The default tree data descriptor, an object of the DefaultDataDescriptor class, |
| * supports XML and Object classes; an Object class data provider must have all children |
| * in <code>children</code> fields. |
| * </p> |
| * |
| * <p>The Tree control has the following default sizing |
| * characteristics:</p> |
| * <table class="innertable"> |
| * <tr> |
| * <th>Characteristic</th> |
| * <th>Description</th> |
| * </tr> |
| * <tr> |
| * <td>Default size</td> |
| * <td>Wide enough to accommodate the icon, label, and |
| * expansion triangle, if any, of the widest node in the |
| * first 7 displayed (uncollapsed) rows, and seven rows |
| * high, where each row is 20 pixels in height. If a |
| * scroll bar is required, the width of the scroll bar is |
| * not included in the width calculations.</td> |
| * </tr> |
| * <tr> |
| * <td>Minimum size</td> |
| * <td>0 pixels.</td> |
| * </tr> |
| * <tr> |
| * <td>Maximum size</td> |
| * <td>5000 by 5000.</td> |
| * </tr> |
| * </table> |
| * |
| * @mxml |
| * <p> |
| * The <mx:Tree> tag inherits all the tag attributes of its superclass, and |
| * adds the following tag attributes: |
| * </p> |
| * <pre> |
| * <mx:Tree |
| * <b>Properties</b> |
| * dataDescriptor="<i>Instance of DefaultDataDescriptor</i>" |
| * dataProvider="null" |
| * dragMoveEnabled="true|false" |
| * firstVisibleItem="<i>First item in the control</i>" |
| * hasRoot="false|true" |
| * itemIcons="null" |
| * maxHorizontalScrollPosition="0" |
| * openItems="null" |
| * showRoot="true|false" |
| * |
| * <b>Styles</b> |
| * alternatingItemColors="undefined" |
| * backgroundDisabledColor="0xDDDDDD" |
| * defaultLeafIcon="<i>'TreeNodeIcon' symbol in Assets.swf</i>" |
| * depthColors="undefined" |
| * disclosureClosedIcon="<i>'TreeDisclosureClosed' symbol in Assets.swf</i>" |
| * disclosureOpenIcon="<i>'TreeDisclosureOpen' symbol in Assets.swf</i>" |
| * folderClosedIcon="<i>'TreeFolderClosed' symbol in Assets.swf</i>" |
| * folderOpenIcon="<i>'TreeFolderOpen' symbol in Assets.swf</i>" |
| * indentation="17" |
| * openDuration="250" |
| * openEasingFunction="undefined" |
| * paddingLeft="2" |
| * paddingRight="0" |
| * rollOverColor="0xAADEFF" |
| * selectionColor="0x7FCDFE" |
| * selectionDisabledColor="0xDDDDDD" |
| * selectionEasingFunction="undefined" |
| * textRollOverColor="0x2B333C" |
| * textSelectedColor="0x2B333C" |
| * |
| * <b>Events</b> |
| * change="<i>No default</i>" |
| * itemClose="<i>No default</i>" |
| * itemOpen="<i>No default</i>" |
| * itemOpening="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @see mx.controls.treeClasses.ITreeDataDescriptor |
| * @see mx.controls.treeClasses.ITreeDataDescriptor2 |
| * @see mx.controls.treeClasses.DefaultDataDescriptor |
| * |
| * @includeExample examples/TreeExample.mxml |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class Tree extends List implements IIMESupport |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var IS_NEW_ROW_STYLE:Object = |
| { |
| depthColors: true, |
| indentation: true, |
| disclosureOpenIcon: true, |
| disclosureClosedIcon: true, |
| folderOpenIcon: true, |
| folderClosedIcon: true, |
| defaultLeafIcon: true |
| }; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class mixins |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Placeholder for mixin by TreeAccImpl. |
| */ |
| mx_internal static var createAccessibilityImplementation:Function; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function Tree() |
| { |
| super(); |
| |
| itemRenderer = new ClassFactory(TreeItemRenderer); |
| editorXOffset = 12; |
| editorWidthOffset = -12; |
| |
| addEventListener(TreeEvent.ITEM_OPENING, expandItemHandler, |
| false, EventPriority.DEFAULT_HANDLER); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Item is currently in the process of opening |
| */ |
| private var opening:Boolean; |
| |
| /** |
| * @private |
| * The tween object that animates rows |
| */ |
| private var tween:Object; |
| |
| /** |
| * @private |
| */ |
| private var maskList:Array; |
| |
| /** |
| * @private |
| */ |
| private var _userMaxHorizontalScrollPosition:Number = 0; |
| |
| /** |
| * @private |
| */ |
| private var eventPending:Object; |
| |
| /** |
| * @private |
| */ |
| private var eventAfterTween:Object; |
| |
| /** |
| * @private |
| */ |
| private var oldLength:int = -1; |
| |
| /** |
| * @private |
| */ |
| private var expandedItem:Object; |
| |
| /** |
| * @private |
| */ |
| private var bSelectedItemRemoved:Boolean = false; |
| |
| /** |
| * @private |
| * Used to slow the scrolling down a bit |
| */ |
| private var minScrollInterval:Number = 50; |
| |
| /** |
| * @private |
| */ |
| private var rowNameID:Number = 0; |
| |
| /** |
| * @private |
| */ |
| private var _editable:Boolean = false; |
| |
| /** |
| * @private |
| * Used to block giving focus to editor on focusIn |
| */ |
| private var dontEdit:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var lastUserInteraction:Event; |
| |
| /** |
| * @private |
| * automation delegate access |
| */ |
| mx_internal var _dropData:Object; |
| |
| /** |
| * An object that specifies the icons for the items. |
| * Each entry in the object has a field name that is the item UID |
| * and a value that is an an object with the following format: |
| * <pre> |
| * {iconID: <i>Class</i>, iconID2: <i>Class</i>} |
| * </pre> |
| * The <code>iconID</code> field value is the class of the icon for |
| * a closed or leaf item and the <code>iconID2</code> is the class |
| * of the icon for an open item. |
| * |
| * <p>This property is intended to allow initialization of item icons. |
| * Changes to this array after initialization are not detected |
| * automatically. |
| * Use the <code>setItemIcon()</code> method to change icons dynamically.</p> |
| * |
| * @see #setItemIcon() |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public var itemIcons:Object; |
| |
| /** |
| * @private |
| */ |
| mx_internal var isOpening:Boolean = false; |
| |
| /** |
| * @private |
| * used by opening tween |
| * rowIndex is the row below the row that was picked |
| * and is the first one that will actually change |
| */ |
| private var rowIndex:int; |
| |
| /** |
| * @private |
| * Number of rows that are or will be tweened |
| */ |
| private var rowsTweened:int; |
| |
| /** |
| * @private |
| */ |
| private var rowList:Array; |
| |
| /** |
| * @private |
| */ |
| mx_internal var collectionLength:int; |
| |
| /** |
| * A hook for accessibility |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal var wrappedCollection:ICollectionView; |
| |
| /** |
| * @private |
| */ |
| mx_internal var collectionThrowsIPE:Boolean; |
| |
| /** |
| * @private |
| */ |
| private var haveItemIndices:Boolean; |
| |
| /** |
| * @private |
| */ |
| private var lastTreeSeekPending:TreeSeekPending; |
| |
| /** |
| * @private |
| */ |
| private var bFinishArrowKeySelection:Boolean = false; |
| private var proposedSelectedItem:Object; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // dataProvider |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var dataProviderChanged:Boolean = false; |
| |
| [Bindable("collectionChange")] |
| [Inspectable(category="Data", defaultValue="null")] |
| |
| /** |
| * An object that contains the data to be displayed. |
| * When you assign a value to this property, the Tree class handles |
| * the source data object as follows: |
| * <p> |
| * <ul><li>A String containing valid XML text is converted to an XMLListCollection.</li> |
| * <li>An XMLNode is converted to an XMLListCollection.</li> |
| * <li>An XMLList is converted to an XMLListCollection.</li> |
| * <li>Any object that implements the ICollectionView interface is cast to |
| * an ICollectionView.</li> |
| * <li>An Array is converted to an ArrayCollection.</li> |
| * <li>Any other type object is wrapped in an Array with the object as its sole |
| * entry.</li></ul> |
| * </p> |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override public function set dataProvider(value:Object):void |
| { |
| // in all cases save off the original |
| if (_rootModel) |
| _rootModel.removeEventListener( |
| CollectionEvent.COLLECTION_CHANGE, |
| collectionChangeHandler); |
| |
| // handle strings and xml |
| if (typeof(value)=="string") |
| value = new XML(value); |
| else if (value is XMLNode) |
| value = new XML(XMLNode(value).toString()); |
| else if (value is XMLList) |
| value = new XMLListCollection(value as XMLList); |
| |
| if (value is XML) |
| { |
| _hasRoot = true; |
| var xl:XMLList = new XMLList(); |
| xl += value; |
| _rootModel = new XMLListCollection(xl); |
| } |
| //if already a collection dont make new one |
| else if (value is ICollectionView) |
| { |
| _rootModel = ICollectionView(value); |
| if (_rootModel.length == 1) |
| _hasRoot = true; |
| } |
| else if (value is Array) |
| { |
| _rootModel = new ArrayCollection(value as Array); |
| } |
| //all other types get wrapped in an ArrayCollection |
| else if (value is Object) |
| { |
| _hasRoot = true; |
| // convert to an array containing this one item |
| var tmp:Array = []; |
| tmp.push(value); |
| _rootModel = new ArrayCollection(tmp); |
| } |
| else |
| { |
| _rootModel = new ArrayCollection(); |
| } |
| //flag for processing in commitProps |
| dataProviderChanged = true; |
| invalidateProperties(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function get dataProvider():Object |
| { |
| if (_rootModel) |
| return _rootModel; |
| |
| return null; |
| } |
| |
| //---------------------------------- |
| // maxHorizontalScrollPosition |
| //---------------------------------- |
| |
| /** |
| * The maximum value for the <code>maxHorizontalScrollPosition</code> property for the Tree control. |
| * Unlike the <code>maxHorizontalScrollPosition</code> property |
| * in the List control, this property is modified by the Tree control as |
| * items open and close and as items in the tree otherwise become |
| * visible or are hidden (for example, by scrolling). |
| * |
| * <p>If you set this property to the widest known item in the dataProvider, |
| * the Tree control modifies it so that even if that widest item |
| * is four levels down in the tree, the user can scroll to see it. |
| * As a result, although you read back the same value for the |
| * <code>maxHorizontalScrollPosition</code> property that you set, |
| * it isn't necessarily the actual value used by the Tree control.</p> |
| * |
| * @default 0 |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override public function get maxHorizontalScrollPosition():Number |
| { |
| return _userMaxHorizontalScrollPosition > 0 ? |
| _userMaxHorizontalScrollPosition : |
| super.maxHorizontalScrollPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set maxHorizontalScrollPosition(value:Number):void |
| { |
| _userMaxHorizontalScrollPosition = value; |
| value += getIndent(); |
| super.maxHorizontalScrollPosition = value; |
| } |
| |
| //---------------------------------- |
| // dragMoveEnabled |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the dragMoveEnabled property. |
| * For Tree only, this initializes to true. |
| */ |
| private var _dragMoveEnabled:Boolean = true; |
| |
| [Inspectable(defaultValue="true")] |
| |
| /** |
| * Indicates that items can be moved instead of just copied |
| * from the Tree control as part of a drag-and-drop operation. |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override public function get dragMoveEnabled():Boolean |
| { |
| return _dragMoveEnabled; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set dragMoveEnabled(value:Boolean):void |
| { |
| _dragMoveEnabled = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // firstVisibleItem |
| //---------------------------------- |
| |
| [Bindable("firstVisibleItemChanged")] |
| |
| /** |
| * The item that is currently displayed in the top row of the tree. |
| * Based on how the branches have been opened and closed and scrolled, |
| * the top row might hold, for example, the ninth item in the list of |
| * currently viewable items which in turn represents |
| * some great-grandchild of the root. |
| * Setting this property is analogous to setting the verticalScrollPosition of the List control. |
| * If the item isn't currently viewable, for example, because it |
| * is under a nonexpanded item, setting this property has no effect. |
| * |
| * <p>NOTE: In Flex 1.0 this property was typed as XMLNode although it really was |
| * either an XMLNode or TreeNode. In 2.0, it is now the generic type Object and will |
| * return an object of the same type as the data contained in the dataProvider.</p> |
| * |
| * <p>The default value is the first item in the Tree control.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get firstVisibleItem():Object |
| { |
| if (listItems.length > 0 && listItems[0].length > 0) |
| return listItems[0][0].data; |
| else |
| return null; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set firstVisibleItem(value:Object):void |
| { |
| var pos:int = getItemIndex(value); |
| if (pos < 0) |
| return; |
| |
| verticalScrollPosition = Math.min(maxVerticalScrollPosition, pos); |
| |
| dispatchEvent(new Event("firstVisibleItemChanged")); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // dataDescriptor |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _dataDescriptor:ITreeDataDescriptor = |
| new DefaultDataDescriptor(); |
| |
| [Inspectable(category="Data")] |
| |
| /** |
| * Tree delegates to the data descriptor for information about the data. |
| * This data is then used to parse and move about the data source. |
| * <p>When you specify this property as an attribute in MXML you must |
| * use a reference to the data descriptor, not the string name of the |
| * descriptor. Use the following format for the property:</p> |
| * |
| * <pre><mx:Tree id="tree" dataDescriptor="{new MyCustomTreeDataDescriptor()}"/>></pre> |
| * |
| * <p>Alternatively, you can specify the property in MXML as a nested |
| * subtag, as the following example shows:</p> |
| * |
| * <pre><mx:Tree> |
| * <mx:dataDescriptor> |
| * <myCustomTreeDataDescriptor></pre> |
| * |
| * <p>The default value is an internal instance of the |
| * DefaultDataDescriptor class.</p> |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function set dataDescriptor(value:ITreeDataDescriptor):void |
| { |
| _dataDescriptor = value; |
| } |
| |
| /** |
| * Returns the current ITreeDataDescriptor. |
| * |
| * @default DefaultDataDescriptor |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get dataDescriptor():ITreeDataDescriptor |
| { |
| return ITreeDataDescriptor(_dataDescriptor); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // showRoot |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Storage variable for showRoot flag. |
| */ |
| mx_internal var _showRoot:Boolean = true; |
| |
| /** |
| * @private |
| * Storage variable for changes to showRoot. |
| */ |
| mx_internal var showRootChanged:Boolean = false; |
| |
| /** |
| * @private |
| * Flag to indicate if the model has a root |
| */ |
| mx_internal var _hasRoot:Boolean = false; |
| |
| /** |
| * @private |
| * Storage variable for the original dataProvider |
| */ |
| mx_internal var _rootModel:ICollectionView; |
| |
| [Inspectable(category="Data", enumeration="true,false", defaultValue="false")] |
| |
| /** |
| * Sets the visibility of the root item. |
| * |
| * If the dataProvider data has a root node, and this is set to |
| * <code>false</code>, the Tree control does not display the root item. |
| * Only the decendants of the root item are displayed. |
| * |
| * This flag has no effect on non-rooted dataProviders, such as List and Array. |
| * |
| * @default true |
| * @see #hasRoot |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get showRoot():Boolean |
| { |
| return _showRoot; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set showRoot(value:Boolean):void |
| { |
| if (_showRoot != value) |
| { |
| _showRoot = value; |
| showRootChanged = true; |
| invalidateProperties(); |
| } |
| } |
| |
| /** |
| * Indicates that the current dataProvider has a root item; for example, |
| * a single top node in a hierarchical structure. XML and Object |
| * are examples of types that have a root. Lists and arrays do not. |
| * |
| * @see #showRoot |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get hasRoot():Boolean |
| { |
| return _hasRoot; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // openItems |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Used to hold a list of items that are opened or set opened. |
| */ |
| private var _openItems:Object = {}; |
| |
| /** |
| * @private |
| */ |
| private var openItemsChanged:Boolean = false; |
| |
| /** |
| * The items that have been opened or set opened. |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get openItems():Object |
| { |
| var openItemsArray:Array = []; |
| for each(var item:* in _openItems) |
| { |
| openItemsArray.push(item); |
| } |
| return openItemsArray; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set openItems(value:Object):void |
| { |
| if (value != null) |
| { |
| for (var uid:String in _openItems) |
| delete _openItems[uid]; |
| |
| for each (var item:* in value) |
| { |
| _openItems[itemToUID(item)] = item; |
| } |
| openItemsChanged = true; |
| invalidateProperties(); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function initializeAccessibility():void |
| { |
| if (Tree.createAccessibilityImplementation != null) |
| Tree.createAccessibilityImplementation(this); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| if (showRootChanged) |
| { |
| if (!_hasRoot) |
| showRootChanged = false; |
| } |
| |
| if (dataProviderChanged || showRootChanged || openItemsChanged) |
| { |
| var tmpCollection:ICollectionView; |
| //reset flags |
| |
| dataProviderChanged = false; |
| showRootChanged = false; |
| |
| //we always reset the open and selected items on a dataprovider assignment |
| if (!openItemsChanged) |
| _openItems = {}; |
| |
| openItemsChanged = false; |
| |
| // are we swallowing the root? |
| if (_rootModel && !_showRoot && _hasRoot) |
| { |
| var rootItem:* = _rootModel.createCursor().current; |
| if (rootItem != null && |
| _dataDescriptor.isBranch(rootItem, _rootModel) && |
| _dataDescriptor.hasChildren(rootItem, _rootModel)) |
| { |
| // then get rootItem children |
| tmpCollection = getChildren(rootItem, _rootModel); |
| } |
| } |
| |
| // at this point _rootModel may be null so we dont need to continue |
| if (_rootModel) |
| { |
| //wrap userdata in a TreeCollection and pass that collection to the List |
| super.dataProvider = wrappedCollection = (_dataDescriptor is ITreeDataDescriptor2) ? |
| ITreeDataDescriptor2(_dataDescriptor).getHierarchicalCollectionAdaptor( |
| tmpCollection != null ? tmpCollection : _rootModel, |
| itemToUID, |
| _openItems) : |
| new HierarchicalCollectionView( |
| tmpCollection != null ? tmpCollection : _rootModel, |
| _dataDescriptor, |
| itemToUID, |
| _openItems); |
| |
| |
| // not really a default handler, but we need to be later than the wrapper |
| wrappedCollection.addEventListener(CollectionEvent.COLLECTION_CHANGE, |
| collectionChangeHandler, |
| false, |
| EventPriority.DEFAULT_HANDLER, true); |
| } |
| else |
| { |
| super.dataProvider = null; |
| } |
| } |
| |
| super.commitProperties(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, |
| unscaledHeight:Number):void |
| { |
| // Kill any animation before resizing; |
| // tween is null if there is no Tween underway. |
| if (tween) |
| tween.endTween(); |
| |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| //update collection length |
| if (collection) |
| collectionLength = collection.length; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function styleChanged(styleProp:String):void |
| { |
| if (styleProp == null || |
| styleProp == "styleName" || |
| IS_NEW_ROW_STYLE[styleProp]) |
| { |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| } |
| |
| super.styleChanged(styleProp); |
| } |
| |
| /** |
| * @private |
| * Position indicator bar that shows where an item will be placed in the list. |
| */ |
| override public function showDropFeedback(event:DragEvent):void |
| { |
| super.showDropFeedback(event); |
| // Adjust for indent |
| var vm:EdgeMetrics = viewMetrics; |
| var offset:int = 0; |
| updateDropData(event); |
| var indent:int = 0; |
| var depth:int; |
| if (_dropData.parent) |
| { |
| offset = getItemIndex(iterator.current); |
| depth = getItemDepth(_dropData.parent, Math.abs(offset - getItemIndex(_dropData.parent))); |
| indent = (depth + 1) * getStyle("indentation"); |
| } |
| else |
| { |
| indent = getStyle("indentation"); |
| } |
| if (indent < 0) |
| indent = 0; |
| //position drop indicator |
| dropIndicator.width = listContent.width - indent; |
| dropIndicator.x = indent + vm.left + 2; |
| if (_dropData.emptyFolder) |
| { |
| dropIndicator.y += _dropData.rowHeight / 2; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override public function calculateDropIndex(event:DragEvent = null):int |
| { |
| if (event) |
| updateDropData(event); |
| |
| return _dropData.rowIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function addDragData(ds:Object):void // actually a DragSource |
| { |
| ds.addHandler(collapseSelectedItems, "treeItems"); |
| } |
| |
| /** |
| * @private |
| * |
| * see ListBase.as |
| */ |
| override mx_internal function addClipMask(layoutChanged:Boolean):void |
| { |
| var vm:EdgeMetrics = viewMetrics; |
| |
| if (horizontalScrollBar && horizontalScrollBar.visible) |
| vm.bottom -= horizontalScrollBar.minHeight; |
| |
| if (verticalScrollBar && verticalScrollBar.visible) |
| vm.right -= verticalScrollBar.minWidth; |
| |
| listContent.scrollRect = new Rectangle( |
| 0, 0, |
| unscaledWidth - vm.left - vm.right, |
| listContent.heightExcludingOffsets); |
| } |
| |
| /** |
| * @private |
| * |
| * Undo the effects of the addClipMask function (above) |
| */ |
| override mx_internal function removeClipMask():void |
| { |
| } |
| |
| /** |
| * Creates a new TreeListData instance and populates the fields based on |
| * the input data provider item. |
| * |
| * @param data The data provider item used to populate the ListData. |
| * @param uid The UID for the item. |
| * @param rowNum The index of the item in the data provider. |
| * |
| * @return A newly constructed ListData object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override protected function makeListData(data:Object, uid:String, |
| rowNum:int):BaseListData |
| { |
| var treeListData:TreeListData = new TreeListData(itemToLabel(data), uid, this, rowNum); |
| initListData(data, treeListData); |
| return treeListData; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function itemToIcon(item:Object):Class |
| { |
| if (item == null) |
| { |
| return null; |
| } |
| |
| var icon:*; |
| var open:Boolean = isItemOpen(item); |
| var branch:Boolean = isBranch(item); |
| var uid:String = itemToUID(item); |
| |
| //first lets check the component |
| var iconClass:Class = |
| itemIcons && itemIcons[uid] ? |
| itemIcons[uid][open ? "iconID2" : "iconID"] : |
| null; |
| |
| if (iconClass) |
| { |
| return iconClass; |
| } |
| else if (iconFunction != null) |
| { |
| return iconFunction(item) |
| } |
| else if (branch) |
| { |
| return getStyle(open ? "folderOpenIcon" : "folderClosedIcon"); |
| } |
| else |
| //let's check the item itself |
| { |
| if (item is XML) |
| { |
| try |
| { |
| if (item[iconField].length() != 0) |
| icon = String(item[iconField]); |
| } |
| catch(e:Error) |
| { |
| } |
| } |
| else if (item is Object) |
| { |
| try |
| { |
| if (iconField && item[iconField]) |
| icon = item[iconField]; |
| else if (item.icon) |
| icon = item.icon; |
| } |
| catch(e:Error) |
| { |
| } |
| } |
| } |
| |
| //set default leaf icon if nothing else was found |
| if (icon == null) |
| icon = getStyle("defaultLeafIcon"); |
| |
| //convert to the correct type and class |
| if (icon is Class) |
| { |
| return icon; |
| } |
| else if (icon is String) |
| { |
| iconClass = Class(systemManager.getDefinitionByName(String(icon))); |
| if (iconClass) |
| return iconClass; |
| |
| return document[icon]; |
| } |
| else |
| { |
| return Class(icon); |
| } |
| |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function drawRowBackgrounds():void |
| { |
| var rowBGs:Sprite = Sprite(listContent.getChildByName("rowBGs")); |
| if (!rowBGs) |
| { |
| rowBGs = new FlexSprite(); |
| rowBGs.name = "rowBGs"; |
| rowBGs.mouseEnabled = false; |
| listContent.addChildAt(rowBGs, 0); |
| } |
| |
| var color:*; |
| var colors:Array; |
| var depthColors:Boolean = false; |
| |
| colors = getStyle("depthColors"); |
| if (colors) |
| { |
| depthColors = true; |
| } |
| else |
| { |
| var colorsStyle:Object = getStyle("alternatingItemColors"); |
| |
| if (colorsStyle) |
| colors = (colorsStyle is Array) ? (colorsStyle as Array) : [colorsStyle]; |
| } |
| color = getStyle("backgroundColor"); |
| if (color === undefined) |
| color = 0xFFFFFF; |
| if (!colors || colors.length == 0) |
| { |
| while (rowBGs.numChildren > n) |
| { |
| rowBGs.removeChildAt(rowBGs.numChildren - 1); |
| } |
| return; |
| } |
| |
| styleManager.getColorNames(colors); |
| |
| var curRow:int = 0; |
| var actualRow:int = verticalScrollPosition; |
| var i:int = 0; |
| var n:int = listItems.length; |
| |
| while (curRow < n) |
| { |
| if (depthColors) |
| { |
| try |
| { |
| if (listItems[curRow][0]) |
| { |
| var d:int = getItemDepth(listItems[curRow][0].data, curRow); |
| var rowColor:uint = colors[d-1] ? colors[d - 1] : uint(color); |
| drawRowBackground(rowBGs, i++, rowInfo[curRow].y, rowInfo[curRow].height, rowColor, actualRow); |
| } |
| else |
| { |
| drawRowBackground(rowBGs, i++, rowInfo[curRow].y, rowInfo[curRow].height, uint(color), actualRow); |
| } |
| } |
| catch(e:Error) |
| { |
| //trace("[Tree] caught exception in drawRowBackground"); |
| } |
| } |
| else |
| { |
| drawRowBackground(rowBGs, i++, rowInfo[curRow].y, rowInfo[curRow].height, colors[actualRow % colors.length], actualRow); |
| } |
| curRow++; |
| actualRow++; |
| } |
| while (rowBGs.numChildren > n) |
| { |
| rowBGs.removeChildAt(rowBGs.numChildren - 1); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Sets the associated icon for the item. Calling this method overrides the |
| * <code>iconField</code> and <code>iconFunction</code> properties for |
| * this item if it is a leaf item. Branch items don't use the |
| * <code>iconField</code> and <code>iconFunction</code> properties. |
| * They use the <code>folderOpenIcon</code> and <code>folderClosedIcon</code> properties. |
| * |
| * @param item Item to affect. |
| * @param iconID Linkage ID for the closed (or leaf) icon. |
| * @param iconID2 Linkage ID for the open icon. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function setItemIcon(item:Object, iconID:Class, iconID2:Class):void |
| { |
| if (!itemIcons) |
| itemIcons = {}; |
| |
| if (!iconID2) |
| iconID2 = iconID; |
| |
| itemIcons[itemToUID(item)] = { iconID: iconID, iconID2: iconID2 }; |
| |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| * Returns <code>true</code> if the specified item is a branch item. The Tree |
| * control delegates to the IDataDescriptor to determine if an item is a branch. |
| * @param item Item to inspect. |
| * @return True if a branch, false if not. |
| * |
| */ |
| private function isBranch(item:Object):Boolean |
| { |
| if (item != null) |
| return _dataDescriptor.isBranch(item, iterator.view); |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| * wraps calls to the descriptor |
| * mx_internal for automation delegate access |
| */ |
| mx_internal function getChildren(item:Object, view:Object):ICollectionView |
| { |
| //get the collection of children |
| var children:ICollectionView = _dataDescriptor.getChildren(item, view); |
| return children; |
| } |
| |
| /** |
| * Determines the number of parents from root to the specified item. |
| * Method starts with the Cursor.current item and will seek forward |
| * to a specific offset, returning the cursor to its original position. |
| * |
| * @private |
| */ |
| mx_internal function getItemDepth(item:Object, offset:int):int |
| { |
| //first test for a match (most cases) |
| if (!collection) |
| return 0; |
| |
| if (!iterator) |
| listContent.iterator = collection.createCursor(); |
| |
| if (iterator.current == item) |
| return getCurrentCursorDepth(); |
| |
| //otherwise seek to offset and get the depth |
| var bookmark:CursorBookmark = iterator.bookmark; |
| iterator.seek(bookmark, offset); |
| var depth:int = getCurrentCursorDepth(); |
| //put the cursor back |
| iterator.seek(bookmark, 0); |
| return depth; |
| } |
| |
| /** |
| * @private |
| * Utility method to get the depth of the current item from the cursor. |
| */ |
| private function getCurrentCursorDepth():int //private |
| { |
| if (_dataDescriptor is ITreeDataDescriptor2) |
| return ITreeDataDescriptor2(_dataDescriptor).getNodeDepth(iterator.current, iterator, _rootModel); |
| |
| return HierarchicalViewCursor(iterator).currentDepth; |
| } |
| |
| /** |
| * @private |
| * Gets the number of visible items from a starting item. |
| */ |
| private function getVisibleChildrenCount(item:Object):int |
| { |
| var count:int = 0; |
| |
| if (item == null) |
| return count; |
| |
| var uid:String = itemToUID(item); |
| var children:Object; |
| if (_openItems[uid] && |
| _dataDescriptor.isBranch(item, iterator.view) && |
| _dataDescriptor.hasChildren(item, iterator.view)) |
| { |
| children = getChildren(item, iterator.view); |
| } |
| if (children != null) |
| { |
| var cursor:IViewCursor = children.createCursor(); |
| while (!cursor.afterLast) |
| { |
| count++; |
| uid = itemToUID(cursor.current); |
| if (_openItems[uid]) |
| count += getVisibleChildrenCount(cursor.current); |
| cursor.moveNext(); |
| } |
| } |
| return count; |
| } |
| |
| /** |
| * Returns <code>true</code> if the specified item branch is open (is showing its children). |
| * @param item Item to inspect. |
| * @return True if open, false if not. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function isItemOpen(item:Object):Boolean |
| { |
| var uid:String = itemToUID(item); |
| return _openItems[uid] != null; |
| } |
| |
| /** |
| * @private |
| */ |
| private function makeMask():DisplayObject |
| { |
| var tmpMask:Shape = new FlexShape(); |
| tmpMask.name = "mask"; |
| |
| var g:Graphics = tmpMask.graphics; |
| g.beginFill(0xFFFFFF); |
| g.moveTo(0,0); |
| g.lineTo(0,10); |
| g.lineTo(10,10); |
| g.lineTo(10,0); |
| g.lineTo(0,0); |
| g.endFill(); |
| |
| listContent.addChild(tmpMask); |
| return tmpMask; |
| } |
| |
| /** |
| * Opens or closes a branch item. |
| * When a branch item opens, it restores the open and closed states |
| * of its child branches if they were already opened. |
| * |
| * If you set <code>dataProvider</code> and then immediately call |
| * <code>expandItem()</code> you may not see the correct behavior. |
| * You should either wait for the component to validate |
| * or call <code>validateNow()</code>. |
| * |
| * @param item Item to affect. |
| * |
| * @param open Specify <code>true</code> to open, <code>false</code> to close. |
| * |
| * @param animate Specify <code>true</code> to animate the transition. (Note: |
| * If a branch has over 20 children, it does not animate the first time it opens, |
| * for performance reasons.) |
| * |
| * @param dispatchEvent Controls whether the tree fires an <code>open</code> event |
| * after the open animation is complete. |
| * |
| * @param cause The event, if any, that initiated the item open action. |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function expandItem(item:Object, open:Boolean, |
| animate:Boolean = false, |
| dispatchEvent:Boolean = false, |
| cause:Event = null):void |
| { |
| //if the iterator is null, that indicates we have not been |
| //validated yet, so we will not continue. |
| if (iterator == null) |
| return; |
| |
| if (cause) |
| lastUserInteraction = cause; |
| |
| expandedItem = item; |
| |
| listContent.allowItemSizeChangeNotification = false; |
| |
| var i:int; |
| var bSelected:Boolean = false; |
| var bHighlight:Boolean = false; |
| var bCaret:Boolean = false; |
| var newRowIndex:int; |
| var rowData:BaseListData; |
| |
| var tmpMask:DisplayObject; |
| |
| var uid:String = itemToUID(item); |
| // if this can't be opened, or shouldn't be, don't! |
| if (!isBranch(item) || (isItemOpen(item)==open) || isOpening) |
| return; |
| |
| if (itemEditorInstance) |
| endEdit(ListEventReason.OTHER); |
| |
| //we'll use the last recorded length not necessarily the current one |
| oldLength = collectionLength; |
| |
| var bookmark:CursorBookmark = iterator.bookmark; |
| |
| // sent to update the length in the collection |
| var event:CollectionEvent = new CollectionEvent( |
| CollectionEvent.COLLECTION_CHANGE, |
| false, |
| true, |
| CollectionEventKind.EXPAND); |
| event.items = [item]; |
| |
| // update the list of _openItems |
| if (open) |
| { |
| _openItems[uid] = item; |
| collection.dispatchEvent(event); |
| rowsTweened = Math.abs(oldLength - collection.length); |
| } |
| else |
| { |
| delete _openItems[uid]; |
| collection.dispatchEvent(event); |
| //how many rows to move? |
| rowsTweened = Math.abs(oldLength - collection.length); |
| } |
| |
| // will it affect the displayList? |
| if (isItemVisible(item)) |
| { |
| // is the item on screen? |
| if (visibleData[uid]) |
| { |
| //find the rowindex of the row after the open thats opening/closing |
| var n:int = listItems.length; |
| for (rowIndex = 0; rowIndex < n; rowIndex++) |
| { |
| if (rowInfo[rowIndex].uid == uid) |
| { |
| rowIndex++; |
| // rowIndex is set to the row after the one opening/closing |
| // because that is the first row to change |
| break; |
| } |
| } |
| } |
| } |
| //if we're opening or closing a node that is not visible, |
| //we still need to dispatch the correct collectionChange events |
| //so that scroll position and selection properties update correctly. |
| else |
| { |
| var eventArr:Array = open ? |
| buildUpCollectionEvents(true) : buildUpCollectionEvents(false); |
| for (i = 0; i < eventArr.length; i++) |
| { |
| collection.dispatchEvent(eventArr[i]); |
| } |
| return; |
| } |
| |
| var rC:int = listItems.length; |
| var tmpRowInfo:Object; |
| var row:Array; |
| // we will cap this with as many rows as can be displayed later |
| var rowsToMove:int = rowsTweened; |
| var dur:Number = getStyle("openDuration"); |
| if (animate && rowIndex < rC && rowsToMove > 0 && rowsToMove < 20 && dur != 0) |
| { |
| // Kill any previous animation. tween is undefined if there is no Tween underway. |
| if (tween) |
| tween.endTween(); |
| |
| var renderer:IListItemRenderer = listItems[rowIndex - 1][0]; |
| if (renderer is IDropInListItemRenderer) |
| { |
| var di:IDropInListItemRenderer = IDropInListItemRenderer(renderer); |
| var treeListData:TreeListData = TreeListData(di.listData); |
| treeListData = TreeListData(makeListData(treeListData.item, |
| treeListData.uid, treeListData.rowIndex)); |
| di.listData = treeListData; |
| renderer.data = renderer.data; // this forces eval of new listData |
| } |
| |
| // animate the opening |
| opening = open; |
| isOpening = true; |
| |
| maskList = []; |
| rowList = []; |
| |
| var xx:Number = getStyle("paddingLeft") - horizontalScrollPosition; |
| var ww:Number = renderer.width; |
| var yy:Number = 0; |
| var hh:Number; |
| |
| // don't tween anymore than the amount of space we have |
| var delta:int = rowIndex; |
| var maxDist:Number = 0; |
| if (open) |
| { |
| newRowIndex = rowIndex; |
| // don't tween anymore than the amount of space we have |
| maxDist = listContent.height - rowInfo[rowIndex].y; |
| iterator.seek(CursorBookmark.CURRENT, delta); |
| var data:Object; |
| |
| // create the rows now so we know how much to move |
| for (i = 0; i < rowsToMove && yy < maxDist; i++) |
| { |
| data = iterator.current; |
| |
| if (freeItemRenderers.length) |
| { |
| renderer = freeItemRenderers.pop(); |
| } |
| else |
| { |
| renderer = createItemRenderer(data); |
| renderer.owner = this; |
| renderer.styleName = listContent; |
| listContent.addChild(DisplayObject(renderer)); |
| } |
| uid = itemToUID(data); |
| rowData = makeListData(data, uid, rowIndex + i); |
| rowMap[renderer.name] = rowData; |
| if (renderer is IDropInListItemRenderer) |
| IDropInListItemRenderer(renderer).listData = data ? rowData : null; |
| renderer.data = data; |
| renderer.enabled = enabled; |
| if (data) |
| { |
| visibleData[uid] = renderer; |
| renderer.visible = true; |
| } |
| else |
| { |
| renderer.visible = false; |
| } |
| renderer.explicitWidth = ww; |
| |
| //from list |
| if ((renderer is IInvalidating) && |
| (wordWrapChanged || |
| variableRowHeight)) |
| IInvalidating(renderer).invalidateSize(); |
| |
| UIComponentGlobals.layoutManager.validateClient(renderer, true); |
| |
| hh = Math.ceil(variableRowHeight ? |
| renderer.getExplicitOrMeasuredHeight() + |
| cachedPaddingTop + cachedPaddingBottom : |
| rowHeight); |
| var rh:Number = renderer.getExplicitOrMeasuredHeight(); |
| renderer.setActualSize(ww, variableRowHeight ? rh : rowHeight - cachedPaddingTop - cachedPaddingBottom); |
| renderer.move(xx, yy + cachedPaddingTop); |
| bSelected = selectedData[uid] != null; |
| bHighlight = highlightUID == uid; |
| bCaret = caretUID == uid; |
| tmpRowInfo = new ListRowInfo(yy, hh, uid, data); |
| if (data) |
| drawItem(renderer, bSelected, bHighlight, bCaret); |
| yy += hh; |
| |
| rowInfo.splice(rowIndex + i, 0, tmpRowInfo); |
| row = []; |
| row.push(renderer); |
| listItems.splice(rowIndex + i, 0, row); |
| |
| // due to issues in HCV with paging |
| // don't go looking for something unless you really |
| // need it |
| if (i < rowsToMove - 1) |
| { |
| try |
| { |
| iterator.moveNext(); |
| } |
| catch(e:ItemPendingError) |
| { |
| rowsToMove = i + 1; |
| break; |
| } |
| } |
| } |
| rowsTweened = i; |
| // position the new rows; |
| var referenceRowInfo:ListRowInfo = rowInfo[rowIndex + rowsTweened]; |
| for (i = 0; i < rowsTweened; i++) |
| { |
| renderer = listItems[rowIndex + i][0]; |
| renderer.move(renderer.x, renderer.y - (yy - referenceRowInfo.y)); |
| rowInfo[rowIndex + i].y -= yy - referenceRowInfo.y; |
| tmpMask = makeMask(); |
| tmpMask.x = xx; |
| tmpMask.y = referenceRowInfo.y; |
| tmpMask.width = ww; |
| tmpMask.height = yy; |
| listItems[rowIndex + i][0].mask = tmpMask; |
| } |
| } |
| else // closing up rows |
| { |
| var more:Boolean = true; |
| var valid:Boolean = true; |
| var startY:Number = yy = rowInfo[listItems.length - 1].y + rowInfo[listItems.length - 1].height; |
| |
| // figure out how much space was consumed by the rows that are going away |
| for (i = rowIndex; i < rowIndex + rowsToMove && i < rC; i++) |
| { |
| maxDist += rowInfo[i].height; |
| // retain a reference to the rows going away |
| rowList.push({item: listItems[i][0]}); |
| tmpMask = makeMask(); |
| tmpMask.x = xx; |
| tmpMask.y = listItems[rowIndex][0].y; |
| tmpMask.width = ww; |
| tmpMask.height = maxDist; |
| listItems[i][0].mask = tmpMask; |
| } |
| rowsToMove = i - rowIndex; |
| // remove the rows going away |
| rowInfo.splice(rowIndex, rowsToMove); |
| listItems.splice(rowIndex, rowsToMove); |
| |
| iterator.seek(CursorBookmark.CURRENT, listItems.length); |
| more = (iterator != null && !iterator.afterLast && iteratorValid); |
| |
| maxDist += yy; |
| // create the rows now so we know how much to move |
| for (i = 0; i < rowsToMove && yy < maxDist; i++) |
| { |
| //reset item specific values |
| uid = null; |
| data = null; |
| renderer = null; |
| |
| valid = more; |
| data = more ? iterator.current : null; |
| |
| if (valid) |
| { |
| if (freeItemRenderers.length) |
| { |
| renderer = freeItemRenderers.pop(); |
| } |
| else |
| { |
| renderer = createItemRenderer(data); |
| renderer.owner = this; |
| renderer.styleName = listContent; |
| listContent.addChild(DisplayObject(renderer)); |
| } |
| uid = itemToUID(data); |
| rowData = makeListData(data, uid, rC - rowsToMove + i); |
| rowMap[renderer.name] = rowData; |
| if (renderer is IDropInListItemRenderer) |
| IDropInListItemRenderer(renderer).listData = data ? rowData : null; |
| renderer.data = data; |
| renderer.enabled = enabled; |
| if (data) |
| { |
| visibleData[uid] = renderer; |
| renderer.visible = true; |
| } |
| else |
| renderer.visible = false; |
| |
| renderer.explicitWidth = ww; |
| |
| //from list |
| if ((renderer is IInvalidating) && |
| (wordWrapChanged || |
| variableRowHeight)) |
| IInvalidating(renderer).invalidateSize(); |
| |
| UIComponentGlobals.layoutManager.validateClient(renderer, true); |
| |
| hh = Math.ceil(variableRowHeight ? |
| renderer.getExplicitOrMeasuredHeight() + |
| cachedPaddingTop + cachedPaddingBottom : |
| rowHeight); |
| rh = renderer.getExplicitOrMeasuredHeight(); |
| renderer.setActualSize(ww, variableRowHeight ? rh : rowHeight - cachedPaddingTop - cachedPaddingBottom); |
| renderer.move(xx, yy + cachedPaddingTop); |
| } |
| else |
| { |
| // if we've run out of data, we dont make renderers |
| // and we inherit the previous row's height or rowHeight |
| // if it is the first row. |
| // EXCEPT when variable row height is on since the row |
| // above us might be bigger then we are. So we'll get |
| // this row out of the rowList and check it. |
| if (!variableRowHeight) |
| { |
| hh = rowIndex + i > 0 ? rowInfo[rowIndex + i - 1].height : rowHeight; |
| } |
| else |
| { |
| if (rowList[i]) |
| { |
| hh = Math.ceil(rowList[i].item.getExplicitOrMeasuredHeight() + |
| cachedPaddingTop + cachedPaddingBottom); |
| } |
| else |
| { |
| //default |
| hh = rowHeight; |
| } |
| } |
| } |
| bSelected = selectedData[uid] != null; |
| bHighlight = highlightUID == uid; |
| bCaret = caretUID == uid; |
| tmpRowInfo = new ListRowInfo(yy, hh, uid, data); |
| rowInfo.push(tmpRowInfo); |
| if (data) |
| { |
| drawItem(renderer, bSelected, bHighlight, bCaret); |
| } |
| yy += hh; |
| |
| if (valid) |
| { |
| row = []; |
| row.push(renderer); |
| listItems.push(row); |
| } |
| else |
| { |
| listItems.push([]); |
| } |
| |
| if (more) |
| { |
| try |
| { |
| more = iterator.moveNext(); |
| } |
| catch (e:ItemPendingError) |
| { |
| more = false; |
| } |
| } |
| } |
| |
| //make indicator masks |
| var maskY:Number = rowList[0].item.y - getStyle("paddingTop"); |
| var maskX:Number = rowList[0].item.x - getStyle("paddingLeft"); |
| for (i = 0; i < rowList.length; i++) |
| { |
| var indicator:Object = selectionIndicators[itemToUID(rowList[i].item.data)]; |
| if (indicator) |
| { |
| tmpMask = makeMask(); |
| tmpMask.x = maskX; |
| tmpMask.y = maskY; |
| tmpMask.width = rowList[i].item.width + |
| getStyle("paddingLeft") + |
| getStyle("paddingRight"); |
| tmpMask.height = rowList[i].item.y + |
| rowList[i].item.height + |
| getStyle("paddingTop") + |
| getStyle("paddingBottom") - |
| maskY; |
| selectionIndicators[itemToUID(rowList[i].item.data)].mask = tmpMask; |
| } |
| } |
| } |
| // restore the iterator |
| iterator.seek(bookmark, 0); |
| |
| rC = rowList.length; |
| for (i = 0; i < rC; i++) |
| { |
| rowList[i].itemOldY = rowList[i].item.y; |
| } |
| rC = listItems.length; |
| for (i = rowIndex; i < rC; i++) |
| { |
| if (listItems[i].length) |
| { |
| rowInfo[i].itemOldY = listItems[i][0].y; |
| } |
| rowInfo[i].oldY = rowInfo[i].y; |
| } |
| // slow down the tween if there's lots of rows to tween |
| dur = dur * Math.max(rowsToMove / 5, 1); |
| |
| if (dispatchEvent) |
| eventAfterTween = item; |
| |
| tween = new Tween(this, 0, (open) ? yy : startY - yy, dur, 5); |
| var oE:Function = getStyle("openEasingFunction") as Function; |
| if (oE != null) |
| tween.easingFunction = oE; |
| |
| // Block all layout, responses from web service, and other background |
| // processing until the tween finishes executing. |
| UIComponent.suspendBackgroundProcessing(); |
| // force drawing in case there's new rows |
| UIComponentGlobals.layoutManager.validateNow(); |
| } |
| else |
| { |
| // not to be animated |
| if (dispatchEvent) |
| { |
| dispatchTreeEvent(open ? TreeEvent.ITEM_OPEN : TreeEvent.ITEM_CLOSE, |
| item, |
| visibleData[itemToUID(item)], |
| lastUserInteraction); |
| lastUserInteraction = null; |
| } |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| } |
| |
| // If we're wordwrapping, no need to adjust maxHorizontalScrollPosition. |
| // Also check if _userMaxHorizontalScrollPosition is greater than 0. |
| if (!wordWrap && initialized) |
| { |
| super.maxHorizontalScrollPosition = |
| _userMaxHorizontalScrollPosition > 0 ? |
| _userMaxHorizontalScrollPosition + getIndent() : |
| super.maxHorizontalScrollPosition; |
| } |
| //restore ItemSizeChangeNotification flag |
| listContent.allowItemSizeChangeNotification = variableRowHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function onTweenUpdate(value:Object):void |
| { |
| var renderer:IFlexDisplayObject; |
| var n:int; |
| var i:int; |
| var deltaY:Number; |
| var lastY:Number; |
| |
| n = listItems.length; |
| var s:Sprite; |
| for (i = rowIndex; i < n; i++) |
| { |
| //move items that are staying |
| if (listItems[i].length) |
| { |
| renderer = IFlexDisplayObject(listItems[i][0]); |
| lastY = renderer.y; |
| renderer.move(renderer.x, rowInfo[i].itemOldY + value); |
| deltaY = renderer.y - lastY; |
| } |
| //move selection graphics of the items that are staying visible |
| s = selectionIndicators[rowInfo[i].uid]; |
| |
| rowInfo[i].y += deltaY; |
| |
| if (s) |
| { |
| s.y += deltaY; |
| } |
| } |
| //move the items that are going away. |
| n = rowList.length; |
| for (i = 0; i < n; i++) |
| { |
| s = null; |
| renderer = IFlexDisplayObject(rowList[i].item); |
| if (rowMap[renderer.name] != null) |
| { |
| s = selectionIndicators[BaseListData(rowMap[renderer.name]).uid]; |
| } |
| lastY = renderer.y; |
| renderer.move(renderer.x, rowList[i].itemOldY + value); |
| deltaY = renderer.y - lastY; |
| //move selection graphic for items that are going away |
| if (s) |
| { |
| s.y += deltaY; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function onTweenEnd(value:Object):void |
| { |
| UIComponent.resumeBackgroundProcessing(); |
| |
| onTweenUpdate(value); |
| |
| var i:int; |
| var renderer:*; |
| var dilir:IDropInListItemRenderer; |
| var rC:int = listItems.length; |
| var itemUID:*; |
| var indicator:Object; |
| |
| isOpening = false; |
| |
| //dispatch collectionChange ADD or REMOVE events that correlate |
| //to the nodes that were expanded or collapsed |
| if (collection) |
| { |
| var eventArr:Array = opening ? |
| buildUpCollectionEvents(true) : buildUpCollectionEvents(false); |
| for (i = 0; i < eventArr.length; i++) |
| { |
| collection.dispatchEvent(eventArr[i]); |
| } |
| } |
| |
| if (opening) |
| { |
| var firstDeletedRow:int = -1; |
| for (i = rowIndex; i < rC; i++) |
| { |
| if (listItems[i].length) |
| { |
| renderer = listItems[i][0]; |
| var mask:DisplayObject = renderer.mask; |
| if (mask) |
| { |
| listContent.removeChild(mask); |
| renderer.mask = null; |
| } |
| rowMap[renderer.name].rowIndex = i; |
| if (renderer is IDropInListItemRenderer) |
| { |
| dilir = IDropInListItemRenderer(renderer); |
| if (dilir.listData) |
| { |
| dilir.listData.rowIndex = i; |
| dilir.listData = dilir.listData; // call the setter |
| } |
| } |
| if (renderer.y > listContent.height) |
| { |
| addToFreeItemRenderers(renderer); |
| itemUID = itemToUID(renderer.data); |
| if (selectionIndicators[itemUID]) |
| { |
| //remove indicators mask |
| indicator = selectionIndicators[itemUID]; |
| if (indicator) |
| { |
| mask = indicator.mask; |
| if (mask) |
| { |
| listContent.removeChild(mask); |
| indicator.mask = null; |
| } |
| } |
| removeIndicators(itemUID); |
| } |
| delete rowMap[renderer.name]; |
| if (firstDeletedRow < 0) |
| firstDeletedRow = i; |
| } |
| } |
| else |
| { |
| if (rowInfo[i].y >= listContent.height) |
| { |
| if (firstDeletedRow < 0) |
| firstDeletedRow = i; |
| } |
| } |
| } |
| if (firstDeletedRow >= 0) |
| { |
| rowInfo.splice(firstDeletedRow); |
| listItems.splice(firstDeletedRow); |
| } |
| } |
| else //closing |
| { |
| for (i = 0; i < rowList.length; i++) |
| { |
| mask = rowList[i].item.mask; |
| if (mask) |
| { |
| listContent.removeChild(mask); |
| rowList[i].item.mask = null; |
| } |
| addToFreeItemRenderers(rowList[i].item); |
| //kill graphic and graphic mask if necessary |
| itemUID = itemToUID(rowList[i].item.data); |
| if (selectionIndicators[itemUID]) |
| { |
| //remove indicators mask |
| indicator = selectionIndicators[itemUID]; |
| if (indicator) |
| { |
| mask = indicator.mask; |
| if (mask) |
| { |
| listContent.removeChild(mask); |
| indicator.mask = null; |
| } |
| } |
| removeIndicators(itemUID); |
| } |
| delete rowMap[rowList[i].item.name]; |
| } |
| for (i = rowIndex; i < rC; i++) |
| { |
| if (listItems[i].length) |
| { |
| renderer = listItems[i][0]; |
| rowMap[renderer.name].rowIndex = i; |
| if (renderer is IDropInListItemRenderer) |
| { |
| dilir = IDropInListItemRenderer(renderer); |
| if (dilir.listData) |
| { |
| dilir.listData.rowIndex = i; |
| dilir.listData = dilir.listData; // call the setter |
| } |
| } |
| } |
| } |
| } |
| |
| //should we dispatch a tree event? |
| if (eventAfterTween) |
| { |
| dispatchTreeEvent((isItemOpen(eventAfterTween) |
| ? TreeEvent.ITEM_OPEN |
| : TreeEvent.ITEM_CLOSE), |
| eventAfterTween, |
| visibleData[itemToUID(eventAfterTween)], |
| lastUserInteraction); |
| lastUserInteraction = null; |
| eventAfterTween = false; |
| } |
| //invalidate |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| // Get rid of the tween, so this onTweenEnd doesn't get called more than once. |
| tween = null; |
| } |
| |
| /** |
| * @private |
| * |
| * Helper function that builds up the collectionChange ADD or |
| * REMOVE events that correlate to the nodes that were expanded |
| * or collapsed. |
| */ |
| private function buildUpCollectionEvents(open:Boolean):Array |
| { |
| var ce:CollectionEvent; |
| var i:int; |
| var item:Object; |
| var parentArray:Array; |
| var rowsAdded:Array = []; |
| var rowsRemoved:Array = []; |
| var retVal:Array = []; |
| |
| var itemIndex:int = getItemIndex(expandedItem); |
| |
| if (open) |
| { |
| var children:ICollectionView = getChildren(expandedItem, iterator.view); |
| if (!children) |
| return []; |
| var cursor:IViewCursor = children.createCursor(); |
| var push:Boolean = true; |
| while (!cursor.afterLast) |
| { |
| rowsAdded.push(cursor.current); |
| cursor.moveNext(); |
| } |
| } |
| else |
| { |
| var stack:Array = []; |
| var j:int = 0; |
| stack = getOpenChildrenStack(expandedItem, stack); |
| while (j < stack.length) |
| { |
| for (i = 0; i < selectedItems.length; i++) |
| { |
| if (selectedItems[i] == stack[j]) |
| { |
| bSelectedItemRemoved = true; |
| } |
| } |
| rowsRemoved.push(stack[j]); |
| j++; |
| } |
| } |
| if (rowsAdded.length > 0) |
| { |
| ce = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| ce.kind = CollectionEventKind.ADD; |
| ce.location = itemIndex + 1; |
| ce.items = rowsAdded; |
| retVal.push(ce); |
| } |
| if (rowsRemoved.length > 0) |
| { |
| ce = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); |
| ce.kind = CollectionEventKind.REMOVE; |
| ce.location = itemIndex + 1; |
| ce.items = rowsRemoved; |
| retVal.push(ce); |
| } |
| return retVal; |
| } |
| |
| /** |
| * @private |
| * Go through the open items and figure out which is deepest. |
| */ |
| private function getIndent():Number |
| { |
| var depth:Number = 0; |
| for (var p:String in _openItems) |
| { |
| // add one since its children are actually indented |
| depth = Math.max(getParentStack(_openItems[p]).length + 1, depth); |
| } |
| return depth * getStyle("indentation"); |
| } |
| |
| /** |
| * Checks to see if item is visible in the list |
| * @private |
| */ |
| override public function isItemVisible(item:Object):Boolean |
| { |
| //first check visible data |
| if (visibleData[itemToUID(item)]) |
| return true; |
| |
| //then check parent items |
| var parentItem:Object = getParentItem(item); |
| if (parentItem) |
| { |
| var uid:String = itemToUID(parentItem); |
| if (visibleData[uid] && _openItems[uid]) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @private |
| */ |
| public function getItemIndex(item:Object):int |
| { |
| var cursor:IViewCursor = collection.createCursor(); |
| var i:int = 0; |
| do |
| { |
| if (cursor.current === item) |
| break; |
| i++; |
| } |
| while (cursor.moveNext()); |
| // set back to 0 in case a change event comes along |
| // and causes the cursor to hit an unexpected IPE |
| cursor.seek(CursorBookmark.FIRST, 0); |
| return i; |
| } |
| |
| /** |
| * @private |
| */ |
| private function getIndexItem(index:int):Object |
| { |
| var cursor:IViewCursor = collection.createCursor(); |
| var i:int = index; |
| while (cursor.moveNext()) |
| { |
| if (i == 0) |
| return cursor.current; |
| i--; |
| } |
| return null; |
| } |
| |
| /** |
| * Opens or closes all the tree items below the specified item. |
| * |
| * If you set <code>dataProvider</code> and then immediately call |
| * <code>expandChildrenOf()</code> you may not see the correct behavior. |
| * You should either wait for the component to validate |
| * or call the <code>validateNow()</code> method. |
| * |
| * @param item The starting item. |
| * |
| * @param open Toggles an open or close operation. |
| * Specify <code>true</code> to open the items, and <code>false</code> to close them. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function expandChildrenOf(item:Object, open:Boolean):void |
| { |
| //if the iterator is null, that indicates we have not been |
| //validated yet, so we will not continue. |
| if (iterator == null) |
| return; |
| |
| // if it is not a branch item there's nothing to do |
| if (isBranch(item)) |
| { |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| null, //trigger |
| open, //opening |
| false, //animate |
| true); //dispatch |
| |
| var childItems:ICollectionView; |
| if (item != null && |
| _dataDescriptor.isBranch(item, iterator.view) && |
| _dataDescriptor.hasChildren(item, iterator.view)) |
| { |
| childItems = getChildren(item, iterator.view); |
| } |
| if (childItems) |
| { |
| var cursor:IViewCursor = childItems.createCursor(); |
| while (!cursor.afterLast) |
| { |
| if (isBranch(cursor.current)) |
| expandChildrenOf(cursor.current, open); |
| cursor.moveNext(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the known parent of a child item. This method returns a value |
| * only if the item was or is currently visible. Top level items have a |
| * parent with the value <code>null</code>. |
| * |
| * @param The item for which to get the parent. |
| * |
| * @return The parent of the item. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function getParentItem(item:Object):* |
| { |
| if (item == null) |
| return null; |
| if (item && collection) |
| { |
| if (_dataDescriptor is ITreeDataDescriptor2) |
| return ITreeDataDescriptor2(_dataDescriptor).getParent(item, wrappedCollection, _rootModel); |
| return HierarchicalCollectionView(collection).getParentItem(item); |
| } |
| return null; |
| } |
| |
| /** |
| * @private |
| * Returns the stack of parents from a child item. |
| */ |
| private function getParentStack(item:Object):Array |
| { |
| var stack:Array = []; |
| if (item == null) |
| return stack; |
| |
| var parent:* = getParentItem(item); |
| while (parent) |
| { |
| stack.push(parent); |
| parent = getParentItem(parent); |
| } |
| return stack; |
| } |
| |
| /** |
| * @private |
| * Returns a stack of all open descendants of an item. |
| */ |
| private function getOpenChildrenStack(item:Object, stack:Array):Array |
| { |
| var curr:Object; |
| if (item == null) |
| return stack; |
| |
| var children:ICollectionView = getChildren(item, iterator.view); |
| if (!children) |
| return []; |
| var cursor:IViewCursor = children.createCursor(); |
| while (!cursor.afterLast) |
| { |
| curr = cursor.current; |
| stack.push(curr); |
| if (isBranch(curr) && isItemOpen(curr)) |
| { |
| getOpenChildrenStack(curr, stack); |
| } |
| cursor.moveNext(); |
| } |
| return stack; |
| } |
| |
| /** |
| * @private |
| * Finds the index distance between a parent and child |
| */ |
| private function getChildIndexInParent(parent:Object, child:Object):int |
| { |
| var index:int = 0; |
| if (!parent) |
| { |
| var cursor:IViewCursor = ICollectionView(iterator.view).createCursor(); |
| while (!cursor.afterLast) |
| { |
| if (child === cursor.current) |
| break; |
| index++; |
| cursor.moveNext(); |
| } |
| } |
| else |
| { |
| if (parent != null && |
| _dataDescriptor.isBranch(parent, iterator.view) && |
| _dataDescriptor.hasChildren(parent, iterator.view)) |
| { |
| var children:ICollectionView = getChildren(parent, iterator.view); |
| if (children.contains(child)) |
| { |
| cursor = children.createCursor(); |
| while (!cursor.afterLast) |
| { |
| if (child === cursor.current) |
| break; |
| cursor.moveNext(); |
| index++; |
| } |
| |
| } |
| else |
| { |
| //throw new Error("Parent item does not contain specified child: " + itemToUID(child)); |
| } |
| } |
| } |
| return index; |
| } |
| |
| /** |
| * @private |
| * Collapses those items in the selected items array that have |
| * parent nodes already selected. |
| */ |
| private function collapseSelectedItems():Array |
| { |
| var collection:ArrayCollection = new ArrayCollection(selectedItems); |
| |
| for (var i:int = 0; i < selectedItems.length; i++) |
| { |
| var item:Object = selectedItems[i]; |
| var stack:Array = getParentStack(item); |
| for (var j:int = 0; j < stack.length; j++) |
| { |
| if (collection.contains(stack[j])) |
| { |
| //item's parent is included in the selected item set |
| var index:int = collection.getItemIndex(item); |
| var removed:Object = collection.removeItemAt(index); |
| break; |
| } |
| } |
| } |
| return collection.source; |
| } |
| |
| /** |
| * @private |
| */ |
| private function updateDropData(event:DragEvent):void |
| { |
| var rowCount:int = rowInfo.length; |
| var rowNum:int = 0; |
| var yy:int = rowInfo[rowNum].height; |
| var pt:Point = globalToLocal(new Point(event.stageX, event.stageY)); |
| while (rowInfo[rowNum] && pt.y >= yy) |
| { |
| if (rowNum != rowInfo.length-1) |
| { |
| rowNum++; |
| yy += rowInfo[rowNum].height; |
| } |
| else |
| { |
| // now we're past all rows. adding a pixel or two should be enough. |
| // at this point yOffset doesn't really matter b/c we're past all elements |
| // but might as well try to keep it somewhat correct |
| yy += rowInfo[rowNum].height; |
| rowNum++; |
| } |
| } |
| |
| var lastRowY:Number = rowNum < rowInfo.length ? rowInfo[rowNum].y : (rowInfo[rowNum-1].y + rowInfo[rowNum-1].height); |
| var yOffset:Number = pt.y - lastRowY; |
| var rowHeight:Number = rowNum < rowInfo.length ? rowInfo[rowNum].height : rowInfo[rowNum-1].height; |
| |
| rowNum += verticalScrollPosition; |
| |
| var parent:Object; |
| var index:int; |
| var emptyFolder:Boolean = false; |
| var numItems:int = collection ? collection.length : 0; |
| |
| var topItem:Object = (rowNum > _verticalScrollPosition && rowNum <= numItems) ? |
| listItems[rowNum - _verticalScrollPosition - 1][0].data : null; |
| var bottomItem:Object = (rowNum - verticalScrollPosition < rowInfo.length && rowNum < numItems) ? |
| listItems[rowNum - _verticalScrollPosition][0].data : null; |
| |
| var topParent:Object = collection ? getParentItem(topItem) : null; |
| var bottomParent:Object = collection ? getParentItem(bottomItem) : null; |
| |
| // check their relationship |
| if (yOffset > rowHeight * .5 && |
| isItemOpen(bottomItem) && |
| _dataDescriptor.isBranch(bottomItem, iterator.view) && |
| (!_dataDescriptor.hasChildren(bottomItem, iterator.view) || |
| _dataDescriptor.getChildren(bottomItem, iterator.view).length == 0)) |
| { |
| // we'll get here if we're dropping into an empty folder. |
| // we have to be in the lower 50% of the row, otherwise |
| // we're "between" rows. |
| parent = bottomItem; |
| index = 0; |
| emptyFolder = true; |
| } |
| else if (!topItem && !rowNum == rowCount) |
| { |
| parent = collection ? getParentItem(bottomItem) : null; |
| index = bottomItem ? getChildIndexInParent(parent, bottomItem) : 0; |
| rowNum = 0; |
| } |
| else if (bottomItem && bottomParent == topItem) |
| { |
| // we're dropping in the first item of a folder, that's an easy one |
| parent = topItem; |
| index = 0; |
| } |
| else if (topItem && bottomItem && topParent == bottomParent) |
| { |
| parent = collection ? getParentItem(topItem) : null; |
| index = iterator ? getChildIndexInParent(parent, bottomItem) : 0; |
| } |
| else |
| { |
| //we're dropping at the end of a folder. Pay attention to the position. |
| if (topItem && (yOffset < (rowHeight * .5))) |
| { |
| // ok, we're on the top half of the bottomItem. |
| parent = topParent; |
| index = getChildIndexInParent(parent, topItem) + 1; // insert after |
| } |
| else if (!bottomItem) |
| { |
| parent = null; |
| if ((rowNum - verticalScrollPosition) == 0) |
| index = 0; |
| else if (collection) |
| index = collection.length; |
| else index = 0; |
| } |
| else |
| { |
| parent = bottomParent; |
| index = getChildIndexInParent(parent, bottomItem); |
| } |
| } |
| _dropData = { parent: parent, index: index, localX: event.localX, localY: event.localY, |
| emptyFolder: emptyFolder, rowHeight: rowHeight, rowIndex: rowNum }; |
| } |
| |
| /** |
| * Initializes a TreeListData object that is used by the tree item renderer. |
| * |
| * @param item The item to be rendered. |
| * @param treeListData The TreeListDataItem to use in rendering the item. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected function initListData(item:Object, treeListData:TreeListData):void |
| { |
| if (item == null) |
| return; |
| |
| var open:Boolean = isItemOpen(item); |
| var branch:Boolean = isBranch(item); |
| var uid:String = itemToUID(item); |
| |
| // this is hidden by non-branches but kept so we know how wide it is so things align |
| treeListData.disclosureIcon = getStyle(open ? "disclosureOpenIcon" : |
| "disclosureClosedIcon"); |
| treeListData.open = open; |
| treeListData.hasChildren = branch; |
| treeListData.depth = getItemDepth(item, treeListData.rowIndex); |
| treeListData.indent = (treeListData.depth - 1) * getStyle("indentation"); |
| treeListData.item = item; |
| treeListData.icon = itemToIcon(item); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function layoutEditor(x:int, y:int, w:int, h:int):void |
| { |
| var indent:int = rowMap[editedItemRenderer.name].indent; |
| itemEditorInstance.move(x + indent, y); |
| itemEditorInstance.setActualSize(w - indent, h); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function scrollHandler(event:Event):void |
| { |
| if (isOpening) |
| return; |
| |
| // TextField.scroll bubbles so you might see it here |
| if (event is ScrollEvent) |
| super.scrollHandler(event); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function keyDownHandler(event:KeyboardEvent):void |
| { |
| var evt:ListEvent; |
| var pt:Point; |
| |
| if (isOpening) |
| { |
| event.stopImmediatePropagation(); |
| return; |
| } |
| |
| if (itemEditorInstance) |
| return; |
| |
| // If rtl layout, need to swap LEFT and RIGHT so correct action |
| // is done. |
| var keyCode:uint = mapKeycodeForLayoutDirection(event); |
| |
| // Keyboard handling is consistent with Windows Explorer. |
| var item:Object = selectedItem; |
| if (event.ctrlKey) |
| { |
| // Ctrl keys always get sent to the List. |
| super.keyDownHandler(event); |
| } |
| else if (keyCode == Keyboard.SPACE) |
| { |
| // if user has moved the caret cursor from the selected item |
| // move the cursor back to selected item |
| if (caretIndex != selectedIndex) |
| { |
| // erase the caret |
| var renderer:IListItemRenderer = indexToItemRenderer(caretIndex); |
| if (renderer) |
| drawItem(renderer); |
| caretIndex = selectedIndex; |
| } |
| |
| // Spacebar toggles the current open/closed status. No effect for leaf items. |
| if (isBranch(item)) |
| { |
| var o:Boolean = !isItemOpen(item); |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| event, //trigger |
| o, //opening |
| true, //animate |
| true); //dispatch |
| } |
| event.stopImmediatePropagation(); |
| } |
| else if (keyCode == Keyboard.LEFT) |
| { |
| // Left Arrow closes an open item. |
| // Otherwise, selects the parent item if there is one. |
| if (isItemOpen(item)) |
| { |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| event, //trigger |
| false, //opening |
| true, //animate |
| true) //dispatch |
| } |
| else |
| { |
| var parentItem:Object = getParentItem(item); |
| if (parentItem) |
| { |
| proposedSelectedItem = parentItem; |
| finishArrowKeySelection(); |
| } |
| } |
| event.stopImmediatePropagation(); |
| } |
| else if (keyCode == Keyboard.RIGHT) |
| { |
| // Right Arrow has no effect on leaf items. Closed branch items are opened. |
| //Opened branch items select the first child. |
| if (isBranch(item)) |
| { |
| if (isItemOpen(item)) |
| { |
| if (item) |
| { |
| var children:ICollectionView = getChildren(item, iterator.view); |
| if (children) |
| { |
| var cursor:IViewCursor = children.createCursor(); |
| if (cursor.current) |
| proposedSelectedItem = cursor.current; |
| } |
| else |
| proposedSelectedItem = null; |
| } |
| else |
| selectedItem = proposedSelectedItem = null; |
| |
| finishArrowKeySelection(); |
| } |
| else |
| { |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| event, //trigger |
| true, //opening |
| true, //animate |
| true); //dispatch |
| } |
| } |
| event.stopImmediatePropagation(); |
| } |
| else if (keyCode == Keyboard.NUMPAD_MULTIPLY) |
| { |
| expandChildrenOf(item, !isItemOpen(item)); |
| } |
| else if (keyCode == Keyboard.NUMPAD_ADD) |
| { |
| if (isBranch(item)) |
| { |
| if (!isItemOpen(item)) |
| { |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| event, //trigger |
| true, //opening |
| true, //animate |
| true); //dispatch |
| } |
| } |
| } |
| else if (keyCode == Keyboard.NUMPAD_SUBTRACT) |
| { |
| if (isItemOpen(item)) |
| { |
| dispatchTreeEvent(TreeEvent.ITEM_OPENING, |
| item, //item |
| null, //renderer |
| event, //trigger |
| false, //opening |
| true, //animate |
| true); //dispatch |
| } |
| } |
| else |
| { |
| // Nothing that we know or care about. Send it off to the List. |
| super.keyDownHandler(event); |
| } |
| } |
| |
| /** |
| * @private |
| * finish up left/right arrow key handling |
| */ |
| private function finishArrowKeySelection():void |
| { |
| bFinishArrowKeySelection = false; |
| |
| if (proposedSelectedItem) |
| selectedItem = proposedSelectedItem; |
| |
| // now test to see if it worked, if it didn't we probably |
| // got an IPE |
| if (selectedItem === proposedSelectedItem || !proposedSelectedItem) |
| { |
| var evt:ListEvent; |
| var pt:Point; |
| evt = new ListEvent(ListEvent.CHANGE); |
| evt.itemRenderer = indexToItemRenderer(selectedIndex); |
| pt = itemRendererToIndices(evt.itemRenderer); |
| if (pt) |
| { |
| evt.rowIndex = pt.y; |
| evt.columnIndex = pt.x; |
| } |
| dispatchEvent(evt); |
| var dI:int = getItemIndex(selectedItem); |
| if (dI != caretIndex) |
| { |
| caretIndex = selectedIndex; |
| } |
| if (dI < _verticalScrollPosition) |
| { |
| verticalScrollPosition = dI; |
| } |
| } |
| else |
| { |
| bFinishArrowKeySelection = true; |
| } |
| } |
| |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseOverHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseOverHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseOutHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseOutHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseClickHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseClickHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseDoubleClickHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseDoubleClickHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseDownHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseDownHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse events on items that are tweening away and are invalid for input |
| */ |
| override protected function mouseUpHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseUpHandler(event); |
| } |
| |
| /** |
| * @private |
| * Blocks mouse wheel handling while tween is running |
| */ |
| override protected function mouseWheelHandler(event:MouseEvent):void |
| { |
| if (!tween) |
| super.mouseWheelHandler(event); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function dragEnterHandler(event:DragEvent):void |
| { |
| if (event.isDefaultPrevented()) |
| return; |
| |
| lastDragEvent = event; |
| haveItemIndices = false; |
| |
| try |
| { |
| if (iteratorValid && event.dragSource.hasFormat("treeItems")) |
| { |
| //if (collectionThrowsIPE) |
| //checkItemIndices(event); |
| |
| DragManager.acceptDragDrop(this); |
| DragManager.showFeedback(event.ctrlKey ? |
| DragManager.COPY : |
| DragManager.MOVE); |
| showDropFeedback(event); |
| return; |
| } |
| } |
| catch(e:ItemPendingError) |
| { |
| if (!lastTreeSeekPending) |
| { |
| lastTreeSeekPending = new TreeSeekPending(event, dragEnterHandler) |
| e.addResponder(new ItemResponder(seekPendingDuringDragResultHandler, seekPendingDuringDragFailureHandler, |
| lastTreeSeekPending)); |
| } |
| } |
| catch(e1:Error) |
| { |
| } |
| hideDropFeedback(event); |
| DragManager.showFeedback(DragManager.NONE); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function dragOverHandler(event:DragEvent):void |
| { |
| if (event.isDefaultPrevented()) |
| return; |
| |
| lastDragEvent = event; |
| |
| try |
| { |
| if (iteratorValid && event.dragSource.hasFormat("treeItems")) |
| { |
| if (collectionThrowsIPE) |
| checkItemIndices(event); |
| |
| DragManager.showFeedback(event.ctrlKey ? |
| DragManager.COPY : |
| DragManager.MOVE); |
| showDropFeedback(event); |
| return; |
| } |
| } |
| catch(e:ItemPendingError) |
| { |
| if (!lastTreeSeekPending) |
| { |
| lastTreeSeekPending = new TreeSeekPending(event, dragOverHandler) |
| e.addResponder(new ItemResponder(seekPendingDuringDragResultHandler, seekPendingDuringDragFailureHandler, |
| lastTreeSeekPending)); |
| } |
| } |
| catch(e1:Error) |
| { |
| } |
| hideDropFeedback(event); |
| DragManager.showFeedback(DragManager.NONE); |
| } |
| |
| /** |
| * @private |
| * The default failure handler when a seek fails due to a page fault. |
| */ |
| private function seekPendingDuringDragFailureHandler(data:Object, |
| info:TreeSeekPending):void |
| { |
| } |
| |
| /** |
| * @private |
| * The default result handler when a seek fails due to a page fault. |
| * This method re-attempts setting the drag feedback |
| */ |
| private function seekPendingDuringDragResultHandler(data:Object, |
| info:TreeSeekPending):void |
| { |
| lastTreeSeekPending = null; |
| |
| if (lastDragEvent) |
| info.retryFunction(info.event); |
| } |
| |
| /** |
| * @private |
| */ |
| private function checkItemIndices(event:DragEvent):void |
| { |
| if (haveItemIndices) |
| return; |
| |
| // if we're moving to ourselves, we need to make sure we have |
| // everything paged below before we allow a drop |
| if ((event.action == DragManager.MOVE || event.action == DragManager.NONE) && dragMoveEnabled) |
| { |
| if (event.dragInitiator == this) |
| { |
| var items:Array = event.dragSource.dataForFormat("treeItems") as Array; |
| var n:int = items.length; |
| for (var i:int = 0; i < n; i++) |
| { |
| var parent:Object = getParentItem(items[i]); |
| getChildIndexInParent(parent, items[i]); |
| } |
| haveItemIndices = true; |
| } |
| } |
| } |
| |
| /** |
| * Handles <code>DragEvent.DRAG_DROP events</code>. This method hides |
| * the drop feedback by calling the <code>hideDropFeedback()</code> method. |
| * |
| * <p>If the action is a <code>COPY</code>, |
| * then this method makes a deep copy of the object |
| * by calling the <code>ObjectUtil.copy()</code> method, |
| * and replaces the copy's <code>uid</code> property (if present) |
| * with a new value by calling the <code>UIDUtil.createUID()</code> method.</p> |
| * |
| * @param event The DragEvent object. |
| * |
| * @see mx.utils.ObjectUtil |
| * @see mx.utils.UIDUtil |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override protected function dragDropHandler(event:DragEvent):void |
| { |
| if (event.isDefaultPrevented()) |
| return; |
| |
| hideDropFeedback(event); |
| |
| if (event.dragSource.hasFormat("treeItems")) |
| { |
| var items:Array = event.dragSource.dataForFormat("treeItems") as Array; |
| var i:int; |
| var n:int; |
| |
| // if we're moving to ourselves, we need to treat it specially and check for "parent" |
| // problems where we could recurse forever. |
| if (event.action == DragManager.MOVE && dragMoveEnabled) |
| { |
| if (event.dragInitiator == this) |
| { |
| // If we're dropping onto ourselves or a child of a descendant then dont actually drop |
| |
| calculateDropIndex(event); |
| |
| // If we did start this drag op then we need to remove first |
| var index:int; |
| var parent:*; |
| var parentItem:*; |
| var dropIndex:int = _dropData.index; |
| |
| //get ancestors of the drop target item |
| var dropParentStack:Array = getParentStack(_dropData.parent); |
| dropParentStack.unshift(_dropData.parent); |
| |
| n = items.length; |
| for (i = 0; i < n; i++) |
| { |
| parent = getParentItem(items[i]); |
| index = getChildIndexInParent(parent, items[i]); |
| //check ancestors of the dropTarget if the item matches, we're invalid |
| |
| for each (parentItem in dropParentStack) |
| { |
| //we dont want to drop into one of our own sets of children |
| if (items[i] === parentItem) |
| return; |
| } |
| |
| //we remove before we add due to the behavior |
| //of structures with parent pointers like e4x |
| removeChildItem(parent, items[i], index); |
| |
| //is the removed item before the drop location? |
| // then we need to shift the dropIndex accordingly |
| if (parent == _dropData.parent && index < _dropData.index) |
| dropIndex--; |
| |
| addChildItem(_dropData.parent, items[i], dropIndex); |
| } |
| |
| return; |
| } |
| } |
| |
| // If not dropping onto ourselves, then add the |
| // items here if it's a copy operation. |
| // If it's a move operation (and not on ourselves), then they |
| // are added in dragCompleteHandler and are removed from |
| // the source's dragCompleteHandler. We do both in dragCompleteHandler |
| // because in order to be re-parented, they must be removed from their |
| // original source FIRST. This means our code isn't coupled fantastically |
| // as dragCompleteHandler must get the destination tree and |
| // cast it to a Tree. |
| |
| if (event.action == DragManager.COPY) |
| { |
| if (!dataProvider) { |
| // Create an empty collection to drop items into. |
| dataProvider = []; |
| validateNow(); |
| } |
| |
| n = items.length; |
| for (i = 0; i < n; i++) |
| { |
| var item:Object = copyItemWithUID(items[i]); |
| |
| addChildItem(_dropData.parent, |
| item, |
| _dropData.index); |
| } |
| } |
| } |
| lastDragEvent = null; |
| } |
| |
| /** |
| * Handles <code>DragEvent.DRAG_COMPLETE</code> events. This method |
| * removes the item from the data provider. |
| * |
| * @param event The DragEvent object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override protected function dragCompleteHandler(event:DragEvent):void |
| { |
| isPressed = false; |
| |
| if (event.isDefaultPrevented()) |
| return; |
| |
| resetDragScrolling() |
| |
| try |
| { |
| if (event.dragSource.hasFormat("treeItems")) |
| { |
| // if we've moved the elements, then remove them here |
| if (event.action == DragManager.MOVE && dragMoveEnabled) |
| { |
| // if we moved onto ourselves, we already handled this in |
| // dragDropHandler |
| if (event.relatedObject != this) |
| { |
| var items:Array = event.dragSource.dataForFormat("treeItems") as Array; |
| var parent:*; |
| var index:int; |
| var i:int; |
| var n:int; |
| |
| //do the remove |
| n = items.length; |
| for (i = 0; i < n; i++) |
| { |
| parent = getParentItem(items[i]); |
| index = getChildIndexInParent(parent, items[i]); |
| removeChildItem(parent, items[i], index); |
| } |
| |
| // then add it to the target control (copy operations are |
| // handled in the target's dragDropHandler), but the MOVE |
| // operations need to be handled here (see comment in |
| // dragDropHandler about this) |
| if (event.relatedObject is Tree) |
| { |
| var targetTree:Tree = Tree(event.relatedObject); |
| if (!targetTree.dataProvider) { |
| // Create an empty collection to drop items into. |
| targetTree.dataProvider = []; |
| targetTree.validateNow(); |
| } |
| |
| n = items.length; |
| for (i = 0; i < n; i++) |
| { |
| var item:Object = items[i]; |
| |
| targetTree.addChildItem(targetTree._dropData.parent, |
| item, |
| targetTree._dropData.index); |
| } |
| } |
| } |
| clearSelected(false); |
| } |
| } |
| } |
| catch(e:ItemPendingError) |
| { |
| e.addResponder(new ItemResponder(seekPendingDuringDragResultHandler, seekPendingDuringDragFailureHandler, |
| new TreeSeekPending(event, dragCompleteHandler))); |
| } |
| lastDragEvent = null; |
| } |
| |
| /** |
| * @private |
| * Delegates to the Descriptor to add a child to a parent |
| */ |
| mx_internal function addChildItem(parent:Object, child:Object, index:Number):Boolean |
| { |
| return _dataDescriptor.addChildAt(parent, child, index, iterator.view); |
| } |
| |
| /** |
| * @private |
| * Delegates to the Descriptor to remove a child from a parent |
| */ |
| mx_internal function removeChildItem(parent:Object, child:Object, index:Number):Boolean |
| { |
| return _dataDescriptor.removeChildAt(parent, child, index, iterator.view); |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function dispatchTreeEvent(type:String, |
| item:Object, |
| renderer:IListItemRenderer, |
| trigger:Event = null, |
| opening:Boolean = true, |
| animate:Boolean = true, |
| dispatch:Boolean = true):void |
| { |
| var event:TreeEvent; |
| |
| // Create expanding event. |
| if (type == TreeEvent.ITEM_OPENING) |
| { |
| event = new TreeEvent(TreeEvent.ITEM_OPENING, |
| false, true); |
| event.opening = opening; |
| event.animate = animate; |
| event.dispatchEvent = dispatch; |
| } |
| |
| // Create all other events. |
| if (!event) |
| event = new TreeEvent(type); |
| |
| // Common values. |
| event.item = item; |
| event.itemRenderer = renderer; |
| event.triggerEvent = trigger; |
| |
| // Send it off. |
| dispatchEvent(event); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Handler for CollectionEvents dispatched from the root dataProvider as the data changes. |
| */ |
| override protected function collectionChangeHandler(event:Event):void |
| { |
| //if the iterator is null that indicates we havent been validated yet so we'll bail. |
| if (iterator == null) |
| return; |
| |
| var node:Object; |
| var parent:Object; |
| |
| if (event is CollectionEvent) |
| { |
| var ce:CollectionEvent = CollectionEvent(event); |
| |
| if (ce.kind == CollectionEventKind.EXPAND) |
| { |
| //we ignore expand in list/tree |
| event.stopPropagation(); |
| } |
| if (ce.kind == CollectionEventKind.UPDATE) |
| { |
| //this prevents listbase from invalidating the displaylist too early. |
| event.stopPropagation(); |
| //we only want to update the displaylist if an updated item was visible |
| //but dont have a sufficient test for that yet |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| } |
| else |
| { |
| super.collectionChangeHandler(event); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function adjustAfterRemove(items:Array, location:int, emitEvent:Boolean):Boolean |
| { |
| var indicesLength:int = selectedItems.length; |
| var requiresValueCommit:Boolean = emitEvent; |
| var length:int = items.length; |
| |
| if (_selectedIndex > location) |
| { |
| _selectedIndex -= length; |
| requiresValueCommit = true; |
| } |
| |
| if (bSelectedItemRemoved && indicesLength < 1) |
| { |
| _selectedIndex = getItemIndex(expandedItem); |
| requiresValueCommit = true; |
| bSelectionChanged = true; |
| bSelectedIndexChanged = true; |
| invalidateDisplayList(); |
| } |
| |
| return requiresValueCommit; |
| } |
| |
| /** |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal function expandItemHandler(event:TreeEvent):void |
| { |
| if (event.isDefaultPrevented()) |
| return; |
| |
| if (event.type == TreeEvent.ITEM_OPENING) |
| { |
| expandItem(event.item, event.opening, event.animate, event.dispatchEvent, event.triggerEvent); |
| } |
| } |
| |
| /** |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override mx_internal function selectionDataPendingResultHandler( |
| data:Object, |
| info:ListBaseSelectionDataPending):void |
| { |
| super.selectionDataPendingResultHandler(data, info); |
| if (bFinishArrowKeySelection && selectedItem === proposedSelectedItem) |
| finishArrowKeySelection(); |
| } |
| } |
| |
| } |
| |
| import mx.events.DragEvent; |
| |
| class TreeSeekPending |
| { |
| public function TreeSeekPending(event:DragEvent, retryFunction:Function) |
| { |
| this.event = event; |
| this.retryFunction = retryFunction; |
| } |
| |
| public var event:DragEvent; |
| |
| public var retryFunction:Function; |
| |
| } |