blob: 5bf874f3b1080863f2b13315da07b876e0cbd4e0 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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="&#64;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="&#64;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="&#64;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 &lt;mx:Tree&gt; tag inherits all the tag attributes of its superclass, and
* adds the following tag attributes:
* </p>
* <pre>
* &lt;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"
* &nbsp;
* <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"
* &nbsp;
* <b>Events</b>
* change="<i>No default</i>"
* itemClose="<i>No default</i>"
* itemOpen="<i>No default</i>"
* itemOpening="<i>No default</i>"
* /&gt;
* </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>&lt;mx:Tree id="tree" dataDescriptor="{new MyCustomTreeDataDescriptor()}"/&gt;></pre>
*
* <p>Alternatively, you can specify the property in MXML as a nested
* subtag, as the following example shows:</p>
*
* <pre>&lt;mx:Tree&gt;
* &lt;mx:dataDescriptor&gt;
* &lt;myCustomTreeDataDescriptor&gt;</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 row index of the first row after the one that's 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;
}