blob: a872381507af641ed7cafc4749ba45650fa00022 [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.listClasses
{
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.ui.Keyboard;
import flash.utils.Dictionary;
import flash.utils.clearInterval;
import flash.utils.setInterval;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.ItemResponder;
import mx.collections.ItemWrapper;
import mx.collections.ListCollectionView;
import mx.collections.ModifiedCollectionView;
import mx.collections.XMLListCollection;
import mx.collections.errors.CursorError;
import mx.collections.errors.ItemPendingError;
import mx.controls.dataGridClasses.DataGridListData;
import mx.core.DragSource;
import mx.core.EdgeMetrics;
import mx.core.EventPriority;
import mx.core.FlexShape;
import mx.core.FlexSprite;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IInvalidating;
import mx.core.ILayoutDirectionElement;
import mx.core.IUIComponent;
import mx.core.IUID;
import mx.core.IUITextField;
import mx.core.ScrollControlBase;
import mx.core.ScrollPolicy;
import mx.core.SpriteAsset;
import mx.core.mx_internal;
import mx.effects.Effect;
import mx.effects.IEffectTargetHost;
import mx.effects.Tween;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DragEvent;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import mx.events.ListEvent;
import mx.events.MoveEvent;
import mx.events.SandboxMouseEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.events.ScrollEventDirection;
import mx.events.TweenEvent;
import mx.managers.DragManager;
import mx.managers.IFocusManagerComponent;
import mx.managers.ISystemManager;
import mx.skins.halo.ListDropIndicator;
import mx.utils.ObjectUtil;
import mx.utils.UIDUtil;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the <code>selectedIndex</code> or <code>selectedItem</code> property
* changes as a result of user interaction.
*
* @eventType mx.events.ListEvent.CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="change", type="mx.events.ListEvent")]
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains the data to display.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
/**
* Dispatched when the user rolls the mouse pointer over an item in the control.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemRollOver", type="mx.events.ListEvent")]
/**
* Dispatched when the user rolls the mouse pointer out of an item in the control.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OUT
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemRollOut", type="mx.events.ListEvent")]
/**
* Dispatched when the user clicks on an item in the control.
*
* @eventType mx.events.ListEvent.ITEM_CLICK
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemClick", type="mx.events.ListEvent")]
/**
* Dispatched when the user double-clicks on an item in the control.
*
* @eventType mx.events.ListEvent.ITEM_DOUBLE_CLICK
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemDoubleClick", type="mx.events.ListEvent")]
//--------------------------------------
// Styles
//--------------------------------------
include "../../styles/metadata/FocusStyles.as"
include "../../styles/metadata/PaddingStyles.as"
/**
* The colors to use for the backgrounds of the items in the list.
* The value is an array of two or more colors.
* The backgrounds of the list items alternate among the colors in the array.
*
* <p>For DataGrid controls, all items in a row have the same background color,
* and each row's background color is determined from the array of colors.</p>
*
* <p>For the TileList control, which uses a single list to populate a
* two-dimensional display, the style can result in a checkerboard appearance,
* stripes, or other patterns based on the number of columns and rows and
* the number of colors specified. TileList cycles through the colors, placing
* the individual item background colors according to the
* layout direction. If you have an even number of colors and an even number of
* columns for a TileList layed out horizontally, you will get striping. If
* the number of columns is an odd number, you will get a checkerboard pattern.
* </p>
*
* <p>Only takes effect if no <code>backgroundColor</code> is specified.</p>
*
* @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")]
/**
* The skin to use to indicate where a dragged item can be dropped.
* When an AdvancedListBase-derived component is a potential drop target in a
* drag-and-drop operation, a call to the <code>showDropFeedback()</code>
* method makes an instance of this class and positions it one pixel above
* the item renderer for the item where, if the drop occurs, is the item after
* the dropped item.
*
* @default mx.controls.listClasses.ListDropIndicator
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="dropIndicatorSkin", type="Class", inherit="no")]
/**
* The number of pixels between the bottom of the row
* and the bottom of the renderer in the row.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
/**
* The number of pixels between the top of the row
* and the top of the renderer in the row.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* The color of the background of a renderer when the user rolls over it.
*
* @default 0xEEFEE6
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The color of the background of a renderer when the user selects it.
*
* @default 0x7FCEFF
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]
/**
* The color of the background of a renderer when the component is disabled.
*
* @default 0xDDDDDD
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionDisabledColor", type="uint", format="Color", inherit="yes")]
/**
* The duration of the selection effect.
* When an item is selected an effect plays as the background is colored.
* Set to 0 to disable the effect.
*
* @default 250
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionDuration", type="Number", format="Time", inherit="no")]
/**
* The easingFunction for the selection effect.
* When an item is selected an effect plays as the background is colored.
* The default is a linear fade in of the color. An easingFunction can be used
* for controlling the selection effect.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionEasingFunction", type="Function", inherit="no")]
/**
* The color of the text of a renderer when the user rolls over a it.
*
* @default 0x2B333C
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The color of the text of a renderer when the user selects it.
*
* @default 0x2B333C
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]
/**
* A flag that controls whether items are highlighted as the mouse rolls
* over them.
* If <code>true</code>, rows are highlighted as the mouse rolls over them.
* If <code>false</code>, rows are highlighted only when selected.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="useRollOver", type="Boolean", inherit="no")]
/**
* The vertical alignment of a renderer in a row.
* Possible values are <code>"top"</code>, <code>"middle"</code>,
* and <code>"bottom"</code>.
* The DataGrid positions the renderers in a row based on this style
* and the <code>paddingTop</code> and <code>paddingBottom</code> styles.
* if the item in the columns for a row have different heights
* Other list classes do not use <code>verticalAlign</code> but
* the item renderers can examine this style property
* and adjust their layout based on it.
*
* @default "top"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="verticalAlign", type="String", enumeration="bottom,middle,top", inherit="no")]
/**
* The effect used when changes occur in the control's data provider.
*
* This can be a class reference (to a subclass of effect) or an
* Effect object instance. The former is appropriate for CSS, the
* latter for inline definition within a component.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="dataChangeEffect", type="Object", inherit="no")]
//--------------------------------------
// Other metadata
//--------------------------------------
//[AccessibilityClass(implementation="mx.accessibility.ListBaseAccImpl")]
/**
* The AdvancedListBase class is the base class for controls,
* such as the AdvancedDataGrid and OLAPDataGrid controls, that represent lists
* of items that can have one or more selected items and can scroll through the
* items. Items are supplied using the <code>dataProvider</code> property
* and displayed via item renderers.
*
* <p>In a model/view architecture, the AdvancedListBase subclass represent
* the view, and the data provider represents the model.</p>
*
* @mxml
*
* <p>The AdvancedListBase class inherits all of the tag properties of its superclasses,
* and adds the following tag properties:</p>
*
* <pre>
* &lt;mx:<i>tagname</i>
* <b>Properties</b>
* allowDragSelection="false|true"
* allowMultipleSelection="false|true"
* columnCount="4"
* columnWidth="NaN"
* dataProvider="null"
* dataTipField="label"
* dataTipFunction="null"
* dragEnabled="false|true"
* dragMoveEnabled="false|true"
* dropEnabled="false|true"
* iconField="null"
* iconFunction="null"
* itemRenderer="null"
* labelField="label"
* labelFunction="null"
* lockedColumnCount=0
* lockedRowCount=0
* menuSelectionMode="false|true"
* rowCount="-1"
* rowHeight="NaN"
* selectable="true|false"
* selectedIndex="-1"
* selectedIndices="null"
* selectedItem="null"
* selectedItems="null"
* showDataTips="false|true"
* variableRowHeight="false|true"
* wordWrap="false|true"
*
* <b>Styles</b>
* alternatingItemColors="undefined"
* dataChangeEffect="undefined"
* dropIndicatorSkin="ListDropIndicator"
* focusAlpha="0.5"
* focusRoundedCorners="tl tr bl br"
* paddingBottom="2"
* paddingLeft="2"
* paddingRight="0"
* paddingTop="2"
* rollOverColor="0xEEFEE6"
* selectionColor="0x7FCEFF"
* selectionDisabledColor="0xDDDDDD"
* selectionDuration="250"
* selectionEasingFunction="undefined"
* textRollOverColor="0x2B333C"
* textSelectedColor="0x2B333C"
* useRollOver="true|false"
* verticalAlign="top|middle|bottom"
*
* <b>Events</b>
* change="<i>No default</i>"
* dataChange="<i>No default</i>"
* itemClick="<i>No default</i>"
* itemDoubleClick="<i>No default</i>"
* itemRollOut="<i>No default</i>"
* itemRollOver="<i>No default</i>"
* itemClick="<i>No default</i>"
* /&gt;
* </pre>
*
* @see mx.collections.ICollectionView
* @see mx.controls.AdvancedDataGrid
* @see mx.controls.OLAPDataGrid
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class AdvancedListBase extends ScrollControlBase
implements IDataRenderer, IFocusManagerComponent,
IListItemRenderer, IDropInListItemRenderer,
IEffectTargetHost
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* Anything in this list of styles will trigger a full repaint.
*/
private var IS_ITEM_STYLE:Object =
{
alternatingItemColors: true,
backgroundColor: true,
backgroundDisabledColor: true,
color: true,
rollOverColor: true,
selectionColor: true,
selectionDisabledColor: true,
styleName: true,
textColor:true,
textRollOverColor: true,
textSelectedColor: true
};
/**
* @private
* Mouse movement threshold for determining when to start a drag.
*/
mx_internal static const DRAG_THRESHOLD:int = 4;
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ListBaseAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function AdvancedListBase()
{
super();
tabEnabled = true;
addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
addEventListener(MouseEvent.CLICK, mouseClickHandler);
addEventListener(MouseEvent.DOUBLE_CLICK, mouseDoubleClickHandler);
invalidateProperties();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* An ICollectionView that represents the data provider.
* When you set the <code>dataProvider</code> property,
* Flex wraps the data provider as necessary to
* support the ICollectionView interface and
* sets this property to the result.
* The AdvancedListBase class then uses this property to access
* data in the provider.
* When you get the <code>dataProvider</code> property,
* Flex returns this value.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var collection:ICollectionView;
/**
* The main IViewCursor instance used to fetch items from the
* data provider and pass the items to the renderers.
* At the end of any sequence of code, it must always
* be positioned at the topmost visible item being displayed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var iterator:IViewCursor;
/**
* A flag that indicates that a page fault as occurred and that
* the iterator's position is not valid (not positioned at the topmost
* item being displayed).
* If the component gets a page fault (an ItemPending error),
* it sets <code>iteratorValid</code> to <code>false</code>. Code that
* normally handles the rendering of items checks this flag and does not
* run until the page of data comes in from the server.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var iteratorValid:Boolean = true;
/**
* The most recent seek that caused a page fault.
* If there are multiple page faults, only the most recent one
* is of interest, as that is where to position the iterator
* and start rendering rows again.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lastSeekPending:ListBaseSeekPending;
/**
* A hash table of data provider item renderers currently in view.
* The table is indexed by the data provider item's UID and is used
* to quickly get the renderer used to display a particular item.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var visibleData:Object = {};
/**
* An internal display object that parents all of the item renderers,
* selection and highlighting indicators and other supporting graphics.
* This is roughly equivalent to the <code>contentPane</code> in the
* Container class, and is used for managing scrolling.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var listContent:AdvancedListBaseContentHolder;
/**
* The layer in <code>listContent</code> where all selection
* and highlight indicators are drawn.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var selectionLayer:Sprite;
/**
* An Array of Arrays that contains
* the item renderer instances that render each data provider item.
* This is a two-dimensional row major Array
* (Array of rows that are Arrays of columns).
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var listItems:Array = [];
/**
* An array of ListRowInfo objects that cache row heights and
* other tracking information for the rows in the <code>listItems</code> property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var rowInfo:Array = [];
/**
* A hash map of item renderers to their respective ListRowInfo object.
* The ListRowInfo object is indexed by the DisplayObject name of the
* item renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var rowMap:Object = {};
/**
* A stack of unused item renderers.
* Most list classes recycle renderers they've already created
* as they scroll out of the displayable area; doing so
* saves time during scrolling.
* The recycled renderers are stored here.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var freeItemRenderers:Array = [];
/**
* A hash map of currently unused item renderers that may be
* used again in the near future. Used when running data effects.
* The map is indexed by the data provider item's UID.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var reservedItemRenderers:Object = {};
/**
* A hash map of item renderers that are not subject
* to the layout algorithms of the list
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var unconstrainedRenderers:Object = {};
/**
* A dictionary mapping item renderers to the ItemWrappers
* used to supply their data. Only applicable if a data
* effect is running.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var dataItemWrappersByRenderer:Dictionary = new Dictionary(true);
/**
* A flag that indicates if a data effect should be initiated
* the next time the display is updated.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var runDataEffectNextUpdate:Boolean = false;
/**
* A flag indicating if a data change effect is currently running
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var runningDataEffect:Boolean = false;
/**
* The effect that plays when changes occur in the data
* provider for the control.
* Set the effect by setting the <code>dataChangeEffect</code>
* style.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var cachedDataChangeEffect:Effect = null;
/**
* The collection view that temporarily preserves previous
* data provider state to facilitate running data change effects.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var modifiedCollectionView:ModifiedCollectionView;
/**
* A copy of the value normally stored in the <code>collection</code>
* property used while running data changes effects. This value should be
* null when a data change effect is not running.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var actualCollection:ICollectionView;
/**
* The number of extra item renderers the layout algorithm can use when
* constructing animations for data effects. Changes that take place in
* the data provider corresponding to the items visible onscreen or this
* many items before or after the items onscreen will be subject to
* full effects processing. Items outside this range may not be
* animated perfectly by the effects.
*
* <p>A reasonable value for this property is approximately the number
* of rows visible onscreen. Setting it to a very large value may
* cause performance problems when used with a dataProvider with many
* items.</p>
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var offscreenExtraRows:int = 0;
// TODO May have to reconsider for Tilelists (do we want to consider
// rows? Do we do this separately for vertical/horizontal?
// TODO this should be a property, and changing it should trigger
// update
//
// TODO Would rather not make this protected
/**
* The number of offscreen items currently above the topmost visible
* renderer. This number will be &lt;= offscreenExtraRows / 2.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var offscreenExtraRowsTop:int = 0;
/**
* The number of offscreen items currently below the bottommost visible
* item renderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var offscreenExtraRowsBottom:int = 0;
/**
* The number of columns that are currently not visible.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var offscreenExtraColumns:int = 0;
/**
* The number of columns on the left side of the control
* that are currently not visible.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var offscreenExtraColumnsLeft:int = 0;
/**
* The number of columns on the right side of the control
* that are currently not visible.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var offscreenExtraColumnsRight:int = 0;
/**
* A copy of the value normally stored in the <code>iterator</code>
* property used while running data changes effects.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var actualIterator:IViewCursor;
/**
* The UID of the item that is current rolled over or under the caret.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var highlightUID:String;
/**
* The renderer that is currently rolled over or under the caret.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var highlightItemRenderer:IListItemRenderer;
/**
* The DisplayObject that contains the graphics that indicates
* which renderer is highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var highlightIndicator:Sprite;
/**
* The UID of the item under the caret.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var caretUID:String;
/**
* The renderer for the item under the caret. In the selection
* model, there is an anchor, a caret and a highlighted item. When
* the mouse is being used for selection, the item under the mouse is
* highlighted as the mouse rolls over the item.
* When the mouse is clicked with no modifier keys (Shift or Control), the
* set of selected items is cleared and the item under the highlight is
* selected and becomes the anchor. The caret is unused in mouse
* selection.
*
* <p>If there is an anchor and another item is selected while
* using the Shift key, the old set of selected items is cleared, and
* all items between the item and the anchor are selected. Clicking
* items while using the Control key toggles the selection of individual
* items and does not move the anchor.</p>
*
* <p>When selecting items using the keyboard, if the arrow keys are used
* with no modifier keys, the old selection is cleared and the new item
* is selected and becomes the anchor and the caret, and a caret indicator
* is shown around the selection highlight. </p>
*
* <p>If the user uses arrow keys
* with the Shift key, the old selection is cleared and the items between
* the anchor and the new item are selected. The caret moves to the new
* item. </p>
*
* <p>If arrow keys are used with the Control key, just the caret moves.
* The user can use the Space key to toggle selection of the item under
* the caret.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var caretItemRenderer:IListItemRenderer;
/**
* The DisplayObject that contains the graphics that indicate
* which renderer is the caret.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var caretIndicator:Sprite;
/**
* A hash table of ListBaseSelectionData objects that track which
* items are currently selected. The table is indexed by the UID
* of the items.
*
* @see mx.controls.listClasses.ListBaseSelectionData
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var selectedData:Object = {};
/**
* A hash table of selection indicators. This table allows the component
* to quickly find and remove the indicators when the set of selected
* items is cleared. The table is indexed by the item's UID.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var selectionIndicators:Object = {};
/**
* A hash table of selection tweens. This allows the component to
* quickly find and clean up any tweens in progress if the set
* of selected items is cleared. The table is indexed by the item's UID.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var selectionTweens:Object = {};
/**
* A bookmark to the item under the caret. A bookmark allows the
* component to quickly seek to a position in the collection of items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var caretBookmark:CursorBookmark;
/**
* A bookmark to the item that is the anchor. A bookmark allows the
* component to quickly seek to a position in the collection of items.
* This property is used when selecting a set of items between the anchor
* and the caret or highlighted item, and when finding the selected item
* after a Sort or Filter is applied.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var anchorBookmark:CursorBookmark;
/**
* A flag that indicates whether to show caret.
* This property is usually set
* to <code>false</code> when mouse activity is detected and set back to
* <code>true</code> when the keyboard is used for selection.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var showCaret:Boolean;
/**
* The most recently calculated index where the drag item
* should be added to the drop target.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lastDropIndex:int;
/**
* A flag that indicates whether the <code>columnWidth</code>
* and <code>rowHeight</code> properties need to be calculated.
* This property is set to <code>true</code> if a style changes that can affect the
* measurements of the renderer, or if the data provider is changed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var itemsNeedMeasurement:Boolean = true;
/**
* A flag that indicates that the size of the renderers may have changed.
* The component usually responds by re-applying the data items to all of
* the renderers on the next <code>updateDisplayList()</code> call.
* There is an assumption that re-applying the items will invalidate the
* item renderers and cause them to re-measure.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var itemsSizeChanged:Boolean = false;
/**
* A flag that indicates that the renderer changed.
* The component usually responds by destroying all existing renderers
* and redrawing all of the renderers on the next
* <code>updateDisplayList()</code> call.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var rendererChanged:Boolean = false;
/**
* A flag that indicates that the a data change effect has
* just completed.
* The component usually responds by cleaning up various
* internal data structures on the next
* <code>updateDisplayList()</code> call.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var dataEffectCompleted:Boolean = false;
/**
* A flag that indicates whether the value of the <code>wordWrap</code>
* property has changed since the last time the display list was updated.
* This property is set when you change the <code>wordWrap</code>
* property value, and is reset
* to <code>false</code> by the <code>updateDisplayList()</code> method.
* The component usually responds by re-applying the data items to all of
* the renderers on the next <code>updateDisplayList()</code> call.
* This is different from itemsSizeChanged because it further indicates
* that re-applying the data items to the renderers may not invalidate them
* since the only thing that changed was whether or not the renderer should
* factor in wordWrap into its size calculations
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var wordWrapChanged:Boolean = false;
/**
* A flag that indicates if keyboard selection was interrupted by
* a page fault. The component responds by suspending the rendering
* of items until the page of data arrives.
* The <code>finishKeySelection()</code> method will be called
* when the paged data arrives
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var keySelectionPending:Boolean = false;
/**
* The offset of the item in the data provider that is the selection
* anchor point.
*
* @see mx.controls.listClasses.ListBase#caretItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var anchorIndex:int = -1;
/**
* The offset of the item in the data provider that is at the selection
* caret point.
*
* @see mx.controls.listClasses.ListBase#caretItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var caretIndex:int = -1;
/**
* @private
*/
private var columnCountChanged:Boolean = true;
/**
* @private
*/
private var columnWidthChanged:Boolean = false;
/**
* The default number of columns to display. This value
* is used if the calculation for the number of
* columns results in a value less than 1 when
* trying to calculate the column count based on size or
* content.
*
* @default 4
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var defaultColumnCount:int = 4;
/**
* The default number of rows to display. This value
* is used if the calculation for the number of
* columns results in a value less than 1 when
* trying to calculate the row count based on size or
* content.
*
* @default 4
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var defaultRowCount:int = 4;
/**
* The column count requested by explicitly setting the
* <code>columnCount</code> property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var explicitColumnCount:int = -1;
/**
* The column width requested by explicitly setting the
* <code>columnWidth</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var explicitColumnWidth:Number;
/**
* The row count requested by explicitly setting
* <code>rowCount</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var explicitRowCount:int = -1;
/**
* The row height requested by explicitly setting
* <code>rowHeight</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var explicitRowHeight:Number;
/**
* @private
*/
private var rowCountChanged:Boolean = true;
/**
* @private
* Cached style value.
*/
mx_internal var cachedPaddingTop:Number;
/**
* @private
* Cached style value.
*/
mx_internal var cachedPaddingBottom:Number;
/**
* @private
* Cached style value.
*/
mx_internal var cachedVerticalAlign:String;
/**
* @private
*/
private var oldUnscaledWidth:Number;
/**
* @private
*/
private var oldUnscaledHeight:Number;
/**
* @private
*/
private var horizontalScrollPositionPending:Number;
/**
* @private
*/
private var verticalScrollPositionPending:Number;
/**
* @private
*/
private var mouseDownPoint:Point;
/**
* @private
*/
private var bSortItemPending:Boolean = false;
// these three keep track of the key selection that caused
// the page fault
private var bShiftKey:Boolean = false;
private var bCtrlKey:Boolean = false;
private var lastKey:uint = 0;
private var bSelectItem:Boolean = false;
/**
* @private
* true if we don't know for sure what index we're on in the database
*/
private var approximate:Boolean = false;
// if false, pixel scrolling only in horizontal direction
mx_internal var bColumnScrolling:Boolean = true;
// either "horizontal", "vertical", "grid" Used to determine how
// to measure the list.
mx_internal var listType:String = "grid";
// mx_internal for automation delegate access
mx_internal var bSelectOnRelease:Boolean;
private var mouseDownItem:IListItemRenderer;
private var mouseDownIndex:int; // For drag and drop
mx_internal var bSelectionChanged:Boolean = false;
mx_internal var bSelectedIndexChanged:Boolean = false;
private var bSelectedItemChanged:Boolean = false;
private var bSelectedItemsChanged:Boolean = false;
private var bSelectedIndicesChanged:Boolean = false;
/**
* @private
* Dirty flag for the cache style value cachedPaddingTop.
*/
private var cachedPaddingTopInvalid:Boolean = true;
/**
* @private
* Dirty flag for the cache style value cachedPaddingBottom.
*/
private var cachedPaddingBottomInvalid:Boolean = true;
/**
* @private
* Dirty flag for the cache style value cachedVerticalAlign.
*/
private var cachedVerticalAlignInvalid:Boolean = true;
/**
* @private
* The first ListBaseSelectionData in a link list of ListBaseSelectionData.
* This represents the item that was most recently selected.
* ListBaseSelectionData instances are linked together and keep track of the
* order the user selects an item. This order is reflected in selectedIndices
* and selectedItems.
*/
private var firstSelectionData:ListBaseSelectionData;
/**
* The renderer that is or was rolled over or under the caret.
* In DG, this is always column 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var lastHighlightItemRenderer:IListItemRenderer;
/**
* The renderer that is or was rolled over or under the caret.
* In DG, this is the actual item
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var lastHighlightItemRendererAtIndices:IListItemRenderer;
/**
* The last coordinate send in ITEM_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private var lastHighlightItemIndices:Point;
private var dragScrollingInterval:int = 0;
/**
* @private
* An Array of Shapes that are used as clip masks for the list items
*/
private var itemMaskFreeList:Array;
/**
* @private
* An array of item renderers being tracked for MoveEvents while
* data change effects are running.
*/
private var trackedRenderers:Array = [];
/**
* @private
* Whether the mouse button is pressed
*/
mx_internal var isPressed:Boolean = false;
/**
* A separate IViewCursor used to find indices of items and
* other things. The collectionIterator can be at any
* place within the set of items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var collectionIterator:IViewCursor;
mx_internal var dropIndicator:IFlexDisplayObject;
//--------------------------------------------------------------------------
//
// Overridden properties: UIComponent
//
//--------------------------------------------------------------------------
//----------------------------------
// enabled
//----------------------------------
[Inspectable(category="General")]
/**
* @private
*/
override public function set enabled(value:Boolean):void
{
super.enabled = value;
var ui:IFlexDisplayObject = border as IFlexDisplayObject;
if (ui)
{
if (ui is IUIComponent)
IUIComponent(ui).enabled = value;
if (ui is IInvalidating)
IInvalidating(ui).invalidateDisplayList();
}
itemsSizeChanged = true;
invalidateDisplayList();
}
//----------------------------------
// showInAutomationHierarchy
//----------------------------------
/**
* @private
*/
override public function set showInAutomationHierarchy(value:Boolean):void
{
//do not allow value changes
}
//--------------------------------------------------------------------------
//
// Overridden properties: ScrollControlBase
//
//--------------------------------------------------------------------------
//----------------------------------
// horizontalScrollPolicy
//----------------------------------
/**
* @private
*/
override public function set horizontalScrollPolicy(value:String):void
{
super.horizontalScrollPolicy = value;
itemsSizeChanged = true;
invalidateDisplayList();
}
//----------------------------------
// horizontalScrollPosition
//----------------------------------
/**
* @private
*/
override public function get horizontalScrollPosition():Number
{
if (!isNaN(horizontalScrollPositionPending))
return horizontalScrollPositionPending;
return super.horizontalScrollPosition;
}
/**
* @private
*/
override public function set horizontalScrollPosition(value:Number):void
{
// if not init or no data;
if (listItems.length == 0 || !dataProvider || !isNaN(horizontalScrollPositionPending))
{
horizontalScrollPositionPending = value;
if (dataProvider)
invalidateDisplayList();
return;
}
horizontalScrollPositionPending = NaN;
// trace("set horizontalScrollPosition " + value + " " + super.horizontalScrollPosition);
var oldValue:int = super.horizontalScrollPosition;
super.horizontalScrollPosition = value;
removeClipMask();
if (oldValue != value)
{
// we're going to get a full repaint soon so don't bother here.
if (itemsSizeChanged)
return;
var deltaPos:int = value - oldValue;
var direction:Boolean = (deltaPos > 0);
deltaPos = Math.abs(deltaPos);
if (bColumnScrolling && deltaPos >= columnCount - lockedColumnCount)
{
clearIndicators();
visibleData = {};
makeRowsAndColumnsWithExtraColumns(oldUnscaledWidth, oldUnscaledHeight);
drawRowBackgrounds();
}
else
{
scrollHorizontally(value, deltaPos, direction);
}
}
addClipMask(false);
}
//----------------------------------
// verticalScrollPolicy
//----------------------------------
/**
* @private
*/
override public function set verticalScrollPolicy(value:String):void
{
super.verticalScrollPolicy = value;
itemsSizeChanged = true;
invalidateDisplayList();
}
//----------------------------------
// verticalScrollPosition
//----------------------------------
[Bindable("scroll")]
[Bindable("viewChanged")]
/**
* @private
*/
override public function get verticalScrollPosition():Number
{
if (!isNaN(verticalScrollPositionPending))
return verticalScrollPositionPending;
return super.verticalScrollPosition;
}
/**
* @private
*/
override public function set verticalScrollPosition(value:Number):void
{
if (listItems.length == 0 || !dataProvider || !isNaN(verticalScrollPositionPending))
{
verticalScrollPositionPending = value;
if (dataProvider)
invalidateDisplayList();
return;
}
verticalScrollPositionPending = NaN;
var oldValue:int = super.verticalScrollPosition;
super.verticalScrollPosition = value;
removeClipMask();
var oldoffscreenExtraRowsTop:int = offscreenExtraRowsTop;
var oldoffscreenExtraRowsBottom:int = offscreenExtraRowsBottom;
// trace("set verticalScrollPosition", oldValue, value);
if (oldValue != value)
{
var deltaPos:int = value - oldValue;
var direction:Boolean = (deltaPos > 0);
deltaPos = Math.abs(deltaPos);
if (deltaPos >= rowInfo.length - lockedRowCount || !iteratorValid)
{
clearIndicators();
visibleData = {};
makeRowsAndColumnsWithExtraRows(oldUnscaledWidth, oldUnscaledHeight);
}
else
{
scrollVertically(value, deltaPos, direction);
adjustListContent(oldUnscaledWidth,oldUnscaledHeight);
}
// if variable rowheight, we have to recalibrate the scrollbars thumb size
// on each scroll, otherwise you can't scroll down to a bunch of fat rows
// at the bottom of a list.
if (variableRowHeight)
configureScrollBars();
drawRowBackgrounds();
}
// if needed, add a clip mask to the items in the last row of the list
addClipMask((offscreenExtraRowsTop != oldoffscreenExtraRowsTop) || (offscreenExtraRowsBottom != oldoffscreenExtraRowsBottom));
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// allowDragSelection
//----------------------------------
/**
* A flag that indicates whether drag-selection is enabled.
* Drag-selection is the ability to select an item by dragging
* into it as opposed to normal selection where you can't have
* the mouse button down when you mouse over the item you want
* to select. This feature is used in ComboBox dropdowns
* to support pressing the mouse button when the mouse is over the
* dropdown button, and then dragging the mouse into the dropdown to select
* an item.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var allowDragSelection:Boolean = false;
//----------------------------------
// allowMultipleSelection
//----------------------------------
/**
* @private
* Storage for the allowMultipleSelection property.
*/
private var _allowMultipleSelection:Boolean = false;
[Inspectable(category="General", enumeration="false,true", defaultValue="false")]
/**
* A flag that indicates whether you can allow more than one item to be
* selected at the same time.
* If <code>true</code>, users can select multiple items.
* There is no option to disallow discontiguous selection.
* Standard complex selection options are always in effect
* (shift-click, control-click).
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get allowMultipleSelection():Boolean
{
return _allowMultipleSelection;
}
/**
* @private
*/
public function set allowMultipleSelection(value:Boolean):void
{
_allowMultipleSelection = value;
}
//----------------------------------
// columnCount
//----------------------------------
/**
* @private
* Storage for the columnCount property.
*/
private var _columnCount:int = -1;
/**
* The number of columns to be displayed in a TileList control or items
* in a HorizontalList control.
* For the data grids, specifies the number of visible columns.
*
* <p><b>Note</b>: Setting this property has no effect on a DataGrid control,
* which bases the number of columns on the control width and the
* individual column widths.</p>
*
* @default 4
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get columnCount():int
{
return _columnCount;
}
/**
* @private
*/
public function set columnCount(value:int):void
{
explicitColumnCount = value;
if (_columnCount != value)
{
setColumnCount(value);
columnCountChanged = true;
invalidateProperties();
invalidateSize();
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("columnCountChanged"));
}
}
/**
* Internal version for setting columnCount
* without invalidation or notification.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function setColumnCount(value:int):void
{
_columnCount = value;
}
//----------------------------------
// columnWidth
//----------------------------------
/**
* @private
* Storage for the columnWidth property.
*/
private var _columnWidth:Number;
/**
* The width of the control's columns.
* This property is used by TileList and HorizontalList controls;
* It has no effect on data grid controls, where you set the individual
* column widths.
*
* @default 50
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get columnWidth():Number
{
return _columnWidth;
}
/**
* @private
*/
public function set columnWidth(value:Number):void
{
explicitColumnWidth = value;
if (_columnWidth != value)
{
setColumnWidth(value);
invalidateSize();
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("columnWidthChanged"));
}
}
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:Object;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* The item in the data provider this component should render when
* this component is used as an item renderer or item editor.
* The list class sets this property on each renderer or editor
* and the component displays the data. ListBase-derived classes
* support this property for complex situations like having a
* List of DataGrids or a DataGrid where one column is a List.
*
* <p>The list classes use the <code>listData</code> property
* in addition to the <code>data</code> property to determine what
* to display.
* If the list class is in a DataGrid it expects the <code>dataField</code>
* property of the column to map to a property in the data
* and sets <code>selectedItem</code> value to that property.
* If it is in a List or TileList control, it expects the
* <code>labelField</code> property of the list to map to a property
* in the data, and sets <code>selectedItem</code> value to that property.
* Otherwise it sets the <code>selectedItem</code> to the data itself.</p>
*
* <p>This property uses the data provider but does not set it.
* In all cases, you must set the data provider in some other way.</p>
*
* <p>You do not set this property in MXML.</p>
*
* @see mx.core.IDataRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
_data = value;
if (_listData && _listData is DataGridListData)
selectedItem = _data[DataGridListData(_listData).dataField];
else if (_listData is ListData && ListData(_listData).labelField in _data)
selectedItem = _data[ListData(_listData).labelField];
else
selectedItem = _data;
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
//----------------------------------
// dataProvider
//----------------------------------
[Bindable("collectionChange")]
[Inspectable(category="Data", defaultValue="undefined")]
/**
* Set of data to be viewed.
* This property lets you use most types of objects as data providers.
* If you set the <code>dataProvider</code> property to an Array,
* it will be converted to an ArrayCollection. If you set the property to
* an XML object, it will be converted into an XMLListCollection with
* only one item. If you set the property to an XMLList, it will be
* converted to an XMLListCollection.
* If you set the property to an object that implements the
* IList or ICollectionView interface, the object will be used directly.
*
* <p>As a consequence of the conversions, when you get the
* <code>dataProvider</code> property, it will always be
* an ICollectionView, and therefore not necessarily be the type of object
* you used to you set the property.
* This behavior is important to understand if you want to modify the data
* in the data provider: changes to the original data may not be detected,
* but changes to the ICollectionView object that you get back from the
* <code>dataProvider</code> property will be detected.</p>
*
* @default null
* @see mx.collections.ICollectionView
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dataProvider():Object
{
// if we are running a data change effect, return the true
// data provider, rather than the ModifiedCollectionView wrapper.
if (actualCollection)
return actualCollection;
return collection;
}
/**
* @private
*/
public function set dataProvider(value:Object):void
{
if (collection)
{
collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler);
}
if (value is Array)
{
collection = new ArrayCollection(value as Array);
}
else if (value is ICollectionView)
{
collection = ICollectionView(value);
}
else if (value is IList)
{
collection = new ListCollectionView(IList(value));
}
else if (value is XMLList)
{
collection = new XMLListCollection(value as XMLList);
}
else if (value is XML)
{
var xl:XMLList = new XMLList();
xl += value;
collection = new XMLListCollection(xl);
}
else
{
// convert it to an array containing this one item
var tmp:Array = [];
if (value != null)
tmp.push(value);
collection = new ArrayCollection(tmp);
}
// get an iterator for the displaying rows. The CollectionView's
// main iterator is left unchanged so folks can use old DataSelector
// methods if they want to
iterator = collection.createCursor();
collectionIterator = collection.createCursor(); //IViewCursor(collection);
// trace("ListBase added change listener");
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);
clearSelectionData();
var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.RESET;
collectionChangeHandler(event);
dispatchEvent(event);
itemsNeedMeasurement = true;
invalidateProperties();
invalidateSize();
invalidateDisplayList();
}
//----------------------------------
// dataTipField
//----------------------------------
/**
* @private
* Storage for the dataTipField property.
*/
private var _dataTipField:String = "label";
[Bindable("dataTipFieldChanged")]
[Inspectable(category="Data", defaultValue="label")]
/**
* Name of the field in the data provider items to display as the
* data tip. By default, the list looks for a property named
* <code>label</code> on each item and displays it.
* However, if the data objects do not contain a <code>label</code>
* property, you can set the <code>dataTipField</code> property to
* use a different property in the data object. An example would be
* "FullName" when viewing a
* set of people's names retrieved from a database.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dataTipField():String
{
return _dataTipField;
}
/**
* @private
*/
public function set dataTipField(value:String):void
{
_dataTipField = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("dataTipFieldChanged"));
}
//----------------------------------
// dataTipFunction
//----------------------------------
/**
* @private
* Storage for the dataTipFunction property.
*/
private var _dataTipFunction:Function;
[Bindable("dataTipFunctionChanged")]
[Inspectable(category="Data")]
/**
* User-supplied function to run on each item to determine its dataTip.
* By default, the list looks for a property named <code>label</code>
* on each data provider item and displays it.
* However, some items do not have a <code>label</code> property
* nor do they have another property that can be used for displaying
* in the rows. An example is a data set that has lastName and firstName
* fields, but you want to display full names. You can supply a
* <code>dataTipFunction</code> that finds the appropriate
* fields and return a displayable string. The
* <code>dataTipFunction</code> is also good for handling formatting
* and localization.
*
* <p>The dataTipFunction takes a single argument which is the item
* in the data provider and returns a String:</p>
*
* <blockquote>
* <code>myDataTipFunction(item:Object):String</code>
* </blockquote>
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dataTipFunction():Function
{
return _dataTipFunction;
}
/**
* @private
*/
public function set dataTipFunction(value:Function):void
{
_dataTipFunction = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("dataTipFunctionChanged"));
}
//----------------------------------
// dragEnabled
//----------------------------------
/**
* @private
* Storage for the dragEnabled property.
*/
private var _dragEnabled:Boolean = false;
/**
* A flag that indicates whether you can drag items out of
* this control and drop them on other controls.
* If <code>true</code>, dragging is enabled for the control.
* If the <code>dropEnabled</code> property is also <code>true</code>,
* you can drag items and drop them within this control
* to reorder the items.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dragEnabled():Boolean
{
return _dragEnabled;
}
/**
* @private
*/
public function set dragEnabled(value:Boolean):void
{
if (_dragEnabled && !value)
{
removeEventListener(DragEvent.DRAG_START,
dragStartHandler, false);
removeEventListener(DragEvent.DRAG_COMPLETE,
dragCompleteHandler, false);
}
_dragEnabled = value;
if (value)
{
addEventListener(DragEvent.DRAG_START, dragStartHandler, false,
EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler,
false, EventPriority.DEFAULT_HANDLER);
}
}
//----------------------------------
// dragImage
//----------------------------------
/**
* An instance of a class that displays the visuals
* during a drag and drop operation.
*
* @default mx.controls.listClasses.ListItemDragProxy
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get dragImage():IUIComponent
{
var image:ListItemDragProxy = new ListItemDragProxy();
image.owner = this;
image.moduleFactory = moduleFactory;
return image;
}
//----------------------------------
// dragImageOffsets
//----------------------------------
/**
* The offset of the drag image for drag and drop.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get dragImageOffsets():Point
{
var pt:Point = new Point;
var n:int = listItems.length;
for (var i:int = lockedRowCount; i < n; i++)
{
if (selectedData[rowInfo[i].uid])
{
pt.x = listItems[i][0].x;
pt.y = listItems[i][0].y;
}
}
return pt;
}
//----------------------------------
// dragMoveEnabled
//----------------------------------
/**
* @private
* Storage for the dragMoveEnabled property.
*/
private var _dragMoveEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether items can be moved instead
* of just copied from the control as part of a drag-and-drop
* operation.
* If <code>true</code>, and the <code>dragEnabled</code> property
* is <code>true</code>, items can be moved.
* Often the data provider cannot or should not have items removed
* from it, so a MOVE operation should not be allowed during
* drag-and-drop.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dragMoveEnabled():Boolean
{
return _dragMoveEnabled;
}
/**
* @private
*/
public function set dragMoveEnabled(value:Boolean):void
{
_dragMoveEnabled = value;
}
//----------------------------------
// dropEnabled
//----------------------------------
/**
* @private
* Storage for the <code>dropEnabled</code> property.
*/
private var _dropEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether dragged items can be dropped onto the
* control.
*
* <p>If you set this property to <code>true</code>,
* the control accepts all data formats, and assumes that
* the dragged data matches the format of the data in the data provider.
* If you want to explicitly check the data format of the data
* being dragged, you must handle one or more of the drag events,
* such as <code>dragOver</code>, and call the DragEvent's
* <code>preventDefault()</code> method to customize
* the way the list class accepts dropped data.</p>
*
* <p>When you set <code>dropEnabled</code> to <code>true</code>,
* Flex automatically calls the <code>showDropFeedback()</code>
* and <code>hideDropFeedback()</code> methods to display the drop indicator.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dropEnabled():Boolean
{
return _dropEnabled;
}
/**
* @private
*/
public function set dropEnabled(value:Boolean):void
{
if (_dropEnabled && !value)
{
removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false);
removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false);
removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false);
removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false);
}
_dropEnabled = value;
if (value)
{
addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false,
EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false,
EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false,
EventPriority.DEFAULT_HANDLER);
addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false,
EventPriority.DEFAULT_HANDLER);
}
}
//----------------------------------
// iconField
//----------------------------------
/**
* @private
* Storage for iconField property.
*/
private var _iconField:String = "icon";
[Bindable("iconFieldChanged")]
[Inspectable(category="Data", defaultValue="")]
/**
* The name of the field in the data provider object that determines what to
* display as the icon. By default, the list class does not try to display
* icons with the text in the rows. However, by specifying an icon
* field, you can specify a graphic that is created and displayed as an
* icon in the row. This property is ignored by DataGrid.
*
* <p>The renderers will look in the data provider object for a property of
* the name supplied as the iconField. If the value of the property is a
* Class, it will instantiate that class and expect it to be an instance
* of an IFlexDisplayObject. If the value of the property is a String,
* it will look to see if a Class exists with that name in the application,
* and if it can't find one, it will also look for a property on the
* document with that name and expect that property to map to a Class.</p>
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get iconField():String
{
return _iconField;
}
/**
* @private
*/
public function set iconField(value:String):void
{
_iconField = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("iconFieldChanged"));
}
//----------------------------------
// iconFunction
//----------------------------------
/**
* @private
* Storage for iconFunction property.
*/
private var _iconFunction:Function;
[Bindable("iconFunctionChanged")]
[Inspectable(category="Data")]
/**
* A user-supplied function to run on each item to determine its icon.
* By default the list does not try to display icons with the text
* in the rows. However, by specifying an icon function, you can specify
* a Class for a graphic that will be created and displayed as an icon
* in the row.
*
* <p>The <code>iconFunction</code> takes a single argument which is the item
* in the data provider and returns a Class.
* Shown below is the signature of the function:</p>
*
* <pre>iconFunction(item:Object):Class</pre>
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get iconFunction():Function
{
return _iconFunction;
}
/**
* @private
*/
public function set iconFunction(value:Function):void
{
_iconFunction = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("iconFunctionChanged"));
}
//----------------------------------
// itemRenderer
//----------------------------------
/**
* @private
* Storage for the itemRenderer property.
*/
private var _itemRenderer:IFactory;
[Inspectable(category="Data")]
/**
* The custom item renderer for the control.
* You can specify a drop-in, inline, or custom item renderer.
*
* <p>The default item renderer depends on the component class.
* For example, the AdvancedDataGrid class uses
* AdvancedDataGridItemRenderer.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get itemRenderer():IFactory
{
return _itemRenderer;
}
/**
* @private
*/
public function set itemRenderer(value:IFactory):void
{
_itemRenderer = value;
invalidateSize();
invalidateDisplayList();
itemsSizeChanged = true;
rendererChanged = true;
dispatchEvent(new Event("itemRendererChanged"));
}
//----------------------------------
// labelField
//----------------------------------
/**
* @private
* Storage for labelField property.
*/
private var _labelField:String = "label";
[Bindable("labelFieldChanged")]
[Inspectable(category="Data", defaultValue="label")]
/**
* The name of the field in the data provider items to display as the label.
* By default the list looks for a property named <code>label</code>
* on each item and displays it.
* However, if the data objects do not contain a <code>label</code>
* property, you can set the <code>labelField</code> property to
* use a different property in the data object. An example would be
* "FullName" when viewing a set of people names fetched from a database.
*
* @default "label"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelField():String
{
return _labelField;
}
/**
* @private
*/
public function set labelField(value:String):void
{
_labelField = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFieldChanged"));
}
//----------------------------------
// labelFunction
//----------------------------------
/**
* @private
* Storage for labelFunction property.
*/
private var _labelFunction:Function;
[Bindable("labelFunctionChanged")]
[Inspectable(category="Data")]
/**
* A user-supplied function to run on each item to determine its label.
* By default, the list looks for a property named <code>label</code>
* on each data provider item and displays it.
* However, some data sets do not have a <code>label</code> property
* nor do they have another property that can be used for displaying.
* An example is a data set that has lastName and firstName fields
* but you want to display full names.
*
* <p>You can supply a <code>labelFunction</code> that finds the
* appropriate fields and returns a displayable string. The
* <code>labelFunction</code> is also good for handling formatting and
* localization. </p>
*
* <p>For most components, the label function takes a single argument
* which is the item in the data provider and returns a String.</p>
* <pre>
* myLabelFunction(item:Object):String</pre>
*
* <p>The method signature for the data grid classes is:</p>
* <pre>
* myLabelFunction(item:Object, column:DataGridColumn):String</pre>
*
* <p>where <code>item</code> contains the DataGrid item object, and
* <code>column</code> specifies the DataGrid column.</p>
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelFunction():Function
{
return _labelFunction;
}
/**
* @private
*/
public function set labelFunction(value:Function):void
{
_labelFunction = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFunctionChanged"));
}
//----------------------------------
// listData
//----------------------------------
/**
* @private
* Storage for the listData property.
*/
private var _listData:BaseListData;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
*
* When a component is used as a drop-in item renderer or drop-in
* item editor, Flex initializes the <code>listData</code> property
* of the component with the additional data from the list control.
* The component can then use the <code>listData</code> property
* and the <code>data</code> property to display the appropriate
* information as a drop-in item renderer or drop-in item editor.
*
* <p>You do not set this property in MXML or ActionScript;
* Flex sets it when the component is used as a drop-in item renderer
* or drop-in item editor.</p>
*
* @see mx.controls.listClasses.IDropInListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get listData():BaseListData
{
return _listData;
}
/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = value;
}
//----------------------------------
// lockedColumnCount
//----------------------------------
/**
* @private
* Storage for the lockedColumnCount property.
*/
mx_internal var _lockedColumnCount:int = 0;
[Inspectable(defaultValue="0")]
/**
* The index of the first column in the control that scrolls,
* where the first column is at an index of 0.
* Columns with indexes that are lower than this value remain fixed
* in view. This property is not supported by all list classes.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lockedColumnCount():int
{
return _lockedColumnCount;
}
/**
* @private
*/
public function set lockedColumnCount(value:int):void
{
_lockedColumnCount = value;
invalidateDisplayList();
}
//----------------------------------
// lockedRowCount
//----------------------------------
/**
* @private
* Storage for the lockedRowCount property.
*/
mx_internal var _lockedRowCount:int = 0;
[Inspectable(defaultValue="0")]
/**
* The index of the first row in the control that scrolls,
* where the first row is at an index of 0.
* Rows above this one remain fixed in view.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lockedRowCount():int
{
return _lockedRowCount;
}
/**
* @private
*/
public function set lockedRowCount(value:int):void
{
_lockedRowCount = value;
invalidateDisplayList();
}
//----------------------------------
// menuSelectionMode
//----------------------------------
/**
* A flag that indicates whether menu-style selection
* should be used.
* In a Menu, dragging from
* one renderer into another selects the new one
* and un-selects the old.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var menuSelectionMode:Boolean = false;
//----------------------------------
// rowCount
//----------------------------------
/**
* @private
* Storage for the rowCount property.
*/
private var _rowCount:int = -1;
/**
* Number of rows to be displayed.
* If the height of the component has been explicitly set,
* this property might not have any effect.
*
* <p>For a data grid controls, the <code>rowCount</code> property includes the
* header row.
* So, for a data grid control with 3 body rows and a header row,
* the <code>rowCount</code> property is 4.</p>
*
* @default 4
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get rowCount():int
{
return _rowCount;
}
/**
* @private
*/
public function set rowCount(value:int):void
{
explicitRowCount = value;
if (_rowCount != value)
{
setRowCount(value);
rowCountChanged = true;
invalidateProperties();
invalidateSize();
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("rowCountChanged"));
}
}
//----------------------------------
// rowHeight
//----------------------------------
/**
* @private
* Storage for the rowHeight property.
*/
private var _rowHeight:Number;
/**
* @private
*/
private var rowHeightChanged:Boolean = false;
[Inspectable(category="General")]
/**
* The height of the rows in pixels.
* Unless the <code>variableRowHeight</code> property is
* <code>true</code>, all rows are the same height.
* If not specified, the row height is based on
* the font size and other properties of the renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get rowHeight():Number
{
return _rowHeight;
}
/**
* @private
*/
public function set rowHeight(value:Number):void
{
explicitRowHeight = value;
if (_rowHeight != value)
{
setRowHeight(value);
invalidateSize();
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("rowHeightChanged"));
}
}
//----------------------------------
// selectable
//----------------------------------
/**
* @private
* Storage for the selectable property.
*/
private var _selectable:Boolean = true;
[Inspectable(defaultValue="true")]
/**
* A flag that indicates whether the list shows selected items
* as selected.
* If <code>true</code>, the control supports selection.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectable():Boolean
{
return _selectable;
}
/**
* @private
*/
public function set selectable(value:Boolean):void
{
_selectable = value;
}
//----------------------------------
// selectedIndex
//----------------------------------
/**
* @private
* Storage for the selectedIndex property.
*/
mx_internal var _selectedIndex:int = -1;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="-1")]
/**
* The index in the data provider of the selected item.
*
* <p>The default value is -1 (no selected item).</p>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedIndex():int
{
return _selectedIndex;
}
/**
* @private
*/
public function set selectedIndex(value:int):void
{
if (!collection || collection.length == 0)
{
_selectedIndex = value;
bSelectionChanged = true;
bSelectedIndexChanged = true;
invalidateDisplayList();
return;
}
commitSelectedIndex(value);
}
//----------------------------------
// selectedIndices
//----------------------------------
private var _selectedIndices:Array;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General")]
/**
* An array of indices in the data provider of the selected items. The
* items are in the reverse order that the user selected the items.
*
* @default [ ]
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedIndices():Array
{
if (bSelectedIndicesChanged)
return _selectedIndices;
return copySelectedItems(false);
}
/**
* @private
*/
public function set selectedIndices(indices:Array):void
{
// trace("queueing indices");
if (!collection || collection.length == 0)
{
_selectedIndices = indices;
bSelectedIndicesChanged = true;
bSelectionChanged = true;
invalidateDisplayList();
return;
}
commitSelectedIndices(indices);
}
//----------------------------------
// selectedItem
//----------------------------------
/**
* @private
* Storage for the selectedItem property.
*/
mx_internal var _selectedItem:Object;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="null")]
/**
* A reference to the selected item in the data provider.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedItem():Object
{
return _selectedItem;
}
/**
* @private
*/
public function set selectedItem(data:Object):void
{
if (!collection || collection.length == 0)
{
_selectedItem = data;
bSelectedItemsChanged = true;
bSelectedItemChanged = true;
bSelectionChanged = true;
invalidateDisplayList();
return;
}
commitSelectedItem(data);
}
//----------------------------------
// selectedItems
//----------------------------------
private var _selectedItems:Array;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General")]
/**
* An Array of references to the selected items in the data provider. The
* items are in the reverse order that the user selected the items.
* @default [ ]
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedItems():Array
{
return bSelectedItemsChanged && _selectedItems ? _selectedItems : copySelectedItems();
}
/**
* @private
*/
public function set selectedItems(items:Array):void
{
if (!collection || collection.length == 0)
{
_selectedItems = items;
bSelectedItemsChanged = true;
bSelectionChanged = true;
invalidateDisplayList();
return;
}
commitSelectedItems(items);
}
//----------------------------------
// showDataTips
//----------------------------------
/**
* @private
* Storage for the showDataTips property.
*/
private var _showDataTips:Boolean = false;
[Bindable("showDataTipsChanged")]
[Inspectable(category="Data", defaultValue="false")]
/**
* A flag that indicates whether dataTips are displayed for text in the rows.
* If <code>true</code>, dataTips are displayed. DataTips
* are tooltips designed to show the text that is too long for the row.
* If you set a dataTipFunction, dataTips are shown regardless of whether the
* text is too long for the row.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get showDataTips():Boolean
{
return _showDataTips;
}
/**
* @private
*/
public function set showDataTips(value:Boolean):void
{
_showDataTips = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("showDataTipsChanged"));
}
//----------------------------------
// value
//----------------------------------
[Bindable("change")]
[Bindable("valueCommit")]
/**
* The selected item, or the data or label field of the selected item.
* If the selected item is a Number or String
* the value is the item. If the item is an object, the value is
* the data property if it exists, or the label property if it exists.
*
* <p>Note: Using <code>selectedItem</code> is often preferable. This
* property exists for backward compatibility with older applications</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get value():Object
{
var item:Object = selectedItem;
if (!item)
return null;
if (typeof(item) != "object")
return item;
return item.data != null ? item.data : item.label;
}
//----------------------------------
// variableRowHeight
//----------------------------------
/**
* @private
* Storage for the variableRowHeight property.
*/
private var _variableRowHeight:Boolean = false;
[Inspectable(category="General")]
/**
* A flag that indicates whether the individual rows can have different
* height. This property is ignored by TileList and HorizontalList.
* If <code>true</code>, individual rows can have different height values.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get variableRowHeight():Boolean
{
return _variableRowHeight;
}
/**
* @private
*/
public function set variableRowHeight(value:Boolean):void
{
_variableRowHeight = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("variableRowHeightChanged"));
}
//----------------------------------
// wordWrap
//----------------------------------
/**
* @private
* Storage for the wordWrap property.
*/
private var _wordWrap:Boolean = false;
[Inspectable(category="General")]
/**
* A flag that indicates whether text in the row should be word wrapped.
* If <code>true</code>, enables word wrapping for text in the rows.
* Only takes effect if <code>variableRowHeight</code> is also
* <code>true</code>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get wordWrap():Boolean
{
return _wordWrap;
}
/**
* @private
*/
public function set wordWrap(value:Boolean):void
{
if (value == _wordWrap)
return;
_wordWrap = value;
wordWrapChanged = true;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("wordWrapChanged"));
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function initializeAccessibility():void
{
//if (AdvancedListBase.createAccessibilityImplementation != null)
//AdvancedListBase.createAccessibilityImplementation(this);
}
/**
* Create objects that are children of this ListBase, in this case
* the <code>listContent</code> object that will hold all the item
* renderers.
* Note that the item renderers are not created immediately, but later
* when Flex calls the <code>updateDisplayList()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function createChildren():void
{
super.createChildren();
if (!listContent)
{
listContent = new AdvancedListBaseContentHolder(this);
listContent.styleName = this;
addChild(listContent);
}
// This invisible layer, which is a child of listContent
// catches mouse events for all items
// and is where we put selection highlighting by default.
if (!selectionLayer)
{
selectionLayer = new FlexSprite();
selectionLayer.name = "selectionLayer";
selectionLayer.mouseEnabled = false;
listContent.addChild(selectionLayer);
// trace("selectionLayer parent set to " + selectionLayer.parent);
var g:Graphics = selectionLayer.graphics;
g.beginFill(0, 0); // 0 alpha means transparent
g.drawRect(0, 0, 10, 10);
g.endFill();
}
}
/**
* Calculates the column width and row height and number of rows and
* columns based on whether properties like <code>columnCount</code>
* <code>columnWidth</code>, <code>rowHeight</code> and
* <code>rowCount</code> were explicitly set.
*
* @see mx.core.ScrollControlBase
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function commitProperties():void
{
super.commitProperties();
if (cachedPaddingTopInvalid)
{
cachedPaddingTopInvalid = false;
cachedPaddingTop = getStyle("paddingTop");
itemsSizeChanged = true;
invalidateDisplayList();
}
if (cachedPaddingBottomInvalid)
{
cachedPaddingBottomInvalid = false;
cachedPaddingBottom = getStyle("paddingBottom");
itemsSizeChanged = true;
invalidateDisplayList();
}
if (cachedVerticalAlignInvalid)
{
cachedVerticalAlignInvalid = false;
cachedVerticalAlign = getStyle("verticalAlign");
itemsSizeChanged = true;
invalidateDisplayList();
}
if (columnCountChanged)
{
if (_columnCount < 1)
_columnCount = defaultColumnCount;
if (!isNaN(explicitWidth) && isNaN(explicitColumnWidth) && explicitColumnCount > 0)
setColumnWidth((explicitWidth - viewMetrics.left - viewMetrics.right) / columnCount);
columnCountChanged = false;
}
if (rowCountChanged)
{
if (_rowCount < 1)
_rowCount = defaultRowCount;
if (!isNaN(explicitHeight) && isNaN(explicitRowHeight) && explicitRowCount > 0)
setRowHeight((explicitHeight - viewMetrics.top - viewMetrics.bottom) / rowCount);
rowCountChanged = false;
}
}
/**
* Calculates the measured width and height of the component based
* on the <code>rowCount</code>,
* <code>columnCount</code>, <code>rowHeight</code> and
* <code>columnWidth</code> properties.
*
* @see mx.core.ScrollControlBase
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function measure():void
{
super.measure();
var o:EdgeMetrics = viewMetrics;
var cc:int = explicitColumnCount < 1 ?
defaultColumnCount :
explicitColumnCount;
var rc:int = explicitRowCount < 1 ?
defaultRowCount :
explicitRowCount;
if (!isNaN(explicitRowHeight))
{
measuredHeight = explicitRowHeight * rc + o.top + o.bottom;
measuredMinHeight = explicitRowHeight * Math.min(rc, 2) +
o.top + o.bottom;
}
else
{
measuredHeight = rowHeight * rc + o.top + o.bottom;
measuredMinHeight = rowHeight * Math.min(rc, 2) +
o.top + o.bottom;
}
if (!isNaN(explicitColumnWidth))
{
measuredWidth = explicitColumnWidth * cc + o.left + o.right;
measuredMinWidth = explicitColumnWidth * Math.min(cc, 1) +
o.left + o.right;
}
else
{
measuredWidth = columnWidth * cc + o.left + o.right;
measuredMinWidth = columnWidth * Math.min(cc, 1) +
o.left + o.right;
}
// Factor out scrollbars if policy == AUTO. See Container.viewMetrics.
if (verticalScrollPolicy == ScrollPolicy.AUTO &&
verticalScrollBar && verticalScrollBar.visible)
{
measuredWidth -= verticalScrollBar.minWidth;
measuredMinWidth -= verticalScrollBar.minWidth;
}
if (horizontalScrollPolicy == ScrollPolicy.AUTO &&
horizontalScrollBar && horizontalScrollBar.visible)
{
measuredHeight -= horizontalScrollBar.minHeight;
measuredMinHeight -= horizontalScrollBar.minHeight;
}
}
/**
* @private
* This is a copy of the code in UIComponent, modified to
* start a data change effect if appropriate.
*/
override public function validateDisplayList():void
{
// this code is nearly duplicating UIComponent.validateDisplayList();
oldLayoutDirection = layoutDirection;
if (invalidateDisplayListFlag)
{
// Check if our parent is the top level system manager
var sm:ISystemManager = parent as ISystemManager;
if (sm)
{
if (sm == systemManager.topLevelSystemManager &&
sm.document != this)
{
// Size ourself to the new measured width/height
setActualSize(getExplicitOrMeasuredWidth(),
getExplicitOrMeasuredHeight());
}
}
// Don't validate transform.matrix until after setting actual size
validateMatrix();
if (runDataEffectNextUpdate)
{
runDataEffectNextUpdate = false;
runningDataEffect = true;
initiateDataChangeEffect(unscaledWidth, unscaledHeight);
}
else
{
updateDisplayList(unscaledWidth, unscaledHeight);
}
invalidateDisplayListFlag = false;
}
else
{
validateMatrix();
}
}
/**
* Adds or removes item renderers if the number of displayable items
* changed.
* Refreshes the item renderers if they might have changed.
* Applies the selection if it was changed programmatically.
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @see mx.core.ScrollControlBase
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
if (oldUnscaledWidth == unscaledWidth &&
oldUnscaledHeight == unscaledHeight &&
!itemsSizeChanged && !bSelectionChanged &&
!scrollAreaChanged)
{
return;
}
if (oldUnscaledWidth != unscaledWidth)
itemsSizeChanged = true;
super.updateDisplayList(unscaledWidth, unscaledHeight);
var cursorPos:CursorBookmark;
adjustListContent(unscaledWidth,unscaledHeight);
var collectionHasItems:Boolean = (collection && collection.length > 0);
if (collectionHasItems)
adjustScrollPosition();
// Remove the clip mask that was applied to items in the last row.
removeClipMask();
// have to resize selection layer without scaling so refill it
var g:Graphics = selectionLayer.graphics;
g.clear();
if (listContent.width > 0 && listContent.height > 0)
{
g.beginFill(0x808080, 0);
g.drawRect(0, 0, listContent.width, listContent.height);
g.endFill();
}
if (rendererChanged)
purgeItemRenderers();
else if (dataEffectCompleted)
partialPurgeItemRenderers();
// optimize layout if only height is changing
if (oldUnscaledWidth == unscaledWidth &&
!scrollAreaChanged &&
!itemsSizeChanged &&
listItems.length > 0 &&
iterator &&
columnCount == 1)
{
var rowIndex:int = listItems.length - 1;
if (oldUnscaledHeight > unscaledHeight)
// shrinking, so just toss extra rows
reduceRows(rowIndex);
else
makeAdditionalRows(rowIndex);
}
else // redo all layout
{
if (iterator)
cursorPos = iterator.bookmark;
clearIndicators();
// visibleData = {};
if (iterator)
{
if (offscreenExtraColumns)
makeRowsAndColumnsWithExtraColumns(unscaledWidth, unscaledHeight);
else
makeRowsAndColumnsWithExtraRows(unscaledWidth, unscaledHeight);
}
else
{
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
}
// restore iterator to original position
seekPositionIgnoreError(iterator,cursorPos);
}
oldUnscaledWidth = unscaledWidth;
oldUnscaledHeight = unscaledHeight;
configureScrollBars();
// if needed, add a clip mask to the items in the last row
addClipMask(true);
itemsSizeChanged = false;
wordWrapChanged = false;
adjustSelectionSettings(collectionHasItems);
if (keySelectionPending && iteratorValid)
{
keySelectionPending = false;
finishKeySelection();
}
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
if (IS_ITEM_STYLE[styleProp])
{
itemsSizeChanged = true;
invalidateDisplayList();
}
else if (styleProp == "paddingTop")
{
cachedPaddingTopInvalid = true;
invalidateProperties();
}
else if (styleProp == "paddingBottom")
{
cachedPaddingBottomInvalid = true;
invalidateProperties();
}
else if (styleProp == "verticalAlign")
{
cachedVerticalAlignInvalid = true;
invalidateProperties();
}
else if (styleProp == "dataChangeEffect")
{
cachedDataChangeEffect = null;
}
else if (listItems)
{
var n:int = listItems.length;
for (var i:int = 0; i < n; i++)
{
var m:int = listItems[i].length;
for (var j:int = 0; j < m; j++)
{
if (listItems[i][j])
listItems[i][j].styleChanged(styleProp);
}
}
}
super.styleChanged(styleProp);
if (invalidateSizeFlag)
{
itemsNeedMeasurement = true;
invalidateProperties();
}
if (styleManager.isSizeInvalidatingStyle(styleProp))
scrollAreaChanged = true;
}
//--------------------------------------------------------------------------
//
// Methods: Measuring
//
//--------------------------------------------------------------------------
/**
* Measures a set of items from the data provider using
* the current item renderer and returns the
* maximum width found. This method is used to calculate the
* width of the component. The various ListBase-derived classes
* have slightly different implementations. DataGrid measures
* its columns instead of data provider items, and TileList
* just measures the first item and assumes all items are the
* same size.
*
* <p>This method is not implemented in the AdvancedListBase class
* and must be implemented in the child class.</p>
*
* <p>A negative <code>index</code> value can be used to specify
* that the width calculation includes any headers.</p>
*
* @param index The data provider item at which to start measuring
* the width.
*
* @param count The number of items to measure in calculating the width.
*
* @return The widest of the measured items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function measureWidthOfItems(index:int = -1, count:int = 0):Number
{
return NaN;
}
/**
* Measures a set of items from the data provider using the
* current item renderer and returns the sum of the heights
* of those items.
*
* <p>This method is not implemented in the AdvancedListBase class
* and must be implemented in the child class.</p>
*
* <p>A negative <code>index</code> value can be used to specify
* that the height calculation includes any headers.</p>
*
* @param index The data provider item at which to start calculating
* the height.
*
* @param count The number of items to use in calculating the height.
*
* @return The sum of the height of the measured items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function measureHeightOfItems(index:int = -1, count:int = 0):Number
{
return NaN;
}
//--------------------------------------------------------------------------
//
// Methods: Item fields
//
//--------------------------------------------------------------------------
/**
* Returns the string the renderer would display for the given data object
* based on the labelField and labelFunction properties.
* If the method cannot convert the parameter to a string, it returns a
* single space.
*
* @param data Object to be rendered.
*
* @return The string to be displayed based on the data.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemToLabel(data:Object):String
{
if (data == null)
return " ";
if (labelFunction != null)
return labelFunction(data);
if (data is XML)
{
try
{
if (data[labelField].length() != 0)
data = data[labelField];
//by popular demand, this is a default XML labelField
//else if (data.@label.length() != 0)
// data = data.@label;
}
catch (e:Error)
{
}
}
else if (data is Object)
{
try
{
if (data[labelField] != null)
data = data[labelField];
}
catch(e:Error)
{
}
}
if (data is String)
return String(data);
try
{
return data.toString();
}
catch(e:Error)
{
}
return " ";
}
/**
* Returns the dataTip string the renderer would display for the given
* data object based on the dataTipField and dataTipFunction properties.
* If the method cannot convert the parameter to a string, it returns a
* single space.
* <p>For use by developers creating subclasses of ListBase or its children.
* Not used by application developers.</p>
*
* @param data Object to be rendered.
*
* @return String displayable string based on the data.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemToDataTip(data:Object):String
{
if (dataTipFunction != null)
return dataTipFunction(data);
if (typeof(data) == "object")
{
try
{
if (data[dataTipField] != null)
data = data[dataTipField];
else if (data.label != null)
data = data.label;
}
catch(e:Error)
{
}
}
if (typeof(data) == "string")
return String(data);
try
{
return data.toString();
}
catch(e:Error)
{
}
return " ";
}
/**
* Returns the class for an icon, if any, for a data item,
* based on the iconField and iconFunction properties.
* The field in the item can return a string as long as that
* string represents the name of a class in the application.
* The field in the item can also be a string that is the name
* of a variable in the document that holds the class for
* the icon.
*
* @param data The item from which to extract the icon class.
*
* @return The icon for the item, as a class reference or
* <code>null</code> if none.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemToIcon(data:Object):Class
{
if (data == null)
return null;
if (iconFunction != null)
return iconFunction(data);
var iconClass:Class;
var icon:*;
if (data is XML)
{
try
{
if (data[iconField].length() != 0)
{
icon = String(data[iconField]);
if (icon != null)
{
iconClass =
Class(systemManager.getDefinitionByName(icon));
if (iconClass)
return iconClass;
return document[icon];
}
}
}
catch(e:Error)
{
}
}
else if (data is Object)
{
try
{
if (data[iconField] != null)
{
if (data[iconField] is Class)
return data[iconField];
if (data[iconField] is String)
{
iconClass = Class(systemManager.getDefinitionByName(
data[iconField]));
if (iconClass)
return iconClass;
return document[data[iconField]];
}
}
}
catch(e:Error)
{
}
}
return null;
}
//--------------------------------------------------------------------------
//
// Methods: Renderer management
//
//--------------------------------------------------------------------------
/**
* @private
* Make enough rows and columns to fill the area
* described by left, top, right, bottom.
* Renderers are created and inserted into the <code>listItems</code>
* array starting at <code>(firstColumn, firstRow)(</code>
* and moving downwards.
*
* <p>If <code>byCount</code> and <code>rowsNeeded</code> are specified,
* then just make that many rows and ignore the <code>bottom</code>
* and <code>right</code> parameters.</p>
*
* @param left Horizontal pixel offset of area to fill.
*
* @param top Vertical pixel offset of area to fill.
*
* @param right Horizontal pixel offset of area to fill
* (from left side of component).
*
* @param bottom Vertical pixel offset of area to fill
* (from top of component).
*
* @param firstColumn Offset into <code>listItems</code> to store
* the first renderer to be created.
*
* @param firstRow Offset into <code>listItems</code> to store
* the first renderer to be created.
*
* @param byCount If true, make <code>rowsNeeded</code> number of rows
* and ignore <code>bottom</code> parameter
*
* @param rowsNeeded Number of rows to create if <code>byCount</code>
* is true;
*
* @return A Point containing the number of rows and columns created.
*/
protected function makeRowsAndColumns(left:Number, top:Number,
right:Number, bottom:Number,
firstColumn:int, firstRow:int,
byCount:Boolean = false,
rowsNeeded:uint = 0):Point
{
return new Point(0,0);
}
private function makeRowsAndColumnsWithExtraRows(unscaledWidth:Number,unscaledHeight:Number):void
{
var pt:Point;
var desiredExtraRowsTop:int = offscreenExtraRows / 2;
var desiredExtraRowsBottom:int = offscreenExtraRows / 2;
offscreenExtraRowsTop = Math.min(desiredExtraRowsTop, verticalScrollPosition);
var index:int = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition - offscreenExtraRowsTop);
seekPositionSafely(index);
var cursorPos:CursorBookmark = iterator.bookmark;
// if necessary, make the rows that will eventually be offscreen, above visible rows
if (offscreenExtraRowsTop > 0)
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0,true,offscreenExtraRowsTop);
var curY:Number = offscreenExtraRowsTop > 0 ? rowInfo[offscreenExtraRowsTop-1].y + rowHeight : 0;
// make onscreen items
pt = makeRowsAndColumns(0, curY, listContent.width, curY + listContent.heightExcludingOffsets, 0, offscreenExtraRowsTop);
// if necessary, and possible, make offscreen rows below visible rows.
if (desiredExtraRowsBottom > 0 && !iterator.afterLast)
{
// watch out for boundary condition
if (offscreenExtraRowsTop + pt.y - 1 < 0)
curY = 0;
else
curY = rowInfo[offscreenExtraRowsTop + pt.y - 1].y + rowInfo[offscreenExtraRowsTop + pt.y - 1].height;
var currentRows:int = listItems.length;
//
pt = makeRowsAndColumns(0,curY,listContent.width,curY,0,offscreenExtraRowsTop + pt.y,true,desiredExtraRowsBottom);
if (pt.y < desiredExtraRowsBottom)
{
var extraEmptyRows:int = listItems.length - (currentRows + pt.y);
if (extraEmptyRows)
for (var i:int = 0; i < extraEmptyRows; i++)
{
listItems.pop();
rowInfo.pop();
}
}
offscreenExtraRowsBottom = pt.y;
}
// adjust the ListContent offsets so that the first visible row is exactly at the top of the screen, etc.
var oldContentHeight:Number = listContent.heightExcludingOffsets;
listContent.topOffset = -offscreenExtraRowsTop * rowHeight;
listContent.bottomOffset = (offscreenExtraRowsBottom > 0) ?
listItems[listItems.length-1][0].y + rowHeight - oldContentHeight + listContent.topOffset :
0;
if (iteratorValid)
iterator.seek(cursorPos, 0);
// make sure list content is moved to the appropriate place.
// might be able to optimize and not do this every time
adjustListContent(unscaledWidth,unscaledHeight);
}
private function makeRowsAndColumnsWithExtraColumns(unscaledWidth:Number,unscaledHeight:Number):void
{
// NOTE: this function only works correctly for fixed column width
//if we scrolled more than the number of scrollable rows
var desiredOffscreenColumnsLeft:int = offscreenExtraColumns / 2;
var desiredOffscreenColumnsRight:int = offscreenExtraColumns / 2;
offscreenExtraColumnsLeft = Math.min(desiredOffscreenColumnsLeft,horizontalScrollPosition);
var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition);
seekPositionSafely(index);
var cursorPos:CursorBookmark = iterator.bookmark;
// if we are maintaining an extra column buffer, make extra columns
if (offscreenExtraColumnsLeft > 0)
makeRowsAndColumns(0,0,0,listContent.height,0,0,true,offscreenExtraColumnsLeft);
var curX:Number = offscreenExtraColumnsLeft ? listItems[0][offscreenExtraColumnsLeft-1].x + columnWidth : 0;
var pt:Point = makeRowsAndColumns(curX, 0, curX + listContent.widthExcludingOffsets, listContent.height, offscreenExtraColumnsLeft, 0);
if (desiredOffscreenColumnsRight > 0 && !iterator.afterLast)
{
if (offscreenExtraColumnsLeft + pt.x - 1 < 0)
curX = 0;
else
curX = listItems[0][offscreenExtraColumnsLeft + pt.x - 1].x + columnWidth;
var currentColumns:int = listItems[0].length;
pt = makeRowsAndColumns(curX,0,curX,listContent.height,offscreenExtraColumnsLeft+pt.x,0,true,desiredOffscreenColumnsRight);
if (pt.x < desiredOffscreenColumnsRight)
{
var extraEmptyColumns:int = listItems[0].length - (currentColumns + pt.x);
if (extraEmptyColumns)
{
for (var i:int = 0; i < listItems.length; i++)
for (var j:int = 0; j < extraEmptyColumns; j++)
listItems[i].pop();
}
}
offscreenExtraColumnsRight = pt.x; // I *think* this is always true
}
var oldContentWidth:Number = listContent.widthExcludingOffsets;
listContent.leftOffset = -offscreenExtraColumnsLeft * columnWidth;
listContent.rightOffset = (offscreenExtraColumnsRight > 0) ?
listItems[0][listItems[0].length-1].x + columnWidth - oldContentWidth + listContent.leftOffset :
0;
iterator.seek(cursorPos, 0);
adjustListContent(unscaledWidth, unscaledHeight);
}
/**
* Computes the offset into the data provider of the item
* at colIndex, rowIndex.
* The 9th row 3rd column in a TileList could be different items
* in the data provider based on the direction the tiles are laid
* out and the number of rows and columns in the TileList.
*
* @param rowIndex The 0-based index of the row, including rows
* scrolled off the top. Thus, if <code>verticalScrollPosition</code>
* is 2 then the first visible row has a rowIndex of 2.
*
* @param colIndex The 0-based index of the column, including
* columns scrolled off the left. If
* <code>horizontalScrollPosition</code> is 2 then the first column
* on the left has a columnIndex of 2.
*
* @return The offset into the data provider.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function indicesToIndex(rowIndex:int, colIndex:int):int
{
return rowIndex * columnCount + colIndex;
}
/**
* The row for the data provider item at the given index.
*
* @param index The offset into the data provider.
*
* @return The row the item would be displayed at in the component.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function indexToRow(index:int):int
{
return index;
}
/**
* The column for the data provider item at the given index.
*
* @param index The offset into the data provider.
*
* @return The column the item would be displayed at in the component.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function indexToColumn(index:int):int
{
return 0;
}
/**
* @private
* Used by accessibility.
*/
mx_internal function indicesToItemRenderer(row:int,
col:int):IListItemRenderer
{
return listItems[row][col];
}
/**
* Returns a Point instance containing the column index and row index of an
* item renderer. Since item renderers are only created for items
* within the set of viewable rows
* you cannot use this method to get the indices for items
* that are not visible. Also note that item renderers
* are recycled so the indices you get for an item may change
* if that item renderer is reused to display a different item.
* Usually, this method is called during mouse and keyboard handling
* when the set of data displayed by the item renderers hasn't yet
* changed.
*
* @param item An item renderer.
*
* @return A Point instance. The <code>x</code> property contains the column index
* and the <code>y</code> property contains the row index.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemRendererToIndices(item:IListItemRenderer):Point
{
if (!item || !(item.name in rowMap))
return null;
var index:int = rowMap[item.name].rowIndex;
var len:int = listItems[index].length;
for (var i:int = 0; i < len; i++)
{
if (listItems[index][i] == item)
break;
}
return new Point(i < lockedColumnCount ?
i :
i + horizontalScrollPosition,
index < lockedRowCount ?
index :
index + verticalScrollPosition + offscreenExtraRowsTop);
}
/**
* Get an item renderer for the index of an item in the data provider,
* if one exists. Since item renderers only exist for items
* within the set of viewable rows
* items, you cannot use this method for items that are not visible.
*
* @param index The offset into the data provider for an item.
*
* @return The item renderer that is displaying the item, or
* <code>null</code> if the item is not currently displayed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function indexToItemRenderer(index:int):IListItemRenderer
{
var firstItemIndex:int = verticalScrollPosition - offscreenExtraRowsTop;
if (index < firstItemIndex ||
index >= firstItemIndex + listItems.length)
{
return null;
}
return listItems[index - firstItemIndex][0];
}
/**
* Returns the index of the item in the data provider of the item
* being rendered by this item renderer. Since item renderers
* only exist for items that are within the set of viewable
* rows, you cannot
* use this method for items that are not visible.
*
* @param itemRenderer The item renderer that is displaying the
* item for which you want to know the data provider index.
*
* @return The index of the item in the data provider.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemRendererToIndex(itemRenderer:IListItemRenderer):int
{
if (itemRenderer.name in rowMap)
{
var index:int = rowMap[itemRenderer.name].rowIndex;
return index < lockedRowCount ?
index :
// not clear why the commented out logic isn't correct...
// maybe rowIndex isn't being set correctly?
// index + verticalScrollPosition + offscreenExtraRowsTop;
index + verticalScrollPosition;
}
else
{
return int.MIN_VALUE;
}
}
/**
* Determines the UID for a data provider item. All items
* in a data provider must either have a unique ID (UID)
* or one will be generated and associated with it. This
* means that you cannot have an object or scalar value
* appear twice in a data provider.
*
* <p>For example, the following
* data provider is not supported because the value "foo"
* appears twice and the UID for a string is the string itself:</p>
*
* <pre>
* var sampleDP:Array = ["foo", "bar", "foo"]
* </pre>
*
* <p>Simple dynamic objects can appear twice if they are two
* separate instances. The following is supported because
* each of the instances will be given a different UID because
* they are different objects:</p>
*
* <pre>
* var sampleDP:Array = [{label: "foo"}, {label: "foo"}]
* </pre>
*
* <p>Note that the following is not supported because the same instance
* appears twice:</p>
*
* <pre>
* var foo:Object = {label: "foo"};
* sampleDP:Array = [foo, foo];
* </pre>
*
* @param data The data provider item.
*
* @return The UID as a string.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToUID(data:Object):String
{
if (data == null)
return "null";
return UIDUtil.getUID(data);
}
/**
* Returns the item renderer for a given item in the data provider,
* if there is one. Since item renderers only exist for items
* that are within the set of viewable rows, this method
* returns <code>null</code> if the item is not visible.
* For a data grid, this returns the first column's renderer.
*
* @param item The data provider item.
*
* @return The item renderer or <code>null</code> if the item is not
* currently displayed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemToItemRenderer(item:Object):IListItemRenderer
{
return visibleData[itemToUID(item)];
}
/**
* Determines if an item is being displayed by a renderer.
*
* @param item A data provider item.
* @return <code>true</code> if the item is being displayed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemVisible(item:Object):Boolean
{
return itemToItemRenderer(item) != null;
}
/**
* Determines which item renderer is under the mouse. Item
* renderers can be made of multiple mouse targets, or have
* visible areas that are not mouse targets. This method
* checks both targets and position to determine which
* item renderer the mouse is over from the user's perspective,
* which can differ from the information provided by the
* mouse event.
*
* @param event A MouseEvent that contains the position of
* the mouse and the object it is over.
*
* @return The item renderer the mouse is over or
* <code>null</code> if none.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseEventToItemRenderer(
event:MouseEvent):IListItemRenderer
{
return mouseEventToItemRendererOrEditor(event);
}
/**
* @private
*/
mx_internal function mouseEventToItemRendererOrEditor(
event:MouseEvent):IListItemRenderer
{
var target:DisplayObject = DisplayObject(event.target);
if (target == listContent)
{
var pt:Point = new Point(event.stageX, event.stageY);
pt = listContent.globalToLocal(pt);
var yy:Number = 0;
var n:int = listItems.length;
for (var i:int = 0; i < n; i++)
{
if (listItems[i].length)
{
if (pt.y < yy + rowInfo[i].height)
{
var m:int = listItems[i].length;
if (m == 1)
return listItems[i][0];
var j:int = Math.floor(pt.x / columnWidth);
return listItems[i][j];
}
}
yy += rowInfo[i].height;
}
}
else if (target == highlightIndicator)
{
return lastHighlightItemRenderer;
}
while (target && target != this)
{
if (target is IListItemRenderer && target.parent == listContent)
{
if (target.visible)
return IListItemRenderer(target);
break;
}
if (target is IUIComponent)
target = IUIComponent(target).owner;
else
target = target.parent;
}
return null;
}
/**
* @private
* Helper function for addClipMask().
* Returns true if all of the IListItemRenderers are UITextFields.
*/
private function hasOnlyTextRenderers():Boolean
{
if (listItems.length == 0)
return true;
var rowItems:Array = listItems[listItems.length - 1];
var n:int = rowItems.length;
for (var i:int = 0; i < n; i++)
{
if (!(rowItems[i] is IUITextField))
return false;
}
return true;
}
/**
* Determines whether a renderer contains (or owns) a display object.
* Ownership means that the display object isn't actually parented
* by the renderer but is associated with it in some way. Popups
* should be owned by the renderers so that activity in the popup
* is associated with the renderer and not seen as activity in another
* component.
*
* @param renderer The renderer that might contain or own the
* display object.
*
* @param object The display object that might be associated with the
* renderer.
*
* @return <code>true</code> if the display object is contained
* or owned by the renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemRendererContains(renderer:IListItemRenderer,
object:DisplayObject):Boolean
{
if (!object)
return false;
if (!renderer)
return false;
return renderer.owns(object);
}
/**
* Adds a renderer to the recycled renderer list,
* making it invisible and cleaning up references to it.
* If a data effect is running, the renderer is reserved for
* future use for that data. Otherwise it is added to the
* general freeItemRenderers stack.
*
* @param item The IListItemRenderer to add.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function addToFreeItemRenderers(item:IListItemRenderer):void
{
// trace("addToFreeItemRenderers ", item);
DisplayObject(item).visible = false;
var oldWrapper:ItemWrapper = dataItemWrappersByRenderer[item];
// Before deleting from visibleData, make sure it is referring to this
// renderer. (If not, we rendered the data elsewhere).
var UID:String = oldWrapper ? itemToUID(oldWrapper) : itemToUID(item.data);
if (visibleData[UID] == item)
delete visibleData[UID];
// If a data effect is running, reserve any renderer that isn't
// being used, since it may be used again momentarily.
if (oldWrapper)
reservedItemRenderers[itemToUID(oldWrapper)] = item;
else
freeItemRenderers.push(item);
delete rowMap[item.name];
}
/**
* Retrieves an already-created item renderer not currently in use.
* If a data effect is running, it first tries to retrieve from the
* reservedItemRenderers map. Otherwise (or if no reserved renderer
* is found) it retrieves from the freeItemRenderers stack.
*
* @param data The data to be presented by the item renderer.
*
* @return An already-created item renderer not currently in use.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function getReservedOrFreeItemRenderer(data:Object):IListItemRenderer
{
var item:IListItemRenderer;
var uid:String;
if (runningDataEffect)
item = IListItemRenderer(reservedItemRenderers[uid = itemToUID(data)]);
if (item)
delete reservedItemRenderers[uid];
else if (freeItemRenderers.length)
item = freeItemRenderers.pop();
return item;
}
/**
* diagnostics
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function get rendererArray():Array
{
return listItems;
}
mx_internal var lastDragEvent:DragEvent;
//--------------------------------------------------------------------------
//
// Methods: Drawing
//
//--------------------------------------------------------------------------
/**
* Draws any alternating row colors, borders and backgrounds for the rows.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawRowBackgrounds():void
{
}
/**
* Draws the renderer with indicators
* that it is highlighted, selected, or the caret.
*
* @param item The renderer.
* @param selected <code>true</code> if the renderer should be drawn in
* its selected state.
* @param highlighted <code>true</code> if the renderer should be drawn in
* its highlighted state.
* @param caret <code>true</code> if the renderer should be drawn as if
* it is the selection caret.
* @param transition <code>true</code> if the selection state should fade in
* via an effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawItem(item:IListItemRenderer,
selected:Boolean = false,
highlighted:Boolean = false,
caret:Boolean = false,
transition:Boolean = false):void
{
var o:Sprite;
var g:Graphics;
if (!item)
return;
var rowData:BaseListData = rowMap[item.name];
// this can happen due to race conditions when using data effects
if (!rowData)
return;
// trace("drawitem " + rowData.uid + " " + selected + " " + highlighted + " " + caret);
if (highlighted &&
(!highlightItemRenderer || highlightUID != rowData.uid))
{
if (!highlightIndicator)
{
o = new SpriteAsset();
selectionLayer.addChild(DisplayObject(o));
highlightIndicator = o;
}
else
{
selectionLayer.setChildIndex(DisplayObject(highlightIndicator),
selectionLayer.numChildren - 1);
}
o = highlightIndicator;
// Let the highlightIndicator inherit the layoutDirection
if (o is ILayoutDirectionElement)
ILayoutDirectionElement(o).layoutDirection = null;
drawHighlightIndicator(
o, item.x, rowInfo[rowData.rowIndex].y,
item.width, rowInfo[rowData.rowIndex].height,
getStyle("rollOverColor"), item);
lastHighlightItemRenderer = highlightItemRenderer = item;
highlightUID = rowData.uid;
}
else if (!highlighted && highlightItemRenderer && (rowData && highlightUID == rowData.uid) )
{
if (highlightIndicator)
Sprite(highlightIndicator).graphics.clear();
highlightItemRenderer = null;
highlightUID = "";
}
if (selected)
{
var effectiveRowY:Number = runningDataEffect ?
item.y - mx_internal::cachedPaddingTop :
rowInfo[rowData.rowIndex].y;
if (!selectionIndicators[rowData.uid])
{
o = new SpriteAsset();
o.mouseEnabled = false;
// Let the selectionIndicator inherit the layoutDirection
ILayoutDirectionElement(o).layoutDirection = null;
selectionLayer.addChild(DisplayObject(o));
selectionIndicators[rowData.uid] = o;
drawSelectionIndicator(
o, item.x, effectiveRowY /* rowInfo[rowData.rowIndex].y */,
item.width, rowInfo[rowData.rowIndex].height,
enabled ?
getStyle("selectionColor") :
getStyle("selectionDisabledColor"),
item);
if (transition)
applySelectionEffect(o, rowData.uid, item);
}
else
{
o = selectionIndicators[rowData.uid];
// Let the selectionIndicator inherit the layoutDirection
if (o is ILayoutDirectionElement)
ILayoutDirectionElement(o).layoutDirection = null;
drawSelectionIndicator(
o, item.x, effectiveRowY /* rowInfo[rowData.rowIndex].y */,
item.width, rowInfo[rowData.rowIndex].height,
enabled ?
getStyle("selectionColor") :
getStyle("selectionDisabledColor"),
item);
}
}
else if (!selected)
{
if (rowData && selectionIndicators[rowData.uid])
{
if (selectionTweens[rowData.uid])
{
selectionTweens[rowData.uid].removeEventListener(
TweenEvent.TWEEN_UPDATE, selectionTween_updateHandler);
selectionTweens[rowData.uid].removeEventListener(
TweenEvent.TWEEN_END, selectionTween_endHandler);
if (selectionIndicators[rowData.uid].alpha < 1)
Tween.removeTween(selectionTweens[rowData.uid]);
delete selectionTweens[rowData.uid];
}
selectionLayer.removeChild(selectionIndicators[rowData.uid]);
delete selectionIndicators[rowData.uid]
}
}
if (caret) // && (!caretItemRenderer || caretUID != rowData.uid))
{
// Only draw the caret if there has been keyboard navigation.
if (showCaret)
{
if (!caretIndicator)
{
o = new SpriteAsset();
o.mouseEnabled = false;
selectionLayer.addChild(DisplayObject(o));
caretIndicator = o;
}
else
{
selectionLayer.setChildIndex(DisplayObject(caretIndicator),
selectionLayer.numChildren - 1);
}
o = caretIndicator;
// Let the caretIndicator inherit the layoutDirection
if (o is ILayoutDirectionElement)
ILayoutDirectionElement(o).layoutDirection = null;
drawCaretIndicator(
o, item.x, rowInfo[rowData.rowIndex].y,
item.width, rowInfo[rowData.rowIndex].height,
getStyle("selectionColor"), item);
var oldCaretItemRenderer:IListItemRenderer = caretItemRenderer;
caretItemRenderer = item;
caretUID = rowData.uid;
if (oldCaretItemRenderer)
{
if (oldCaretItemRenderer is IFlexDisplayObject)
{
if (oldCaretItemRenderer is IInvalidating)
{
IInvalidating(oldCaretItemRenderer).invalidateDisplayList();
IInvalidating(oldCaretItemRenderer).validateNow();
}
}
else if (oldCaretItemRenderer is IUITextField)
{
IUITextField(oldCaretItemRenderer).validateNow();
}
}
}
}
else if (!caret && caretItemRenderer && caretUID == rowData.uid)
{
if (caretIndicator)
Sprite(caretIndicator).graphics.clear();
caretItemRenderer = null;
caretUID = "";
}
if (item is IFlexDisplayObject)
{
if (item is IInvalidating)
{
IInvalidating(item).invalidateDisplayList();
IInvalidating(item).validateNow();
}
}
else if (item is IUITextField)
{
IUITextField(item).validateNow();
}
}
/**
* Draws the highlight indicator into the given Sprite
* at the position, width and height specified using the
* color specified.
*
* @param indicator A Sprite that should contain the graphics
* for that make a renderer look highlighted.
* @param x The suggested x position for the indicator.
* @param y The suggested y position for the indicator.
* @param width The suggested width for the indicator.
* @param height The suggested height for the indicator.
* @param color The suggested color for the indicator.
* @param itemRenderer The item renderer that is being highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawHighlightIndicator(
indicator:Sprite, x:Number, y:Number,
width:Number, height:Number, color:uint,
itemRenderer:IListItemRenderer):void
{
var g:Graphics = Sprite(indicator).graphics;
g.clear();
g.beginFill(color);
g.drawRect(0, 0, width, height);
g.endFill();
indicator.x = x;
indicator.y = y;
}
/**
* Draws the selection indicator into the given Sprite
* at the position, width and height specified using the
* color specified.
*
* @param indicator A Sprite that should contain the graphics
* for that make a renderer look highlighted.
* @param x The suggested x position for the indicator.
* @param y The suggested y position for the indicator.
* @param width The suggested width for the indicator.
* @param height The suggested height for the indicator.
* @param color The suggested color for the indicator.
* @param itemRenderer The item renderer that is being highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawSelectionIndicator(
indicator:Sprite, x:Number, y:Number,
width:Number, height:Number, color:uint,
itemRenderer:IListItemRenderer):void
{
var g:Graphics = Sprite(indicator).graphics;
g.clear();
g.beginFill(color);
g.drawRect(0, 0, width, height);
g.endFill();
indicator.x = x;
indicator.y = y;
}
/**
* Draws the caret indicator into the given Sprite
* at the position, width and height specified using the
* color specified.
*
* @param indicator A Sprite that should contain the graphics
* for that make a renderer look highlighted.
* @param x The suggested x position for the indicator.
* @param y The suggested y position for the indicator.
* @param width The suggested width for the indicator.
* @param height The suggested height for the indicator.
* @param color The suggested color for the indicator.
* @param itemRenderer The item renderer that is being highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawCaretIndicator(
indicator:Sprite, x:Number, y:Number,
width:Number, height:Number, color:uint,
itemRenderer:IListItemRenderer):void
{
var g:Graphics = Sprite(indicator).graphics;
g.clear();
g.lineStyle(1, color, 1);
g.drawRect(0, 0, width - 1, height - 1);
indicator.x = x;
indicator.y = y;
}
/**
* Removes all selection and highlight and caret indicators.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function clearIndicators():void
{
for (var uniqueID:String in selectionTweens)
{
removeIndicators(uniqueID);
}
if (selectionLayer)
{
while (selectionLayer.numChildren > 0)
{
selectionLayer.removeChildAt(0);
}
}
selectionTweens = {};
selectionIndicators = {};
highlightIndicator = null;
highlightUID = null;
caretIndicator = null;
caretUID = null;
}
/**
* Cleans up selection highlights and other associated graphics
* for a given item in the data provider.
*
* @param uid The UID of the data provider item.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function removeIndicators(uid:String):void
{
if (selectionTweens[uid])
{
selectionTweens[uid].removeEventListener(
TweenEvent.TWEEN_UPDATE, selectionTween_updateHandler);
selectionTweens[uid].removeEventListener(
TweenEvent.TWEEN_END, selectionTween_endHandler);
if (selectionIndicators[uid].alpha < 1)
Tween.removeTween(selectionTweens[uid]);
delete selectionTweens[uid];
}
// toss associated graphics if needed
if (selectionIndicators[uid])
{
selectionLayer.removeChild(selectionIndicators[uid]);
selectionIndicators[uid] = null;
}
if (uid == highlightUID)
{
highlightItemRenderer = null;
highlightUID = null;
if (highlightIndicator)
Sprite(highlightIndicator).graphics.clear();
}
if (uid == caretUID)
{
caretItemRenderer = null;
caretUID = null;
if (caretIndicator)
Sprite(caretIndicator).graphics.clear();
}
}
/**
* @private
*/
mx_internal function clearHighlight(item:IListItemRenderer):void
{
var uid:String = itemToUID(item.data);
drawItem(visibleData[uid], isItemSelected(item.data),
false, uid == caretUID);
var pt:Point = itemRendererToIndices(item);
if (pt && lastHighlightItemIndices)
{
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_ROLL_OUT);
listEvent.columnIndex = lastHighlightItemIndices.x;
listEvent.rowIndex = lastHighlightItemIndices.y;
listEvent.itemRenderer = lastHighlightItemRendererAtIndices;
dispatchEvent(listEvent);
lastHighlightItemIndices = null;
}
}
/**
* Refresh all rows on next update.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function invalidateList():void
{
itemsSizeChanged = true;
invalidateDisplayList();
}
/**
* Refreshes all rows now. Calling this method can require substantial
* processing, because can be expensive at it completely redraws all renderers
* in the list and won't return until complete.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function updateList():void
{
// trace("updateList " + verticalScrollPosition);
removeClipMask();
var cursorPos:CursorBookmark = (iterator) ? iterator.bookmark : null;
clearIndicators();
visibleData = {};
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
if (iterator)
iterator.seek(cursorPos, 0);
drawRowBackgrounds();
configureScrollBars();
addClipMask(true);
}
//--------------------------------------------------------------------------
//
// Methods: Clipping
//
//--------------------------------------------------------------------------
/**
* @private
* By default, there's a single large clip mask applied to the entire
* listContent area of the List. When the List contains a mixture of
* device text and vector graphics (e.g.: there are custom item renderers),
* that clip mask imposes a rendering overhead.
*
* When graphical (non-text) item renderers are used, we optimize by only
* applying a clip mask to the list items in the last row ... and then
* only when it's needed.
*
* This optimization breaks down when there's a horizontal scrollbar.
* Rather than attempting to apply clip masks to every item along the left
* and right edges, we give up and use the default clip mask that covers
* the entire List.
*
* For Lists and DataGrids containing custom item renderers, this
* optimization yields a 25% improvement in scrolling speed.
*/
mx_internal function addClipMask(layoutChanged:Boolean):void
{
// If something about the List has changed, check to see if we need
// to clip items in the last row.
if (layoutChanged)
{
if ((horizontalScrollBar && horizontalScrollBar.visible) ||
hasOnlyTextRenderers() || runningDataEffect ||
listContent.bottomOffset != 0 ||
listContent.topOffset != 0 ||
listContent.leftOffset != 0 ||
listContent.rightOffset != 0)
{
// As described above, we just use the default clip mask
// when there's a horizontal scrollbar or the item renders
// are all UITextFields.
listContent.mask = maskShape;
selectionLayer.mask = null;
}
else
{
// When we're not applying the default clip mask to the whole
// listContent, we still want to apply it to the selectionLayer
// (so that the selection rectangle and the mouseOver rectangle
// are properly clipped)
listContent.mask = null;
selectionLayer.mask = maskShape;
}
}
// If we've decided to clip the entire listContent, then stop here.
// There's no need to clip individual items
if (listContent.mask)
return;
// If the last row fits inside listContent, then stop here. There's
// no need to do any clipping.
var lastRowIndex:int = listItems.length - 1;
var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
var lastRowItems:Array = listItems[lastRowIndex];
if (lastRowInfo.y + lastRowInfo.height <= listContent.height)
return;
// For each list item in the last row, either apply a clip mask or
// set the row's height to not exceed the height of listContent
var numColumns:int = lastRowItems.length;
var rowY:Number = lastRowInfo.y;
var rowWidth:Number = listContent.width;
var rowHeight:Number = listContent.height - lastRowInfo.y;
for (var i:int = 0; i < numColumns; i++)
{
var item:DisplayObject = lastRowItems[i];
var yOffset:Number = item.y - rowY;
if (item is IUITextField)
item.height = rowHeight - yOffset;
else
item.mask = createItemMask(0, rowY + yOffset, rowWidth, rowHeight - yOffset);
}
}
/**
* @private
* Helper function for addClipMask().
* Creates a clip mask with the specified dimensions.
*/
private function createItemMask(x:Number, y:Number,
width:Number, height:Number):DisplayObject
{
var mask:Shape;
// To avoid constantly creating and destroying clip masks, we'll
// maintain a "free list" of masks that are not currently being
// used. Items are added to the free list in removeClipMask, below.
if (!itemMaskFreeList)
itemMaskFreeList = [];
if (itemMaskFreeList.length > 0)
{
mask = itemMaskFreeList.pop();
if (mask.width != width)
mask.width = width;
if (mask.height != height)
mask.height = height;
}
else
{
mask = new FlexShape();
mask.name = "mask";
var g:Graphics = mask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, width, height);
g.endFill();
mask.visible = false;
listContent.addChild(mask);
}
if (mask.x != x)
mask.x = x;
if (mask.y != y)
mask.y = y;
return mask;
}
/**
* @private
*
* Undo the effects of the addClipMask function (above)
*/
mx_internal function removeClipMask():void
{
// If we're currently using the default clip mask to clip the entire
// listContent, then there's no need to undo clipping on individual
// list items.
if (listContent && listContent.mask)
return;
// If there are no rows, do nothing.
var lastRowIndex:int = listItems.length - 1;
if (lastRowIndex < 0)
return;
// Undo the effects of the last "for" loop in addClipMask
var rowHeight:Number = rowInfo[lastRowIndex].height;
var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
var lastRowItems:Array = listItems[lastRowIndex];
var numColumns:int = lastRowItems.length;
for (var i:int = 0; i < numColumns; i++)
{
var item:DisplayObject = lastRowItems[i];
if (item is IUITextField)
{
if (item.height != rowHeight - (item.y - lastRowInfo.y))
item.height = rowHeight - (item.y - lastRowInfo.y);
}
else if (item && item.mask)
{
if(itemMaskFreeList)
{
itemMaskFreeList.push(item.mask);
}
item.mask = null;
}
}
}
//--------------------------------------------------------------------------
//
// Methods: Highlighting and selection
//
//--------------------------------------------------------------------------
/**
* Determines if the item renderer for a data provider item
* is the item under the caret due to keyboard navigation.
*
* @param data The data provider item.
* @return <code>true</code> if the item under the caret
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemShowingCaret(data:Object):Boolean
{
if (data == null)
return false;
if (data is String)
return (data == caretUID);
return itemToUID(data) == caretUID;
}
/**
* Determines if the item renderer for a data provider item
* is highlighted (is rolled over via the mouse or
* or under the caret via keyboard navigation).
*
* @param data The data provider item.
*
* @return <code>true</code> if the item is highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemHighlighted(data:Object):Boolean
{
if (data == null)
return false;
// When something is selected, the selection indicator
// overlays the highlighted indicator so we want
// to draw as selected and not highlighted.
var isSelected:Boolean = highlightUID && selectedData[highlightUID];
if (data is String)
return (data == highlightUID && !isSelected);
return itemToUID(data) == highlightUID && !isSelected;
}
/**
* Determines if the item renderer for a data provider item
* is selected.
*
* @param data The data provider item.
*
* @return <code>true</code> if the item is highlighted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemSelected(data:Object):Boolean
{
if (data == null)
return false;
if (data is String)
return (selectedData[data] != undefined)
return selectedData[itemToUID(data)] != undefined;
}
/**
* Determines if the item renderer for a data provider item
* is selectable.
*
* @param data The data provider item
* @return <code>true</code> if the item is selectable
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemSelectable(data:Object):Boolean
{
if (!selectable)
return false;
if (data == null)
return false;
return true;
}
/**
* @private
*/
private function calculateSelectedIndexAndItem():void
{
var num:int = 0;
for (var p:String in selectedData)
{
num = 1;
break;
}
if (!num)
{
_selectedIndex = -1;
_selectedItem = null;
return;
}
_selectedIndex = selectedData[p].index;
_selectedItem = selectedData[p].data;
}
/**
* Updates the set of selected items given that the item renderer provided
* was clicked by the mouse and the keyboard modifiers are in the given
* state. This method also updates the display of the item renderers based
* on their updated selected state.
*
* @param item The item renderer that was clicked.
* @param shiftKey <code>true</code> if the shift key was held down when
* the mouse was clicked.
* @param ctrlKey <code>true</code> if the ctrl key was held down when
* the mouse was clicked.
* @param transition <code>true</code> if the graphics for the selected
* state should be faded in using an effect.
*
* @return <code>true</code> if the set of selected items changed.
* Clicking on an already-selected item doesn't always change the set
* of selected items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function selectItem(item:IListItemRenderer,
shiftKey:Boolean, ctrlKey:Boolean,
transition:Boolean = true):Boolean
{
if (!item || !isItemSelectable(item.data))
return false;
// Begin multiple selection cases.
// We'll start by assuming the selection has changed.
var selectionChange:Boolean = false;
var placeHolder:CursorBookmark = iterator.bookmark;
var index:int = itemRendererToIndex(item);
var uid:String = itemToUID(item.data);
if (!allowMultipleSelection || (!shiftKey && !ctrlKey))
{
// we want to know if 0, 1 or more items are selected
var numSelected:int = 0;
if (allowMultipleSelection)
{
var curSelectionData:ListBaseSelectionData = firstSelectionData;
if (curSelectionData != null)
{
numSelected++;
if (curSelectionData.nextSelectionData)
numSelected++;
}
}
// allow unselecting via ctrl-click
if (ctrlKey && selectedData[uid])
{
selectionChange = true;
var oldCaretIndex:int = caretIndex;
clearSelected(transition);
caretIndex = oldCaretIndex;
}
// plain old click, ignore if same item is selected unless number of selected items
// is going to change
else if (_selectedIndex != index || bSelectedIndexChanged || (allowMultipleSelection && numSelected != 1))
{
selectionChange = true;
//Clear all other selections, this is a single click
clearSelected(transition);
addSelectionData(uid, new ListBaseSelectionData(item.data, index, approximate));
drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
_selectedIndex = index;
_selectedItem = item.data;
iterator.seek(CursorBookmark.CURRENT, _selectedIndex -
indicesToIndex(verticalScrollPosition - offscreenExtraRowsTop, horizontalScrollPosition - offscreenExtraColumnsLeft));
caretIndex = _selectedIndex;
caretBookmark = iterator.bookmark;
anchorIndex = _selectedIndex;
anchorBookmark = iterator.bookmark;
iterator.seek(placeHolder, 0);
}
}
else if (shiftKey && allowMultipleSelection)
{
// trace("begin shiftsel");
if (anchorBookmark)
{
var oldAnchorBookmark:CursorBookmark = anchorBookmark;
var oldAnchorIndex:int = anchorIndex;
var incr:Boolean = (anchorIndex < index);
clearSelected(false);
caretIndex = index;
caretBookmark = iterator.bookmark;
anchorIndex = oldAnchorIndex;
anchorBookmark = oldAnchorBookmark;
try
{
iterator.seek(anchorBookmark, 0);
}
catch (e:ItemPendingError)
{
e.addResponder(new ItemResponder(selectionPendingResultHandler, selectionPendingFailureHandler,
new ListBaseSelectionPending(incr, index, item.data, transition, placeHolder, CursorBookmark.CURRENT, 0)));
iteratorValid = false;
}
shiftSelectionLoop(incr, anchorIndex, item.data, transition, placeHolder);
}
// selection may or may not change for this case.
// but requires complicated testing.
// so just assume that selection changed.
selectionChange = true;
// trace("end shiftsel");
}
else if (ctrlKey && allowMultipleSelection)
{
if (selectedData[uid])
{
removeSelectionData(uid);
drawItem(visibleData[uid], false, uid == highlightUID, true, transition);
if (item.data == selectedItem)
calculateSelectedIndexAndItem();
}
else
{
addSelectionData(uid, new ListBaseSelectionData(item.data, index, approximate));
drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
_selectedIndex = index;
_selectedItem = item.data;
}
iterator.seek(CursorBookmark.CURRENT, index - indicesToIndex(verticalScrollPosition, horizontalScrollPosition));
caretIndex = index;
caretBookmark = iterator.bookmark;
anchorIndex = index;
anchorBookmark = iterator.bookmark;
iterator.seek(placeHolder, 0);
// if user is clicking with ctl key then
// selection gets changed always.
selectionChange = true;
}
return selectionChange;
}
/**
* @private
*/
private function shiftSelectionLoop(incr:Boolean, index:int,
stopData:Object, transition:Boolean,
placeHolder:CursorBookmark):void
{
var data:Object;
var uid:String;
// Correct the iterator position which, for some strange reason, doesn't
// point to the correct place.
iterator.seek(CursorBookmark.FIRST, anchorIndex);
try
{
do
{
data = iterator.current;
uid = itemToUID(data);
// trace(uid);
addSelectionData(uid, new ListBaseSelectionData(data, index, approximate));
if (visibleData[uid])
drawItem(visibleData[uid], true, uid == highlightUID, false, transition);
if (data === stopData)
{
if (visibleData[uid])
drawItem(visibleData[uid], true, uid == highlightUID, true, transition);
break;
}
if (incr)
index++;
else
index--;
}
while (incr ? iterator.moveNext() : iterator.movePrevious());
}
catch (e:ItemPendingError)
{
e.addResponder(new ItemResponder(
selectionPendingResultHandler, selectionPendingFailureHandler,
new ListBaseSelectionPending(incr, index, stopData, transition,
placeHolder,
CursorBookmark.CURRENT, 0)));
iteratorValid = false;
}
try
{
iterator.seek(placeHolder, 0);
iteratorValid = true;
}
catch (e2:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(placeHolder, 0);
e2.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
}
}
/**
* Clears the set of selected items and removes all graphics
* depicting the selected state of those items.
*
* @param transition <code>true</code> if the graphics should
* have a fadeout effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function clearSelected(transition:Boolean = false):void
{
for (var p:String in selectedData)
{
var data:Object = selectedData[p].data;
removeSelectionData(p);
var item:IListItemRenderer = visibleData[itemToUID(data)];
if (item)
drawItem(item, false, p == highlightUID, false, transition);
}
clearSelectionData();
_selectedIndex = -1;
_selectedItem = null;
_selectedItems = null;
caretIndex = -1;
anchorIndex = -1;
caretBookmark = null;
anchorBookmark = null;
}
/**
* Moves the selection in a horizontal direction in response
* to the user selecting items using the left-arrow or right-arrow
* keys and modifiers such as the Shift and Ctrl keys. This method
* might change the <code>horizontalScrollPosition</code>,
* <code>verticalScrollPosition</code>, and <code>caretIndex</code>
* properties, and call the <code>finishKeySelection()</code>method
* to update the selection.
*
* <p>Not implemented in AdvancedListBase because the default list
* is single column and therefore doesn't scroll horizontally.</p>
*
* @param code The key that was pressed (e.g. Keyboard.LEFT)
* @param shiftKey <code>true</code> if the shift key was held down when
* the keyboard key was pressed.
* @param ctrlKey <code>true</code> if the ctrl key was held down when
* the keyboard key was pressed
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function moveSelectionHorizontally(code:uint, shiftKey:Boolean,
ctrlKey:Boolean):void
{
// For Keyboard.LEFT and Keyboard.RIGHT and maybe Keyboard.UP and Keyboard.DOWN,
// need to account for layoutDirection="rtl".
return;
}
/**
* Moves the selection in a vertical direction in response
* to the user selecting items using the up-arrow or down-arrow
* Keys and modifiers such as the Shift and Ctrl keys. This method
* might change the <code>horizontalScrollPosition</code>,
* <code>verticalScrollPosition</code>, and <code>caretIndex</code>
* properties, and call the <code>finishKeySelection()</code>method
* to update the selection
*
* @param code The key that was pressed (e.g. Keyboard.DOWN)
* @param shiftKey <code>true</code> if the shift key was held down when
* the keyboard key was pressed.
* @param ctrlKey <code>true</code> if the ctrl key was held down when
* the keyboard key was pressed
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function moveSelectionVertically(code:uint, shiftKey:Boolean,
ctrlKey:Boolean):void
{
var newVerticalScrollPosition:Number;
var listItem:IListItemRenderer;
var uid:String;
var len:int;
var bSelChanged:Boolean = false;
showCaret = true;
var rowCount:int = listItems.length;
var partialRow:int = (rowInfo[rowCount-1].y + rowInfo[rowCount-1].height >
listContent.height) ? 1 : 0;
var bUpdateVerticalScrollPosition:Boolean = false;
bSelectItem = false;
switch (code)
{
case Keyboard.UP:
{
if (caretIndex > 0)
{
caretIndex--;
bUpdateVerticalScrollPosition = true;
bSelectItem = true;
}
break;
}
case Keyboard.DOWN:
{
if (caretIndex < collection.length - 1)
{
caretIndex++;
bUpdateVerticalScrollPosition = true;
bSelectItem = true;
}
else if ((caretIndex == collection.length - 1) && partialRow)
{
if (verticalScrollPosition < maxVerticalScrollPosition)
newVerticalScrollPosition = verticalScrollPosition + 1;
}
break;
}
case Keyboard.PAGE_UP:
{
if (caretIndex < lockedRowCount)
{
newVerticalScrollPosition = 0;
caretIndex = 0;
}
// if the caret is on-screen, but not at the top row
// just move the caret to the top row
else if (caretIndex > verticalScrollPosition + lockedRowCount &&
caretIndex < verticalScrollPosition + rowCount)
{
caretIndex = verticalScrollPosition + lockedRowCount;
}
else
{
// paging up is really hard because we don't know how many
// rows to move because of variable row height. We would have
// to double-buffer a previous screen in order to get this exact
// so we just guess for now based on current rowCount
caretIndex = Math.max(caretIndex - rowCount + lockedRowCount, 0);
newVerticalScrollPosition = Math.max(caretIndex - lockedRowCount,0)
}
bSelectItem = true;
break;
}
case Keyboard.PAGE_DOWN:
{
if (caretIndex < lockedRowCount)
{
newVerticalScrollPosition = 0;
}
// if the caret is on-screen, but not at the bottom row
// just move the caret to the bottom row (not partial row)
else if (caretIndex >= verticalScrollPosition + lockedRowCount &&
caretIndex < verticalScrollPosition + rowCount - partialRow - 1)
{
}
else
{
newVerticalScrollPosition = Math.min(caretIndex - lockedRowCount, maxVerticalScrollPosition);
}
bSelectItem = true;
break;
}
case Keyboard.HOME:
{
if (caretIndex > 0)
{
caretIndex = 0;
bSelectItem = true;
newVerticalScrollPosition = 0;
}
break;
}
case Keyboard.END:
{
if (caretIndex < collection.length - 1)
{
caretIndex = collection.length - 1;
bSelectItem = true;
newVerticalScrollPosition = maxVerticalScrollPosition;
}
break;
}
}
if (bUpdateVerticalScrollPosition)
{
if (caretIndex < lockedRowCount)
newVerticalScrollPosition = 0;
else if (caretIndex < verticalScrollPosition + lockedRowCount)
newVerticalScrollPosition = caretIndex - lockedRowCount;
else if (caretIndex >= verticalScrollPosition + rowCount - partialRow)
newVerticalScrollPosition = Math.min(maxVerticalScrollPosition, caretIndex - rowCount + partialRow + 1);
}
if (!isNaN(newVerticalScrollPosition))
{
if (verticalScrollPosition != newVerticalScrollPosition)
{
var se:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
se.detail = ScrollEventDetail.THUMB_POSITION;
se.direction = ScrollEventDirection.VERTICAL;
se.delta = newVerticalScrollPosition - verticalScrollPosition;
se.position = newVerticalScrollPosition;
verticalScrollPosition = newVerticalScrollPosition;
dispatchEvent(se);
}
// bail if we page faulted
if (!iteratorValid)
{
keySelectionPending = true;
return;
}
}
bShiftKey = shiftKey;
bCtrlKey = ctrlKey;
lastKey = code;
finishKeySelection();
}
/**
* Sets selected items based on the <code>caretIndex</code> and
* <code>anchorIndex</code> properties.
* Called by the keyboard selection handlers
* and by the <code>updateDisplayList()</code> method in case the
* keyboard selection handler
* got a page fault while scrolling to get more items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function finishKeySelection():void
{
var uid:String;
var rowCount:int = listItems.length;
var partialRow:int = (rowInfo[rowCount-1].y + rowInfo[rowCount-1].height >
listContent.height) ? 1 : 0;
if (lastKey == Keyboard.PAGE_DOWN)
{
// set caret to last full row of new screen
caretIndex = Math.min(verticalScrollPosition + rowCount - partialRow - 1,
collection.length - 1);
}
var listItem:IListItemRenderer;
var bSelChanged:Boolean = false;
if (bSelectItem && caretIndex - verticalScrollPosition >= 0)
{
if (caretIndex - verticalScrollPosition > listItems.length - 1)
caretIndex = listItems.length - 1 + verticalScrollPosition;
listItem = listItems[caretIndex - verticalScrollPosition][0];
if (listItem)
{
uid = itemToUID(listItem.data);
listItem = visibleData[uid];
if (!bCtrlKey)
{
selectItem(listItem, bShiftKey, bCtrlKey);
bSelChanged = true;
}
if (bCtrlKey)
{
drawItem(listItem, selectedData[uid] != null, uid == highlightUID, true);
}
}
}
if (bSelChanged)
{
var pt:Point = itemRendererToIndices(listItem);
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
evt.itemRenderer = listItem;
dispatchEvent(evt);
}
}
/**
* @private
*/
mx_internal function commitSelectedIndex(value:int):void
{
if (value != -1)
{
value = Math.min(value, collection.length - 1);
var bookmark:CursorBookmark = iterator.bookmark;
var len:int = value - scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
try
{
iterator.seek(CursorBookmark.CURRENT, len);
}
catch (e:ItemPendingError)
{
iterator.seek(bookmark, 0);
// if we can't seek to that spot, try again later.
bSelectedIndexChanged = true;
_selectedIndex = value;
return;
}
var data:Object = iterator.current;
var selectedBookmark:CursorBookmark = iterator.bookmark;
var uid:String = itemToUID(data);
iterator.seek(bookmark, 0);
selectData(uid, data, value, selectedBookmark);
}
else
{
clearSelected();
}
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
/**
* Implementation detail on selecting a data, used by commitSelectedIndex.
* @private
*/
protected function selectData(uid:String, data:Object,
index:int, selectedBookmark:CursorBookmark):void
{
if (!selectedData[uid])
{
if (visibleData[uid])
selectItem(visibleData[uid], false, false);
else
{
clearSelected();
addSelectionData(uid, new ListBaseSelectionData(data, index, approximate));
_selectedIndex = index;
caretIndex = index;
caretBookmark = selectedBookmark;
anchorIndex = index;
anchorBookmark = selectedBookmark;
_selectedItem = data;
}
}
}
/**
* @private
*/
mx_internal function commitSelectedIndices(indices:Array):void
{
// trace("setting indices");
clearSelected();
try
{
collectionIterator.seek(CursorBookmark.FIRST, 0);
}
catch (e:ItemPendingError)
{
e.addResponder(new ItemResponder(selectionIndicesPendingResultHandler, selectionIndicesPendingFailureHandler,
new ListBaseSelectionDataPending(true, 0, indices, CursorBookmark.FIRST, 0)));
return;
}
setSelectionIndicesLoop(0, indices, true);
}
/**
* @private
*/
private function setSelectionIndicesLoop(index:int, indices:Array, firstTime:Boolean = false):void
{
while (indices.length)
{
if (index != indices[0])
{
try
{
collectionIterator.seek(CursorBookmark.CURRENT, indices[0] - index);
}
catch (e:ItemPendingError)
{
e.addResponder(new ItemResponder(selectionIndicesPendingResultHandler, selectionIndicesPendingFailureHandler,
new ListBaseSelectionDataPending(firstTime, index, indices, CursorBookmark.CURRENT, indices[0] - index)));
return;
}
}
index = indices[0];
indices.shift();
var data:Object = collectionIterator.current;
if (firstTime)
{
_selectedIndex = index;
_selectedItem = data;
caretIndex = index;
caretBookmark = collectionIterator.bookmark;
anchorIndex = index;
anchorBookmark = collectionIterator.bookmark;
firstTime = false;
}
addSelectionData(itemToUID(data), new ListBaseSelectionData(data, index, false));
// trace("uid = " + itemToUID(data));
}
if (initialized)
updateList();
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
/**
* @private
*/
private function commitSelectedItem(data:Object, clearFirst:Boolean = true):void
{
if (clearFirst)
clearSelected();
if (data != null)
commitSelectedItems([data]);
}
/**
* @private
*/
private function commitSelectedItems(items:Array):void
{
clearSelected();
var useFind:Boolean = collection.sort != null;
try
{
collectionIterator.seek(CursorBookmark.FIRST, 0);
}
catch (e:ItemPendingError)
{
e.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
new ListBaseSelectionDataPending(useFind, 0, items, null, 0)));
return;
}
setSelectionDataLoop(items, 0, useFind);
}
/**
* @private
*/
private function setSelectionDataLoop(items:Array, index:int, useFind:Boolean = true):void
{
var uid:String;
if (useFind)
{
while (items && items.length)
{
var item:Object = items.pop();
uid = itemToUID(item);
try
{
collectionIterator.findAny(item);
}
catch (e1:ItemPendingError)
{
items.push(item);
e1.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
new ListBaseSelectionDataPending(useFind, 0, items, null, 0)));
return;
}
var bookmark:CursorBookmark = collectionIterator.bookmark;
var viewIndex:int = bookmark.getViewIndex();
if (viewIndex >= 0)
{
addSelectionData(uid, new ListBaseSelectionData(item, viewIndex, true));
}
else
{
try
{
collectionIterator.seek(CursorBookmark.FIRST, 0);
}
catch (e2:ItemPendingError)
{
e2.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
new ListBaseSelectionDataPending(false, 0, items, CursorBookmark.FIRST, 0)));
return;
}
// collection doesn't support indexes from bookmarks so
// try again w/o using bookmarks
items.push(item);
setSelectionDataLoop(items, 0, false);
return;
}
if (items.length == 0)
{
_selectedIndex = viewIndex;
_selectedItem = item;
caretIndex = viewIndex;
caretBookmark = collectionIterator.bookmark;
anchorIndex = viewIndex;
anchorBookmark = collectionIterator.bookmark;
}
}
}
else
{
while (items && items.length && !collectionIterator.afterLast)
{
var n:int = items.length;
var data:Object = collectionIterator.current;
for (var i:int = 0; i < n; i++)
{
if (data == items[i])
{
uid = itemToUID(data);
addSelectionData(uid, new ListBaseSelectionData(data, index, false));
items.splice(i, 1);
if (items.length == 0)
{
_selectedIndex = index;
_selectedItem = data;
caretIndex = index;
caretBookmark = collectionIterator.bookmark;
anchorIndex = index;
anchorBookmark = collectionIterator.bookmark;
}
break;
}
}
try
{
collectionIterator.moveNext();
index++;
}
catch (e2:ItemPendingError)
{
e2.addResponder(new ItemResponder(selectionDataPendingResultHandler, selectionDataPendingFailureHandler,
new ListBaseSelectionDataPending(false, index, items, CursorBookmark.CURRENT, 1)));
return;
}
}
}
if (initialized)
updateList();
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
/**
* @private
*/
private function clearSelectionData():void
{
selectedData = {};
firstSelectionData = null;
}
/**
* @private
*/
mx_internal function addSelectionData(uid:String, selectionData:ListBaseSelectionData):void
{
if (firstSelectionData != null)
firstSelectionData.prevSelectionData = selectionData;
selectionData.nextSelectionData = firstSelectionData;
firstSelectionData = selectionData;
selectedData[uid] = selectionData;
}
/**
* @private
*/
private function removeSelectionData(uid:String):void
{
var curSelectionData:ListBaseSelectionData = selectedData[uid];
if (firstSelectionData == curSelectionData)
firstSelectionData = curSelectionData.nextSelectionData;
if (curSelectionData.prevSelectionData != null)
curSelectionData.prevSelectionData.nextSelectionData = curSelectionData.nextSelectionData;
if (curSelectionData.nextSelectionData != null)
curSelectionData.nextSelectionData.prevSelectionData = curSelectionData.prevSelectionData;
delete selectedData[uid];
}
/**
* Sets up the effect for applying the selection indicator.
* The default is a basic alpha tween.
*
* @param indicator A Sprite that contains the graphics depicting selection.
* @param uid The UID of the item being selected which can be used to index
* into a table and track more than one selection effect.
* @param itemRenderer The item renderer that is being shown as selected.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function applySelectionEffect(indicator:Sprite, uid:String,
itemRenderer:IListItemRenderer):void
{
var selectionDuration:Number =
getStyle("selectionDuration");
if (selectionDuration != 0)
{
indicator.alpha = 0;
selectionTweens[uid] =
new Tween(indicator, 0, 1, selectionDuration, 5);
selectionTweens[uid].addEventListener(TweenEvent.TWEEN_UPDATE,
selectionTween_updateHandler);
selectionTweens[uid].addEventListener(TweenEvent.TWEEN_END,
selectionTween_endHandler);
selectionTweens[uid].setTweenHandlers(onSelectionTweenUpdate,
onSelectionTweenUpdate);
var selectionEasingFunction:Function =
getStyle("selectionEasingFunction") as Function;
if (selectionEasingFunction != null)
selectionTweens[uid].easingFunction = selectionEasingFunction;
}
}
/**
* @private
*/
private function onSelectionTweenUpdate(value:Number):void
{
}
/**
* Makes a copy of the selected items in the order they were
* selected.
*
* @param useDataField <code>true</code> if the array should
* be filled with the actual items or <code>false</code>
* if the array should be filled with the indexes of the items.
*
* @return Array of selected items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function copySelectedItems(useDataField:Boolean = true):Array
{
var tmp:Array = [];
var curSelectionData:ListBaseSelectionData = firstSelectionData;
while (curSelectionData != null)
{
if (useDataField)
tmp.push(curSelectionData.data);
else
tmp.push(curSelectionData.index);
curSelectionData = curSelectionData.nextSelectionData;
}
return tmp;
}
//--------------------------------------------------------------------------
//
// Methods: Scrolling
//
//--------------------------------------------------------------------------
/**
* Returns the data provider index for the item at the first visible
* row and column for the given scroll positions.
*
* @param horizontalScrollPosition The <code>horizontalScrollPosition</code>
* property value corresponding to the scroll position.
* @param verticalScrollPosition The <code>verticalScrollPosition</code>
* property value corresponding to the scroll position.
*
* @return The data provider index.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function scrollPositionToIndex(horizontalScrollPosition:int,
verticalScrollPosition:int):int
{
return iterator ? verticalScrollPosition : -1;
}
/**
* Ensures that the data provider item at the given index is visible.
* If the item is visible, the <code>verticalScrollPosition</code>
* property is left unchanged even if the item is not the first visible
* item. If the item is not currently visible, the
* <code>verticalScrollPosition</code>
* property is changed make the item the first visible item, unless there
* aren't enough rows to do so because the
* <code>verticalScrollPosition</code> value is limited by the
* <code>maxVerticalScrollPosition</code> property.
*
* @param index The index of the item in the data provider.
*
* @return <code>true</code> if <code>verticalScrollPosition</code> changed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function scrollToIndex(index:int):Boolean
{
var newVPos:int;
if (index >= verticalScrollPosition + listItems.length - lockedRowCount - offscreenExtraRowsBottom || index < verticalScrollPosition)
{
newVPos = Math.min(index, maxVerticalScrollPosition);
verticalScrollPosition = newVPos;
return true;
}
return false;
}
/**
* Adjusts the renderers in response to a change
* in scroll position.
*
* <p>The list classes attempt to optimize scrolling
* when the scroll position has changed by less than
* the number of visible rows. In that situation,
* some rows are unchanged and just need to be moved,
* other rows are removed and then new rows are added.
* If the scroll position changes too much, all old rows are removed
* and new rows are added by calling the <code>makeRowsAndColumns()</code>
* method for the entire viewable area.</p>
*
* @param pos The new scroll position.
*
* @param deltaPos The change in position. It is always
* a positive number.
*
* @param scrollUp <code>true</code> if scroll position
* is getting smaller.
*
* @see mx.controls.listClasses.ListBase#makeRowsAndColumns()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function scrollVertically(pos:int, deltaPos:int,
scrollUp:Boolean):void
{
// trace("scrollVertically " + pos);
var i:int;
var j:int;
var r:IListItemRenderer;
var item:IListItemRenderer;
var numRows:int;
var numCols:int;
var uid:String;
var visibleY:Number;
var curY:Number;
var rowCount:int = rowInfo.length;
if(rowCount > listItems.length)
{
rowCount = listItems.length;
}
var columnCount:int = listItems[0].length;
var cursorPos:CursorBookmark;
var moveBlockDistance:Number = 0;
visibleY = (lockedRowCount > 0) ? rowInfo[lockedRowCount - 1].y + rowInfo[lockedRowCount - 1].height : rowInfo[0].y;
if (scrollUp)
{
// find first fully visible row not spanning onto the screen;
// trace("visibleY = " + visibleY);
for (i = lockedRowCount; i < rowCount; i++)
{
if (rowInfo[i].y >= visibleY)
break;
}
var startRow:int = i;
// measure how far we have to move by measuring each row
for (i; i < deltaPos + startRow; i++)
{
// after we shift the items, see if any are still visible
moveBlockDistance += rowInfo[i].height;
try
{
iterator.moveNext();
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
// trace("itemPending in ScrollVertically");
return;
}
}
// trace("moveBlockDistance = " + moveBlockDistance);
// shift rows upward and toss invisible ones.
for (i = lockedRowCount; i < rowCount; i++)
{
numCols = Math.min(columnCount,listItems[i].length);
// if row is not visible, toss it
if (i < deltaPos + lockedRowCount)
{
destroyRow(i, numCols);
}
else if (deltaPos > 0)
{
// after we shift the items, see if any are still visible
for (j = 0; j < numCols; j++)
{
r = listItems[i][j];
r.move(r.x, r.y - moveBlockDistance);
if (r.data && r is IDropInListItemRenderer)
IDropInListItemRenderer(r).listData.rowIndex = i;
rowMap[r.name].rowIndex = i;
}
rowInfo[i].y -= moveBlockDistance;
uid = rowInfo[i].uid;
if (uid)
{
// This assumes all the selection indicators in this row are at
// the same 'y' position
moveIndicators(uid, -moveBlockDistance, false);
}
}
}
// trace("tossed " + deltaPos + " " + rowCount);
// deltaPos is now the number of rows we tossed
if (deltaPos)
{
for (i = lockedRowCount + deltaPos; i < rowCount; i++)
{
numCols = listItems[i].length;
for (j = 0; j < numCols; j++)
{
// trace("compacting " + i + " " + j);
// compact the array
r = listItems[i][j];
if (r.data && r is IDropInListItemRenderer)
IDropInListItemRenderer(r).listData.rowIndex = i - deltaPos;
rowMap[r.name].rowIndex = i - deltaPos;
listItems[i - deltaPos][j] = r;
}
if (listItems[i - deltaPos].length > numCols)
listItems[i-deltaPos].splice(numCols);
// if no columns, make destination row empty. Normally this is filled in by
// makeRowsAndColumns, but if it page faults it won't fill it in
// but the item in [i - deltapos] is already on the free list
if (!numCols)
listItems[i - deltaPos].splice(0);
rowInfo[i - deltaPos] = rowInfo[i];
}
listItems.splice(rowCount - deltaPos);
rowInfo.splice(rowCount - deltaPos);
// trace("listItems.length = " + listItems.length);
}
if(rowInfo && rowInfo.length > 0)
curY = rowInfo[rowCount - deltaPos - 1].y + rowInfo[rowCount - deltaPos - 1].height;
else
curY = 0;
cursorPos = iterator.bookmark;
try
{
iterator.seek(CursorBookmark.CURRENT, rowCount - lockedRowCount - deltaPos);
}
catch (e1:ItemPendingError)
{
// trace("IPE in scrollVertically");
lastSeekPending = new ListBaseSeekPending(cursorPos, 0)
e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
// we don't do anything here and will repaint when the rows arrive
}
// fill it in
makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, rowCount - deltaPos);
iterator.seek(cursorPos, 0);
}
else
{
// scrolling down is different because rows are locked to top.
// instead of measuring how much space we lost, we make the rows requested
// and then toss as many (including 0) rows as needed to make room for the
// new rows
// copy the old rows
curY = 0;
if (lockedRowCount > 0)
curY = rowInfo[lockedRowCount - 1].y + rowInfo[lockedRowCount - 1].height;
else
curY = rowInfo[0].y
// insert slots to be filled by new rows
for (i = 0; i < deltaPos; i++)
{
listItems.splice(lockedRowCount, 0, null);
rowInfo.splice(lockedRowCount, 0, null);
}
try
{
iterator.seek(CursorBookmark.CURRENT, -deltaPos);
}
catch (e2:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, -deltaPos)
e2.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
cursorPos = iterator.bookmark;
var actual:Point = makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, lockedRowCount, true, deltaPos);
// trace("made " + actual.y);
iterator.seek(cursorPos, 0);
if (actual.y == 0)
{
// no more rows, set verticalScrollPosition to 0, restore the rows and leave
verticalScrollPosition = 0;
rowInfo.splice(lockedRowCount, deltaPos);
listItems.splice(lockedRowCount, deltaPos);
}
// measure how far we have to move by measuring each new row
for (i = 0; i < actual.y; i++)
{
moveBlockDistance += rowInfo[lockedRowCount + i].height;
}
// trace("moveBlockDistance = " + moveBlockDistance);
var row:Array;
var rowData:Object;
// trace("curY = " + curY);
var deltaY:Number;
curY += moveBlockDistance;
// trace("curY = " + curY);
// fix up positions of old rows and delete any that fell off bottom
for (i = lockedRowCount + actual.y; i < listItems.length; i++)
{
row = listItems[i];
rowData = rowInfo[i];
var deleteRow:Boolean = false;
deltaY = curY - rowData.y;
// trace("deltaY = " + deltaY + " curY = " + curY + " newRowIndex = " + newRowIndex);
rowData.y = curY;
if (row.length)
{
for (j = 0; j < row.length; j++)
{
item = row[j];
item.move(item.x, item.y + deltaY);
if (item.y >= listContent.height)
{
deleteRow = true;
}
if (!deleteRow)
{
rowMap[item.name].rowIndex += deltaPos;
}
}
}
else
{
if (rowData.y >= listContent.height)
deleteRow = true;
}
uid = rowInfo[i].uid;
if (deleteRow)
{
var oldRow:Array = listItems[i];
if (oldRow.length && oldRow[0].data)
{
removeIndicators(uid);
}
for (j = 0; j < oldRow.length; j++)
{
if (oldRow[j] && oldRow[j].data)
{
delete visibleData[uid];
addToFreeItemRenderers(oldRow[j]);
}
}
listItems.splice(i, 1);
rowInfo.splice(i, 1);
i--; // backup one cuz we deleted one
}
if (uid)
{
// This assumes all the selection indicators in this row are at
// the same 'y' position
moveIndicators(uid, curY, true);
if (selectionIndicators[uid])
selectionIndicators[uid].y = curY;
if (highlightUID == uid)
highlightIndicator.y = curY;
if (caretUID == uid)
caretIndicator.y = curY;
}
curY += rowData.height;
}
rowCount = listItems.length;
}
}
private function destroyRow(i:int, numCols:int):void
{
var r:IListItemRenderer;
var uid:String = rowInfo[i].uid;
removeIndicators(uid);
for (var j:int = 0; j < numCols; j++)
{
r = listItems[i][j];
if (r.data)
delete visibleData[uid];
addToFreeItemRenderers(r);
// we don't seem to be doing this consistently throughout the code?
// listContent.removeChild(DisplayObject(r));
}
}
private function moveRowVertically(i:int, numCols:int, moveBlockDistance:Number):void
{
var r:IListItemRenderer;
for (var j:int = 0; j < numCols; j++)
{
r = listItems[i][j];
r.move(r.x, r.y + moveBlockDistance);
}
rowInfo[i].y += moveBlockDistance;
}
private function shiftRow(oldIndex:int, newIndex:int, numCols:int, shiftItems:Boolean):void
{
var r:IListItemRenderer;
for (var j:int = 0; j < numCols; j++)
{
r = listItems[oldIndex][j];
if (shiftItems)
{
listItems[newIndex][j] = r;
rowMap[r.name].rowIndex = newIndex;
}
// this is sort of a hack to accomodate the fact that
// scrolling down does a splice which throws off these values.
// probably better to call shiftRow with different parameters?
else
{
rowMap[r.name].rowIndex = oldIndex;
}
}
}
/**
* @copy mx.controls.listClasses.ListBase#moveIndicatorsVertically()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function moveIndicatorsVertically(uid:String, moveBlockDistance:Number):void
{
if (uid)
{
if (selectionIndicators[uid])
selectionIndicators[uid].y += moveBlockDistance;
if (highlightUID == uid)
highlightIndicator.y += moveBlockDistance;
if (caretUID == uid)
caretIndicator.y += moveBlockDistance;
}
}
/**
* @copy mx.controls.listClasses.ListBase#moveIndicatorsHorizontally()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function moveIndicatorsHorizontally(uid:String, moveBlockDistance:Number):void
{
if (uid)
{
if (selectionIndicators[uid])
selectionIndicators[uid].x += moveBlockDistance;
if (highlightUID == uid)
highlightIndicator.x += moveBlockDistance;
if (caretUID == uid)
caretIndicator.x += moveBlockDistance;
}
}
private function sumRowHeights(startRowIdx:int, endRowIdx:int):Number
{
var sum:Number = 0;
for (var i:int = startRowIdx ; i <= endRowIdx; i++)
sum += rowInfo[i].height;
return sum;
}
/**
* Adjusts the renderers in response to a change
* in scroll position.
*
* <p>The list classes attempt to optimize scrolling
* when the scroll position has changed by less than
* the number of visible rows. In that situation
* some rows are unchanged and just need to be moved,
* other rows are removed and then new rows are added.
* If the scroll position changes too much, all old rows are removed
* and new rows are added by calling the <code>makeRowsAndColumns()</code>
* method for the entire viewable area.</p>
*
* <p>Not implemented in AdvancedListBase because the default list
* is single column and therefore doesn't scroll horizontally.</p>
*
* @param pos The new scroll position.
* @param deltaPos The change in position. It is always
* a positive number.
* @param scrollUp <code>true</code> if scroll position
* is getting smaller.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void
{
// update visible columns
// translate vertical logic here
}
/**
* Configures the ScrollBars based on the number of rows and columns and
* viewable rows and columns.
* This method is called from the <code>updateDisplayList()</code> method
* after the rows and columns have been updated.
* The method should figures out what parameters to pass into the
* <code>setScrollBarProperties()</code> to properly set the ScrollBars up.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function configureScrollBars():void
{
}
/**
* Interval function that scrolls the list up or down
* if the mouse goes above or below the list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragScroll():void
{
var slop:Number = 0;
var scrollInterval:Number;
var oldPosition:Number;
var d:Number;
var scrollEvent:ScrollEvent;
// sometimes, we'll get called even if interval has been cleared
if (dragScrollingInterval == 0)
return;
const minScrollInterval:Number = 30;
if (DragManager.isDragging)
{
slop = viewMetrics.top
+ (variableRowHeight ? getStyle("fontSize") / 4 : rowHeight);
}
clearInterval(dragScrollingInterval);
if (mouseY < slop)
{
oldPosition = verticalScrollPosition;
verticalScrollPosition = Math.max(0, oldPosition - 1);
if (DragManager.isDragging)
{
scrollInterval = 100;
}
else
{
d = Math.min(0 - mouseY - 30, 0);
// quadratic relation between distance and scroll speed
scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
}
dragScrollingInterval = setInterval(dragScroll, scrollInterval);
if (oldPosition != verticalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.position = verticalScrollPosition;
scrollEvent.delta = verticalScrollPosition - oldPosition;
dispatchEvent(scrollEvent);
}
}
else if (mouseY > (unscaledHeight - slop))
{
oldPosition = verticalScrollPosition;
verticalScrollPosition = Math.min(maxVerticalScrollPosition, verticalScrollPosition + 1);
if (DragManager.isDragging)
{
scrollInterval = 100;
}
else
{
d = Math.min(mouseY - unscaledHeight - 30, 0);
scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
}
dragScrollingInterval = setInterval(dragScroll, scrollInterval);
if (oldPosition != verticalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.position = verticalScrollPosition;
scrollEvent.delta = verticalScrollPosition - oldPosition;
dispatchEvent(scrollEvent);
}
}
else
{
dragScrollingInterval = setInterval(dragScroll, 15);
}
if (DragManager.isDragging && lastDragEvent && oldPosition != verticalScrollPosition)
{
dragOverHandler(lastDragEvent);
}
}
/**
* @private
* Stop the drag scrolling callback.
*/
mx_internal function resetDragScrolling():void
{
if (dragScrollingInterval != 0)
{
clearInterval(dragScrollingInterval);
dragScrollingInterval = 0;
}
}
//--------------------------------------------------------------------------
//
// Methods: Drag and drop
//
//--------------------------------------------------------------------------
/**
* Adds the selected items to the DragSource object as part of a
* drag-and-drop operation.
* Override this method to add other data to the drag source.
*
* @param dragSource The DragSource object to which to add the data.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function addDragData(dragSource:Object):void // actually a DragSource
{
// The Halo drag drop data format
dragSource.addHandler(copySelectedItems, "items");
// The Spark drag drop data format
dragSource.addHandler(copySelectedItemsForDragDrop, "itemsByIndex");
// Calculate the index of the focus item within the vector
// of ordered items returned for the "itemsByIndex" format.
var caretIndex:int = 0;
var draggedIndices:Array = selectedIndices;
var count:int = draggedIndices.length;
for (var i:int = 0; i < count; i++)
{
if (mouseDownIndex > draggedIndices[i])
caretIndex++;
}
dragSource.addData(caretIndex, "caretIndex");
}
/**
* Returns the index where the dropped items should be added
* to the drop target.
*
* @param event A DragEvent that contains information about
* the position of the mouse. If <code>null</code> the
* method should return the <code>dropIndex</code> value from the
* last valid event.
*
* @return Index where the dropped items should be added.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function calculateDropIndex(event:DragEvent = null):int
{
if (event)
{
var item:IListItemRenderer;
var pt:Point = new Point(event.localX, event.localY);
pt = DisplayObject(event.target).localToGlobal(pt);
pt = listContent.globalToLocal(pt);
var n:int = listItems.length;
for (var i:int = 0; i < n; i++)
{
if (rowInfo[i].y <= pt.y && pt.y <= rowInfo[i].y + rowInfo[i].height)
{
item = listItems[i][0];
break;
}
}
if (item)
{
lastDropIndex = itemRendererToIndex(item);
}
else
lastDropIndex = collection ? collection.length : 0;
}
return lastDropIndex;
}
/**
* Calculates the y position of the drop indicator
* when performing a drag-and-drop operation.
*
* @param rowCount The number of visible rows in the control.
*
* @param rowNum The row number in the control where the drop indicator should appear.
*
* @return The y axis coordinate of the drop indicator.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function calculateDropIndicatorY(rowCount:Number,
rowNum:int):Number
{
var i:int;
var yy:Number = 0;
if (rowCount && listItems[rowNum].length && listItems[rowNum][0])
{
return listItems[rowNum][0].y - 1
}
for (i = 0; i < rowCount; i++)
{
if (listItems[i].length)
yy += rowInfo[i].height;
else
break;
}
return yy;
}
/**
* Displays a drop indicator under the mouse pointer to indicate that a
* drag and drop operation is allowed and where the items will
* be dropped.
*
* @param event A DragEvent object that contains information as to where
* the mouse is.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function showDropFeedback(event:DragEvent):void
{
if (!dropIndicator)
{
var dropIndicatorClass:Class = getStyle("dropIndicatorSkin");
if (!dropIndicatorClass)
dropIndicatorClass = ListDropIndicator;
dropIndicator = IFlexDisplayObject(new dropIndicatorClass());
var vm:EdgeMetrics = viewMetrics;
drawFocus(true);
dropIndicator.x = 2;
dropIndicator.setActualSize(listContent.width - 4, 4);
dropIndicator.visible = true;
listContent.addChild(DisplayObject(dropIndicator));
if (collection)
dragScroll();
}
var rowNum:Number = calculateDropIndex(event);
if (rowNum >= lockedRowCount)
rowNum -= verticalScrollPosition;
var rc:Number = listItems.length;
if (rowNum >= rc)
rowNum = rc - 1;
if (rowNum < 0)
rowNum = 0;
dropIndicator.y = calculateDropIndicatorY(rc, rowNum);
}
/**
* Hides the drop indicator under the mouse pointer that indicates that a
* drag and drop operation is allowed.
*
* @param event A DragEvent object that contains information about the
* mouse location.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function hideDropFeedback(event:DragEvent):void
{
if (dropIndicator)
{
listContent.removeChild(DisplayObject(dropIndicator));
dropIndicator = null;
drawFocus(false);
if (dragScrollingInterval != 0)
{
clearInterval(dragScrollingInterval);
dragScrollingInterval = 0;
}
}
}
/**
* 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>This method is used for a drag and drop copy.</p>
*
* @param item The item to copy.
*
* @return The copy of the object.
*
* @see mx.utils.ObjectUtil
* @see mx.utils.UIDUtil
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function copyItemWithUID(item:Object):Object
{
var copyObj:Object = ObjectUtil.copy(item);
if (copyObj is IUID)
{
IUID(copyObj).uid = UIDUtil.createUID();
}
else if (copyObj is Object && "mx_internal_uid" in copyObj)
{
copyObj.mx_internal_uid = UIDUtil.createUID();
}
return copyObj;
}
/**
* @private
*/
private function copySelectedItemsForDragDrop():Vector.<Object>
{
// Copy the vector so that we don't modify the original
// since selectedIndices returns a reference.
var draggedIndices:Array = selectedIndices.slice(0, selectedIndices.length);
var result:Vector.<Object> = new Vector.<Object>(draggedIndices.length);
// Sort in the order of the data source
draggedIndices.sort();
// Copy the items
var count:int = draggedIndices.length;
for (var i:int = 0; i < count; i++)
result[i] = dataProvider.getItemAt(draggedIndices[i]);
return result;
}
/**
* @private
* Insert for drag and drop, handles the Spark "itemsByIndex" data format.
*/
private function insertItemsByIndex(dropIndex:int, dragSource:DragSource, event:DragEvent):void
{
var items:Vector.<Object> = dragSource.dataForFormat("itemsByIndex") as Vector.<Object>;
// Copy or move the items. No need to check whether the operation is
// reorder within this list, as ListBase never creates the
// "itemsByIndex" data format. This is a new data format from Spark List only.
collectionIterator.seek(CursorBookmark.FIRST, dropIndex);
var count:int = items.length;
for (var i:int = 0; i < count; i++)
{
if (event.action == DragManager.COPY)
{
collectionIterator.insert(copyItemWithUID(items[i]));
}
else if (event.action == DragManager.MOVE)
{
collectionIterator.insert(items[i]);
}
}
}
/**
* @private
* Insert for drag and drop, handles the Halo "items" data format.
*/
private function insertItems(dropIndex:int, dragSource:DragSource, event:DragEvent):void
{
var items:Array = dragSource.dataForFormat("items") as Array;
if (event.action == DragManager.MOVE &&
dragMoveEnabled &&
event.dragInitiator == this)
{
var indices:Array = selectedIndices;
indices.sort(Array.NUMERIC);
for (var i:int = indices.length - 1; i >= 0; i--)
{
collectionIterator.seek(CursorBookmark.FIRST, indices[i]);
if (indices[i] < dropIndex)
dropIndex--;
collectionIterator.remove();
}
clearSelected(false);
}
collectionIterator.seek(CursorBookmark.FIRST, dropIndex);
for (i = items.length - 1; i >= 0; i--)
{
if (event.action == DragManager.COPY)
{
collectionIterator.insert(copyItemWithUID(items[i]));
}
else if (event.action == DragManager.MOVE)
{
collectionIterator.insert(items[i]);
}
}
}
//--------------------------------------------------------------------------
//
// Methods: Support for pending data
//
//--------------------------------------------------------------------------
/**
* The default failure handler when a seek fails due to a page fault.
*
* @param data The data that caused the error.
*
* @param info Data about a seek operation
* that was interrupted by an ItemPendingError error.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function seekPendingFailureHandler(data:Object,
info:ListBaseSeekPending):void
{
}
/**
* The default result handler when a seek fails due to a page fault.
* This method checks to see if it has the most recent page fault result:
* if not it simply exits; if it does, it sets the iterator to
* the correct position.
*
* @param data The data that caused the error.
*
* @param info Data about a seek operation
* that was interrupted by an ItemPendingError error.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function seekPendingResultHandler(data:Object,
info:ListBaseSeekPending):void
{
// trace("seekPendingResultHandler", this);
if (info != lastSeekPending)
{
return;
}
lastSeekPending = null;
iteratorValid = true;
try
{
iterator.seek(info.bookmark, info.offset);
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(info.bookmark, info.offset)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
if (bSortItemPending)
{
bSortItemPending = false;
adjustAfterSort();
}
itemsSizeChanged = true;
invalidateDisplayList();
}
/**
* @private
*/
private function findPendingFailureHandler(data:Object,
info:ListBaseFindPending):void
{
}
/**
* @private
*/
private function findPendingResultHandler(data:Object,
info:ListBaseFindPending):void
{
// trace("findPendingResultHandler", this);
iterator.seek(info.bookmark, info.offset);
findStringLoop(info.searchString, info.startingBookmark, info.currentIndex, info.stopIndex);
}
/**
* @private
*/
private function selectionPendingFailureHandler(
data:Object,
info:ListBaseSelectionPending):void
{
}
/**
* @private
*/
private function selectionPendingResultHandler(
data:Object,
info:ListBaseSelectionPending):void
{
// trace("selectionPendingResultHandler", this);
iterator.seek(info.bookmark, info.offset);
shiftSelectionLoop(info.incrementing, info.index, info.stopData,
info.transition, info.placeHolder);
}
/**
* @private
*/
private function selectionDataPendingFailureHandler(
data:Object,
info:ListBaseSelectionDataPending):void
{
}
/**
* @private
*/
private function selectionDataPendingResultHandler(
data:Object,
info:ListBaseSelectionDataPending):void
{
// trace("selectionDataPendingResultHandler", this);
if (info.bookmark)
iterator.seek(info.bookmark, info.offset);
setSelectionDataLoop(info.items, info.index, info.useFind);
}
/**
* @private
*/
private function selectionIndicesPendingFailureHandler(
data:Object,
info:ListBaseSelectionDataPending):void
{
}
/**
* @private
*/
private function selectionIndicesPendingResultHandler(
data:Object,
info:ListBaseSelectionDataPending):void
{
// trace("selectionIndicesPendingResultHandler", this);
if (info.bookmark)
iterator.seek(info.bookmark, info.offset);
setSelectionIndicesLoop(info.index, info.items, info.useFind);
}
//--------------------------------------------------------------------------
//
// Methods: Keyboard lookup
//
//--------------------------------------------------------------------------
/**
* Tries to find the next item in the data provider that
* starts with the character in the <code>eventCode</code> parameter.
* You can override this to do fancier typeahead lookups. The search
* starts at the <code>selectedIndex</code> location; if it reaches
* the end of the data provider it starts over from the beginning.
*
* @param eventCode The key that was pressed on the keyboard.
* @return <code>true</code> if a match was found.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function findKey(eventCode:int):Boolean
{
var tmpCode:int = eventCode;
return tmpCode >= 33 &&
tmpCode <= 126 &&
findString(String.fromCharCode(tmpCode));
}
/**
* Finds an item in the list based on a string
* and moves the selection to it. The search
* starts at the <code>selectedIndex</code> location; if it reaches
* the end of the data provider it starts over from the beginning.
*
* @param str The string to match.
* @return <code>true</code> if a match was found.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function findString(str:String):Boolean
{
if (!collection || collection.length == 0)
return false;
var cursorPos:CursorBookmark;
cursorPos = iterator.bookmark;
var stopIndex:int = selectedIndex;
var i:int = stopIndex + 1; // start at next
if (selectedIndex == -1)
{
try
{
iterator.seek(CursorBookmark.FIRST, 0);
}
catch (e1:ItemPendingError)
{
e1.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
CursorBookmark.FIRST, 0, 0, collection.length)));
iteratorValid = false;
return false;
}
stopIndex = collection.length;
i = 0;
}
else
{
try
{
iterator.seek(CursorBookmark.FIRST, stopIndex);
}
catch (e2:ItemPendingError)
{
if (anchorIndex == collection.length - 1)
{
e2.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
CursorBookmark.FIRST, 0, 0, collection.length)));
}
else
{
e2.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
anchorBookmark, 1, anchorIndex + 1, anchorIndex)));
}
iteratorValid = false;
return false;
}
var bMovedNext:Boolean = false;
// If we ran off the end, go back to beginning.
try
{
bMovedNext = iterator.moveNext();
}
catch (e3:ItemPendingError)
{
// Assume we don't fault unless there is more data.
e3.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
anchorBookmark, 1, anchorIndex + 1, anchorIndex)));
iteratorValid = false;
return false;
}
if (!bMovedNext)
{
try
{
iterator.seek(CursorBookmark.FIRST, 0);
}
catch (e4:ItemPendingError)
{
e4.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
CursorBookmark.FIRST, 0, 0, collection.length)));
iteratorValid = false;
return false;
}
stopIndex = collection.length;
i = 0;
}
}
return findStringLoop(str, cursorPos, i, stopIndex);
}
/**
* @private
*/
private function findStringLoop(str:String, cursorPos:CursorBookmark,
i:int, stopIndex:int):Boolean
{
// Search from the current index.
// Jump back to beginning if we hit the end.
for (i; i != stopIndex; i++)
{
var itmStr:String = itemToLabel(iterator.current);
if (itmStr)
{
itmStr = itmStr.substring(0, str.length);
if (str == itmStr || str.toUpperCase() == itmStr.toUpperCase())
{
iterator.seek(cursorPos, 0);
scrollToIndex(i);
commitSelectedIndex(i);
var item:IListItemRenderer = indexToItemRenderer(i);
var pt:Point = itemRendererToIndices(item);
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = item;
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
dispatchEvent(evt);
return true;
}
}
try
{
var more:Boolean = iterator.moveNext();
}
catch (e1:ItemPendingError)
{
e1.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
CursorBookmark.CURRENT, 1, i + 1, stopIndex)));
iteratorValid = false;
return false;
}
// Start from beginning if we hit the end
if (!more && stopIndex != collection.length)
{
i = -1;
try
{
iterator.seek(CursorBookmark.FIRST, 0);
}
catch (e2:ItemPendingError)
{
e2.addResponder(new ItemResponder(
findPendingResultHandler, findPendingFailureHandler,
new ListBaseFindPending(str, cursorPos,
CursorBookmark.FIRST, 0, 0, stopIndex)));
iteratorValid = false;
return false;
}
}
}
iterator.seek(cursorPos, 0);
iteratorValid = true;
return false;
}
//--------------------------------------------------------------------------
//
// Methods: Sorting
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function adjustAfterSort():void
{
var i:int = 0;
for (var p:String in selectedData)
{
i++;
}
var index:int = anchorBookmark ? anchorBookmark.getViewIndex() : -1;
if (index >= 0)
{
// If only one thing selected, then we're done.
if (i == 1)
{
_selectedIndex = anchorIndex = caretIndex = index;
var data:ListBaseSelectionData = selectedData[p];
data.index = index;
}
var newVerticalScrollPosition:int = indexToRow(index);
newVerticalScrollPosition =
Math.min(maxVerticalScrollPosition, newVerticalScrollPosition);
var newHorizontalScrollPosition:int = indexToColumn(index);
newHorizontalScrollPosition =
Math.min(maxHorizontalScrollPosition, newHorizontalScrollPosition);
// Prepare to refresh from there.
var pos:int = scrollPositionToIndex(newHorizontalScrollPosition,
newVerticalScrollPosition);
try
{
iterator.seek(CursorBookmark.CURRENT, pos - index);
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(
CursorBookmark.CURRENT, pos - index)
e.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
return;
}
super.verticalScrollPosition = newVerticalScrollPosition;
if (listType != "vertical")
super.horizontalScrollPosition = newHorizontalScrollPosition;
}
else
{
try
{
iterator.seek(CursorBookmark.FIRST, verticalScrollPosition);
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(
CursorBookmark.FIRST, verticalScrollPosition);
e.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
return;
}
}
// If there's more than one selection, find their new indices.
if (i > 1)
commitSelectedItems(selectedItems);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Initiates a data change effect when there have been changes
* in the data provider.
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function initiateDataChangeEffect(unscaledWidth:Number,
unscaledHeight:Number):void
{
// as optimization, we might get targets from visibleData
// if we know that nothing else has changed.
// otherwise, rerender using our preserved collection view
// store original collection and iterator so we can
// return to using it after the effect has finished
actualCollection = collection;
actualIterator = iterator;
// switch to a view of the collection before the data change
// and call updateDisplayList()
collection = modifiedCollectionView;
modifiedCollectionView.showPreservedState = true;
iterator = collection.createCursor();
var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft,
verticalScrollPosition - offscreenExtraRowsTop);
iterator.seek(CursorBookmark.FIRST,index);
updateDisplayList(unscaledWidth,unscaledHeight);
var targets:Array = [];
var targetHash:Dictionary = new Dictionary(true);
// record the initial set of targets for the
// data change effect
var n:int = listItems.length;
var m:int = 0;
for (var i:int = 0; i < n; i++)
{
var rowItems:Array = listItems[i];
if (rowItems && (rowItems.length > 0))
{
m = rowItems.length;
for (var j:int = 0; j < m; j++)
{
var target:Object = rowItems[j];
if (target)
{
targets.push(target);
targetHash[target] = true;
}
}
}
}
cachedDataChangeEffect.targets = targets;
if (cachedDataChangeEffect.effectTargetHost != this)
cachedDataChangeEffect.effectTargetHost = this;
cachedDataChangeEffect.captureStartValues();
// Now get additional targets that are only in endstate
modifiedCollectionView.showPreservedState = false;
// This is not great from a usability point of view if all the items have
// been added offscreen...we're scrolling the view as well.
// Also, ideally we wouldn't have to do this seek; the iterator
// would notice via events that the modifiedCollectionView had changed
iterator.seek(CursorBookmark.FIRST,index);
itemsSizeChanged = true;
updateDisplayList(unscaledWidth,unscaledHeight);
// Acquire additional targets
var newTargets:Array = [];
var oldTargets:Array = cachedDataChangeEffect.targets;
n = listItems.length;
for (i = 0; i < n; i++)
{
rowItems = listItems[i];
if (rowItems && (rowItems.length > 0))
{
m = rowItems.length;
for (j = 0; j < m; j++)
{
target = rowItems[j];
if (target && !targetHash[target])
{
oldTargets.push(target);
newTargets.push(target);
}
}
}
}
// Get start values for additional targets,
// and end values for all targets
if (newTargets.length > 0)
{
cachedDataChangeEffect.targets = oldTargets;
cachedDataChangeEffect.captureMoreStartValues(newTargets);
}
cachedDataChangeEffect.captureEndValues();
// Do the layout for the control one more time, to ensure
// that only the items visible before the data change
// occurred are visible
modifiedCollectionView.showPreservedState = true;
iterator.seek(CursorBookmark.FIRST,index);
itemsSizeChanged = true;
updateDisplayList(unscaledWidth,unscaledHeight);
initiateSelectionTracking(oldTargets);
// Start the data effect, which will rewind to start state
cachedDataChangeEffect.addEventListener(EffectEvent.EFFECT_END,finishDataChangeEffect);
cachedDataChangeEffect.play();
}
/**
* @private
* Sets up listeners for MoveEvents for a set of renderers. Listeners are only
* created for renderers representing selected items.
*
* This functionality is used by data change effects, to update selections
* when the item renderers move.
*/
private function initiateSelectionTracking(renderers:Array):void
{
var n:int = renderers.length;
for (var i:int = 0; i < n; i++)
{
var renderer:IListItemRenderer = renderers[i] as IListItemRenderer;
if (selectedData[itemToUID(renderer.data)])
{
renderer.addEventListener(mx.events.MoveEvent.MOVE,rendererMoveHandler);
trackedRenderers.push(renderer);
}
}
}
/**
* @private
* Removes event listeners for MoveEvents set up by initiateSelectionTracking().
*
*/
private function terminateSelectionTracking():void
{
var n:int = trackedRenderers.length;
for (var i:int = 0; i < n; i++)
{
var renderer:IListItemRenderer = trackedRenderers[i] as IListItemRenderer;
renderer.removeEventListener(mx.events.MoveEvent.MOVE,rendererMoveHandler);
}
trackedRenderers = [];
}
/**
* Removes an item renderer if a data change effect is running.
* The item renderer must correspond to data that has already
* been removed from the data provider collection.
*
* This function will be called by a <code>RemoveItemAction</code>
* effect as part of a data change effect to specify the point
* at which a data item ceases to displayed by the control using
* an item renderer.
*
* @param item The item renderer to remove from the control's layout.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeDataEffectItem(item:Object):void
{
// Shouldn't need to check this, but there
// currently appears to be a race condition
// (perhaps a bug in when effectEnd occurs)
if (modifiedCollectionView)
modifiedCollectionView.removeItem(dataItemWrappersByRenderer[item]);
// again, this should not really be necessary
iterator.seek(CursorBookmark.CURRENT);
// force validation, otherwise it can be delayed until the
// end of a data effect
if (mx_internal::invalidateDisplayListFlag)
{
itemsSizeChanged = true;
validateDisplayList();
}
else
invalidateList();
}
/**
* Adds an item renderer if a data change effect is running.
* The item renderer should correspond to a recently added
* data item in the data provider's collection that isn't
* yet being displayed.
*
* This function will be called by an <code>AddItemAction</code>
* effect as part of a data change effect to specify the point
* at which a data item added to a collection begins to be displayed
* by the control using an item renderer.
*
* @param item The item renderer to add to the control's layout.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addDataEffectItem(item:Object):void
{
if (modifiedCollectionView)
modifiedCollectionView.addItem(dataItemWrappersByRenderer[item]);
// if it was previously empty, set cursor to first item
// We shouldn't really have to do this here.
if (iterator.afterLast)
iterator.seek(CursorBookmark.FIRST)
else
iterator.seek(CursorBookmark.CURRENT);
// force validation, otherwise it can be delayed until the
// end of a data effect
if (mx_internal::invalidateDisplayListFlag)
{
itemsSizeChanged = true;
validateDisplayList();
}
else
{
invalidateList();
}
}
/**
* Temporarily stops an item renderer from being positioned
* by the control's layout algorithm.
*
* This function will be called
* as part of a data change effect if the item renderers corresponding
* to certain data items need to move outside the normal positions
* of item renderers in the control.
*
* @param item The data for the item renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function unconstrainRenderer(item:Object):void
{
unconstrainedRenderers[item] = true;
}
/**
* Returns the value for a particular semantic property of
* an item renderer, or null if that property is not defined,
* while a data effect is running.
*
* This function is used by filters in data change effects
* to restrict effects to renderers corresponding to removed
* or added data items.
*
* @param target An item renderer.
*
* @param semanticProperty The semantic property of the renderer
* whose value will be returned.
*
* @return The value for a particular semantic property of
* an item renderer, or null if that property is not defined,
* while a data effect is running.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getRendererSemanticValue(target:Object,semanticProperty:String):Object
{
// this is really only a temporary solution, assuming single boolean semantics
// e.g. "removed" or "added"
return (modifiedCollectionView.getSemantics(dataItemWrappersByRenderer[target]) == semanticProperty);
}
/**
* Returns <code>true</code> if an item renderer is no longer being positioned
* by the list's layout algorithm while a data change effect is
* running as a result of a call to the <code>unconstrainRenderer()</code> method.
*
* @param item An item renderer.
*
* @return <code>true</code> if an item renderer is no longer being positioned
* by the list's layout algorithm.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function isRendererUnconstrained(item:Object):Boolean
{
return (unconstrainedRenderers[item] != null);
}
/**
* Cleans up after a data change effect has finished running
* by restoring the original collection and iterator and removing
* any cached values used by the effect. This method is called by
* the Flex framework; you do not need to call it from your code.
*
* @param event The effect that has finished running.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function finishDataChangeEffect(event:EffectEvent):void
{
// We are doing a bunch of stuff here, possibly more
// than necessary, with the goal of guaranteeing that we leave
// the control in an acceptable state.
collection = actualCollection;
actualCollection = null;
modifiedCollectionView = null;
iterator = actualIterator;
runningDataEffect = false;
unconstrainedRenderers = {};
// rendererChanged = true;
terminateSelectionTracking();
// at the end of a data effect, in order to make sure we
// can re-use the renderers intelligently, we have to
// re-key the item renderers in visibleData
var newVisibleData:Object = {};
for each (var item:Object in visibleData)
if (item.data)
newVisibleData[itemToUID(item.data)] = item;
visibleData = newVisibleData;
// TODO This code seems redundant with the code in cleanupAfterDataChangeEffect();
// need to investigate what's actually needed & where.
var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition - offscreenExtraRowsTop);
iterator.seek(CursorBookmark.FIRST,index);
callLater(cleanupAfterDataChangeEffect);
}
/**
* @private
*
* Initiates a somewhat expensive relayout of the control after finishing up
* a data change effect.
*/
private function cleanupAfterDataChangeEffect():void
{
if (runningDataEffect || runDataEffectNextUpdate)
return;
var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition - offscreenExtraRowsTop);
iterator.seek(CursorBookmark.FIRST,index);
dataEffectCompleted = true;
itemsSizeChanged = true;
//rendererChanged = true;
invalidateList();
dataItemWrappersByRenderer = new Dictionary();
}
/**
* Called from the <code>updateDisplayList()</code> method to
* adjust the size and position of list content.
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function adjustListContent(unscaledWidth:Number = -1,
unscaledHeight:Number = -1):void
{
if (unscaledHeight < 0)
{
unscaledHeight = oldUnscaledHeight;
unscaledWidth = oldUnscaledWidth
}
var lcx:Number = viewMetrics.left + listContent.leftOffset;
var lcy:Number = viewMetrics.top + listContent.topOffset;
listContent.move(lcx, lcy);
var ww:Number = Math.max(0, listContent.rightOffset) - lcx - viewMetrics.right;
var hh:Number = Math.max(0, listContent.bottomOffset) - lcy - viewMetrics.bottom;
listContent.setActualSize(unscaledWidth + ww, unscaledHeight + hh);
}
/**
* @private
*
* Called by updateDisplayList() to make adjustments to vertical and
* horizontal scroll position.
*/
private function adjustScrollPosition():void
{
if (!isNaN(horizontalScrollPositionPending))
{
var hPos:Number = Math.min(horizontalScrollPositionPending,
maxHorizontalScrollPosition);
horizontalScrollPositionPending = NaN;
super.horizontalScrollPosition = hPos;
}
if (!isNaN(verticalScrollPositionPending))
{
var vPos:Number = Math.min(verticalScrollPositionPending,
maxVerticalScrollPosition);
verticalScrollPositionPending = NaN;
super.verticalScrollPosition = vPos;
}
}
/**
* @private
*
* Called by updateDisplayList() to remove existing item renderers
* and clean up various caching structures when renderer changes.
*/
protected function purgeItemRenderers():void
{
rendererChanged = false;
while (listItems.length)
{
var row:Array = listItems.pop();
while (row.length)
{
var item:IListItemRenderer = IListItemRenderer(row.pop());
if (item)
{
listContent.removeChild(DisplayObject(item));
if (dataItemWrappersByRenderer[item])
delete visibleData[itemToUID(dataItemWrappersByRenderer[item])];
else
delete visibleData[itemToUID(item.data)];
// addToFreeItemRenderers(item);
}
}
}
while (freeItemRenderers.length)
{
var freeRenderer:DisplayObject = DisplayObject(freeItemRenderers.pop());
if (freeRenderer.parent)
listContent.removeChild(freeRenderer);
}
rowMap = {};
rowInfo = [];
}
/**
* @private
*
* Called by updateDisplayList() to remove existing item renderers
* and clean up various internal structures at the end of running
* a data change effect.
*/
private function partialPurgeItemRenderers():void
{
dataEffectCompleted = false;
// We make sure all free and reserved itemRenderers are de-parented
// after a data change effect finishes, and also blow away visibleData
// and rowMap. Ideally, this would not be necessary, but it seems
// safest at the moment.
// Note that the makeRowsAndColumns pass after this function is
// called should not make any changes to listItems.
while (freeItemRenderers.length)
{
var freeRenderer:DisplayObject = DisplayObject(freeItemRenderers.pop());
if (freeRenderer.parent)
listContent.removeChild(freeRenderer);
}
for (var p:String in reservedItemRenderers)
{
freeRenderer = DisplayObject(reservedItemRenderers[p]);
if (freeRenderer.parent)
listContent.removeChild(freeRenderer);
}
reservedItemRenderers = {}
rowMap = {};
visibleData = {};
}
/**
* @private
*
* Called by updateDisplayList()
*/
private function reduceRows(rowIndex:int):void
{
while (rowIndex >= 0)
{
if (rowInfo[rowIndex].y >= listContent.height)
{
var colLen:int = listItems[rowIndex].length;
for (var j:int = 0; j < colLen; j++)
addToFreeItemRenderers(listItems[rowIndex][j]);
var uid:String = rowInfo[rowIndex].uid;
delete visibleData[uid];
removeIndicators(uid);
listItems.pop(); // remove the row
rowInfo.pop();
rowIndex--;
}
else
break;
}
}
/**
* @private
*
* Called from updateDisplayList()
*/
private function makeAdditionalRows(rowIndex:int):void
{
var cursorPos:CursorBookmark;
// do layout for additional rows
if (iterator)
{
cursorPos = iterator.bookmark;
try
{
iterator.seek(CursorBookmark.CURRENT, listItems.length - lockedRowCount);
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, listItems.length - lockedRowCount)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
// don't do anything, we'll repaint when the data arrives
}
}
var curY:Number = rowInfo[rowIndex].y + rowInfo[rowIndex].height;
// fill it in
makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, rowIndex + 1);
// restore iterator to original position
seekPositionIgnoreError(iterator,cursorPos);
}
/**
* @private
*
* Called from updateDisplayList() to make adjustments to internal
* properties representing selections.
*/
private function adjustSelectionSettings(collectionHasItems:Boolean):void
{
if (bSelectionChanged)
{
bSelectionChanged = false;
// bSelectedIndexChanged can be true if the dp was reset.
// if selectedItem or cousins are also set, we will resolve
// selectedIndex in there and therefore don't need to process
// it
//in each of these cases allow the "unsetting" of a value regardless
//of whether the collection has items
if (bSelectedIndicesChanged
&& (collectionHasItems || (_selectedIndices == null)))
{
bSelectedIndicesChanged = false;
bSelectedIndexChanged = false;
commitSelectedIndices(_selectedIndices);
}
if (bSelectedItemChanged
&& (collectionHasItems || (_selectedItem == null)))
{
bSelectedItemChanged = false;
bSelectedIndexChanged = false;
commitSelectedItem(_selectedItem);
}
if (bSelectedItemsChanged
&& (collectionHasItems || (_selectedItems == null)))
{
bSelectedItemsChanged = false;
bSelectedIndexChanged = false;
commitSelectedItems(_selectedItems);
}
if (bSelectedIndexChanged
&& (collectionHasItems || (_selectedIndex == -1)))
{
commitSelectedIndex(_selectedIndex);
bSelectedIndexChanged = false;
}
}
}
/**
* @private
*
* Called from updateDisplayList() to seek to a cursorPosition while ignoring any errors
*/
private function seekPositionIgnoreError(iterator:IViewCursor, cursorPos:CursorBookmark):void
{
if (iterator)
{
try
{
iterator.seek(cursorPos, 0);
}
catch (e:ItemPendingError)
{
// we don't recover here since we'd only get here if the first seek failed.
}
}
}
/**
* @private
*
* A convenience function to move the iterator to the next position and handle
* errors if necessary.
*/
private function seekNextSafely(iterator:IViewCursor, pos:int):Boolean
{
try
{
iterator.moveNext();
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
// assumption is that iteratorValid was true when we were called.
return iteratorValid;
}
private function seekPreviousSafely(iterator:IViewCursor, pos:int):Boolean
{
try
{
iterator.movePrevious();
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
// assumption is that iteratorValid was true when we were called.
return iteratorValid;
}
// TODO make this mx_internal?
/**
* @private
*/
protected function seekPositionSafely(index:int):Boolean
{
try
{
iterator.seek(CursorBookmark.FIRST, index);
}
catch (e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, index);
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
return iteratorValid;
}
/**
* @private
* for automation delegate access
*/
mx_internal function getListVisibleData():Object
{
return visibleData;
}
/**
* @private
* for automation delegate access
*/
mx_internal function getItemUID(data:Object):String
{
return itemToUID(data);
}
/**
* @private
* for automation delegate access
*/
mx_internal function getItemRendererForMouseEvent(event:MouseEvent):IListItemRenderer
{
return mouseEventToItemRenderer(event);
}
/**
* @private
* for automation delegate access
*/
mx_internal function getListContentHolder():AdvancedListBaseContentHolder
{
return listContent;
}
/**
* @private
* for automation delegate access
*/
mx_internal function getRowInfo():Array
{
return rowInfo;
}
/**
* @private
* for automation delegate access
*/
mx_internal function convertIndexToRow(index:int):int
{
return indexToRow(index);
}
/**
* @private
* for automation delegate access
*/
mx_internal function convertIndexToColumn(index:int):int
{
return indexToColumn(index);
}
/**
* @private
* for automation delegate access
*/
mx_internal function getCaretIndex():int
{
return caretIndex;
}
/**
* @private
* for automation delegate access
*/
mx_internal function getIterator():IViewCursor
{
return iterator;
}
/**
* @private
* Move the indicators up or down by the given offset.
* This method assumes that all the selection indicators in
* this row are at the same 'y' position.
*/
protected function moveIndicators(uid:String, offset:int, absolute:Boolean):void
{
if (selectionIndicators[uid])
{
if (absolute)
selectionIndicators[uid].y = offset;
else
selectionIndicators[uid].y += offset;
}
if (highlightUID == uid)
{
if (absolute)
highlightIndicator.y = offset;
else
highlightIndicator.y += offset;
}
if (caretUID == uid)
{
if (absolute)
caretIndicator.y = offset;
else
caretIndicator.y += offset;
}
}
/**
* Internal version of setting columnWidth
* without invalidation or notification.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function setColumnWidth(value:Number):void
{
_columnWidth = value;
}
/**
* Sets the <code>rowCount</code> property without causing
* invalidation or setting the <code>explicitRowCount</code>
* property, which permanently locks in the number of rows.
*
* @param v The row count.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function setRowCount(v:int):void
{
//trace("setRowCount " + v);
_rowCount = v;
}
/**
* Sets the <code>rowHeight</code> property without causing invalidation or
* setting of <code>explicitRowHeight</code> which
* permanently locks in the height of the rows.
*
* @param v The row height, in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function setRowHeight(v:Number):void
{
_rowHeight = v;
}
//--------------------------------------------------------------------------
//
// Overridden event handlers: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
if (!selectable)
return;
if (!iteratorValid)
return;
if (!collection)
return;
switch (event.keyCode)
{
case Keyboard.UP:
case Keyboard.DOWN:
{
moveSelectionVertically(
event.keyCode, event.shiftKey, event.ctrlKey);
event.stopPropagation();
break;
}
case Keyboard.LEFT:
case Keyboard.RIGHT:
{
moveSelectionHorizontally(
event.keyCode, event.shiftKey, event.ctrlKey);
event.stopPropagation();
break;
}
case Keyboard.END:
case Keyboard.HOME:
case Keyboard.PAGE_UP:
case Keyboard.PAGE_DOWN:
{
moveSelectionVertically(
event.keyCode, event.shiftKey, event.ctrlKey);
event.stopPropagation();
break;
}
case Keyboard.SPACE:
{
if (caretIndex != -1 && ((caretIndex - verticalScrollPosition + lockedRowCount) >= 0) &&
((caretIndex - verticalScrollPosition + lockedRowCount) < listItems.length))
{
var li:IListItemRenderer =
listItems[caretIndex - verticalScrollPosition + lockedRowCount][0];
if (selectItem(li, event.shiftKey, event.ctrlKey))
{
var pt:Point = itemRendererToIndices(li);
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
evt.itemRenderer = li;
dispatchEvent(evt);
}
}
break;
}
default:
{
if (findKey(event.charCode))
event.stopPropagation();
}
}
}
/**
* Handles <code>mouseWheel</code> events by changing scroll positions.
* This is a copy of the version in the ScrollControlBase class,
* modified to change the <code>horizontalScrollPosition</code> property
* if the target is run horizontally.
*
* @param event The MouseEvent object.
*
* @see mx.core.ScrollControlBase#mouseWheelHandler()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function mouseWheelHandler(event:MouseEvent):void
{
if (verticalScrollBar && verticalScrollBar.visible)
{
event.stopPropagation();
var oldPosition:Number = verticalScrollPosition;
var newPos:int = verticalScrollPosition;
newPos -= event.delta * verticalScrollBar.lineScrollSize;
newPos = Math.max(0, Math.min(newPos, verticalScrollBar.maxScrollPosition));
verticalScrollPosition = newPos;
if (oldPosition != verticalScrollPosition)
{
var scrollEvent:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.position = verticalScrollPosition;
scrollEvent.delta = verticalScrollPosition - oldPosition;
dispatchEvent(scrollEvent);
}
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* Handles CollectionEvents dispatched from the data provider
* as the data changes.
* Updates the renderers, selected indices and scrollbars as needed.
*
* @param event The CollectionEvent.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function collectionChangeHandler(event:Event):void
{
var len:int;
var i:int;
var n:int;
var data:ListBaseSelectionData;
var p:String;
var selectedUID:String;
if (event is CollectionEvent)
{
var ce:CollectionEvent = CollectionEvent(event);
// trace("ListBase collectionEvent", ce.kind);
if (ce.kind == CollectionEventKind.ADD)
{
prepareDataEffect(ce);
// trace("ListBase collectionEvent ADD", ce.location, verticalScrollPosition);
// special case when we have less than a screen full of stuff
if (ce.location == 0 && verticalScrollPosition == 0)
{
try
{
// trace("ListBase collectionEvent ADD adjust");
iterator.seek(CursorBookmark.FIRST);
}
catch (e:ItemPendingError)
{
// trace("IPE in ADD event");
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
// do nothing, we'll repaint when the data arrives
}
}
else if (listType == "vertical" && verticalScrollPosition >= ce.location)
{
super.verticalScrollPosition = super.verticalScrollPosition + ce.items.length;
}
len = ce.items.length;
for (p in selectedData)
{
data = selectedData[p];
if (data.index > ce.location)
data.index += len;
}
if (_selectedIndex >= ce.location)
{
_selectedIndex += len;
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
}
else if (ce.kind == CollectionEventKind.REPLACE)
{
// trace("ListBase collectionEvent REPLACE");
selectedUID = selectedItem ? itemToUID(selectedItem) : null;
n = ce.items.length;
for (i = 0; i < n; i++)
{
var oldUID:String = itemToUID(ce.items[i].oldValue);
var sd:ListBaseSelectionData = selectedData[oldUID];
if (sd)
{
sd.data = ce.items[i].newValue;
delete selectedData[oldUID];
selectedData[itemToUID(sd.data)] = sd;
if (selectedUID == oldUID)
{
_selectedItem = sd.data;
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
}
}
prepareDataEffect(ce);
}
else if (ce.kind == CollectionEventKind.REMOVE)
{
prepareDataEffect(ce);
var requiresValueCommit:Boolean = false;
// trace("ListBase collectionEvent REMOVE", ce.location, verticalScrollPosition);
// make sure we've generated rows for the actual data
// at startup time we might just have blank rows
if (listItems.length && listItems[lockedRowCount].length)
{
// special case when we have less than a screen full of stuff
var firstUID:String = rowMap[listItems[lockedRowCount][0].name].uid;
selectedUID = selectedItem ? itemToUID(selectedItem) : null;
n = ce.items.length;
for (i = 0; i < n; i++)
{
var uid:String = itemToUID(ce.items[i]);
if (uid == firstUID && verticalScrollPosition == 0)
{
try
{
iterator.seek(CursorBookmark.FIRST);
}
catch (e1:ItemPendingError)
{
// trace("IPE in REMOVE event");
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0)
e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
// do nothing, we'll repaint when the data arrives
}
}
if (selectedData[uid])
removeSelectionData(uid);
if (selectedUID == uid)
{
_selectedItem = null;
_selectedIndex = -1;
requiresValueCommit = true;
}
removeIndicators(uid);
}
// Decrement verticalScrollPosition by the number of items that have
// been removed from the top.
if (listType == "vertical" && verticalScrollPosition >= ce.location)
{
if (verticalScrollPosition > ce.location)
{
super.verticalScrollPosition = verticalScrollPosition -
Math.min(ce.items.length,
verticalScrollPosition - ce.location);
}
else
{
// else the underlying collection goes to invalid cuz we removed the current
// so we force a re-seek next, but we want to keep everything in range
if (verticalScrollPosition >= collection.length)
super.verticalScrollPosition = Math.max(collection.length - 1, 0);
}
try
{
offscreenExtraRowsTop = Math.min(offscreenExtraRowsTop, verticalScrollPosition);
iterator.seek(CursorBookmark.FIRST, verticalScrollPosition - offscreenExtraRowsTop);
}
catch (e2:ItemPendingError)
{
// trace("IPE in Remove 2");
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition - offscreenExtraRowsTop)
e2.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
// do nothing, we'll repaint when the data arrives
}
}
var emitEvent:Boolean = adjustAfterRemove(ce.items, ce.location, requiresValueCommit);
if (emitEvent)
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
}
else if (ce.kind == CollectionEventKind.MOVE)
{
if (ce.oldLocation < ce.location)
{
for (p in selectedData)
{
data = selectedData[p];
if (data.index > ce.oldLocation &&
data.index < ce.location)
data.index --;
else if (data.index == ce.oldLocation)
data.index = ce.location;
}
if (_selectedIndex > ce.oldLocation &&
_selectedIndex < ce.location)
_selectedIndex --;
else if (_selectedIndex == ce.oldLocation)
_selectedIndex = ce.location;
}
else if (ce.location < ce.oldLocation)
{
for (p in selectedData)
{
data = selectedData[p];
if (data.index > ce.location &&
data.index < ce.oldLocation)
data.index ++;
else if (data.index == ce.oldLocation)
data.index = ce.location;
}
if (_selectedIndex > ce.location &&
_selectedIndex < ce.oldLocation)
_selectedIndex ++;
else if (_selectedIndex == ce.oldLocation)
_selectedIndex = ce.location;
}
// if the current item got moved
if (ce.oldLocation == verticalScrollPosition)
{
// iterator is at new position, jump to it,
// but make sure we don't max out first
if (ce.location > maxVerticalScrollPosition)
{
iterator.seek(CursorBookmark.CURRENT, maxVerticalScrollPosition - ce.location);
}
super.verticalScrollPosition = Math.min(ce.location, maxVerticalScrollPosition);
}
// if the old location and new location are on
// different sides of the scrollposition
else if ((ce.location >= verticalScrollPosition) &&
(ce.oldLocation < verticalScrollPosition))
seekNextSafely(iterator,verticalScrollPosition);
else if ((ce.location <= verticalScrollPosition) &&
(ce.oldLocation > verticalScrollPosition))
seekPreviousSafely(iterator,verticalScrollPosition);
}
else if (ce.kind == CollectionEventKind.REFRESH)
{
if (anchorBookmark)
{
try
{
iterator.seek(anchorBookmark, 0);
}
catch (e:ItemPendingError)
{
bSortItemPending = true;
lastSeekPending = new ListBaseSeekPending(anchorBookmark, 0);
e.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
}
catch (cursorError:CursorError)
{
// might have been filtered out so
// clear selections
clearSelected();
}
adjustAfterSort();
}
else
{
try
{
iterator.seek(CursorBookmark.FIRST,
verticalScrollPosition);
}
catch (e:ItemPendingError)
{
bSortItemPending = true;
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition)
e.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
}
}
}
else if (ce.kind == CollectionEventKind.RESET)
{
// RemoveAll() on ArrayCollection currently triggers a reset
// Special handling for this case.
if ((collection.length == 0) || (runningDataEffect && actualCollection.length == 0))
{
// All the data is already gone, so in order to run a data
// effect, we reconstruct it from the renderers
var deletedItems:Array = reconstructDataFromListItems();
if (deletedItems.length)
{
var fakeRemove:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
fakeRemove.kind = CollectionEventKind.REMOVE;
fakeRemove.items = deletedItems;
fakeRemove.location = 0;
prepareDataEffect(fakeRemove);
}
}
try
{
iterator.seek(CursorBookmark.FIRST);
collectionIterator.seek(CursorBookmark.FIRST);
}
catch (e:ItemPendingError)
{
lastSeekPending =
new ListBaseSeekPending(CursorBookmark.FIRST, 0);
e.addResponder(new ItemResponder(
seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
}
if (bSelectedIndexChanged || bSelectedItemChanged ||
bSelectedIndicesChanged || bSelectedItemsChanged)
{
bSelectionChanged = true;
}
else
{
commitSelectedIndex(-1);
}
if (isNaN(verticalScrollPositionPending))
{
verticalScrollPositionPending = 0;
super.verticalScrollPosition = 0;
}
if (isNaN(horizontalScrollPositionPending))
{
horizontalScrollPositionPending = 0;
super.horizontalScrollPosition = 0;
}
invalidateSize();
}
}
itemsSizeChanged = true;
invalidateDisplayList();
}
/**
* @private
* Reconstructs an array of items for a pseudo-data provider. Used to
* leverage the data effects infrastructure after removeAll() has been
* called on the data provider.
*
* Subclasses may need to override this function, e.g. for TileLists
* with vertical layout.
*/
mx_internal function reconstructDataFromListItems():Array
{
if (!listItems)
return [];
var items:Array = [];
// might make sense to ignore offscreen rows here
var n:int = listItems.length;
for (var i:int = 0; i < n; i++)
{
if (listItems[i])
{
var renderer:IListItemRenderer = listItems[i][0] as IListItemRenderer;
var data:Object;
var data2:Object;
if (renderer)
{
data = renderer.data;
items.push(data);
var m:int = listItems[i].length;
for (var j:int = 0; j < m; j++)
{
renderer = listItems[i][j] as IListItemRenderer;
if (renderer)
{
data2 = renderer.data;
if (data2 != data)
items.push(data2);
}
}
}
}
}
return items;
}
/**
* @private
*/
protected function prepareDataEffect(ce:CollectionEvent):void
{
if (!cachedDataChangeEffect)
{
// Style can set dataChangeEffect to an Effect object
// or a Class which is a subclass of Effect
var dce:Object = getStyle("dataChangeEffect");
var dceClass:Class = dce as Class;
if (dceClass)
dce = new dceClass();
cachedDataChangeEffect = dce as Effect;
}
if (runningDataEffect)
{
collection = actualCollection;
iterator = actualIterator;
cachedDataChangeEffect.end();
modifiedCollectionView = null;
}
// For now, if iterator is not valid, don't run the data effect.
if (cachedDataChangeEffect && iteratorValid)
{
var firstItemIndex:int = iterator.bookmark.getViewIndex();
var lastItemIndex:int = firstItemIndex + (rowCount * columnCount) - 1;
if (!modifiedCollectionView && (collection is IList))
modifiedCollectionView = new ModifiedCollectionView(ICollectionView(collection));
if (modifiedCollectionView)
{
modifiedCollectionView.processCollectionEvent(ce, firstItemIndex, lastItemIndex);
runDataEffectNextUpdate = true;
if (mx_internal::invalidateDisplayListFlag)
callLater(invalidateList);
else
invalidateList();
}
}
}
/**
* @private
*/
protected function adjustAfterRemove(items:Array, location:int, emitEvent:Boolean):Boolean
{
var data:ListBaseSelectionData;
var requiresValueCommit:Boolean = emitEvent;
var i:int = 0;
var length:int = items.length;
for (var p:String in selectedData)
{
i++;
data = selectedData[p];
if (data.index > location)
data.index -= length;
}
if (_selectedIndex > location)
{
_selectedIndex -= length;
requiresValueCommit = true;
}
// selected the last thing if the selected item
// got removed.
if (i > 0 && _selectedIndex == -1)
{
_selectedIndex = data.index;
_selectedItem = data.data;
requiresValueCommit = true;
}
if (i == 0)
{
_selectedIndex = -1;
bSelectionChanged = true;
bSelectedIndexChanged = true;
invalidateDisplayList();
}
return requiresValueCommit;
}
/**
* Handles <code>MouseEvent.MOUSE_OVER</code> events from any mouse
* targets contained in the list, including the renderers.
* This method finds out which renderer the mouse is over
* and shows it as highlighted.
*
* <p>The list classes also call this from a
* <code>MouseEvent.MOUSE_MOVE</code> event.
* This event is used to detect movement in non-target areas of the
* renderers and in padded areas around the renderers.</p>
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseOverHandler(event:MouseEvent):void
{
var evt:ListEvent;
if (!enabled || !selectable)
return;
if (dragScrollingInterval != 0 && !event.buttonDown)
{
// trace("mouseOver found mouse not pressed but dragInterval");
mouseIsUp();
}
isPressed = event.buttonDown;
var item:IListItemRenderer = mouseEventToItemRenderer(event);
var pt:Point = itemRendererToIndices(item);
if (!item)
return;
var uid:String = itemToUID(item.data);
if (!isPressed || allowDragSelection)
{
// we're rolling onto different subpieces of ourself or our highlight indicator
if (event.relatedObject)
{
var lastUID:String;
if (lastHighlightItemRenderer && highlightUID)
{
var rowData:BaseListData = rowMap[item.name];
lastUID = rowData.uid;
}
if (itemRendererContains(item, event.relatedObject) ||
uid == lastUID ||
event.relatedObject == highlightIndicator)
return;
}
if (getStyle("useRollOver") && (item.data != null))
{
if (allowDragSelection)
bSelectOnRelease = true;
drawItem(visibleData[uid], isItemSelected(item.data), true, uid == caretUID);
if (pt) // during tweens, we may get null
{
evt = new ListEvent(ListEvent.ITEM_ROLL_OVER);
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
evt.itemRenderer = item;
dispatchEvent(evt);
lastHighlightItemIndices = pt;
lastHighlightItemRendererAtIndices = item;
}
}
}
else
{
if (DragManager.isDragging)
return;
if ((dragScrollingInterval != 0 && allowDragSelection) || menuSelectionMode)
{
if (selectItem(item, event.shiftKey, event.ctrlKey))
{
evt = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = item;
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
dispatchEvent(evt);
}
}
}
}
/**
* Handles <code>MouseEvent.MOUSE_OUT</code> events from any mouse targets
* contained in the list including the renderers. This method
* finds out which renderer the mouse has left
* and removes the highlights.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseOutHandler(event:MouseEvent):void
{
if (!enabled || !selectable)
return;
isPressed = event.buttonDown;
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (!item)
return;
if (!isPressed)
{
// either we're rolling onto different subpieces of ourself or our
// highlight indicator, or the clearing of the highlighted item has
// already happened care of the mouseMove handler
if (itemRendererContains(item, event.relatedObject) ||
event.relatedObject == listContent ||
event.relatedObject == highlightIndicator ||
!highlightItemRenderer)
return;
if (getStyle("useRollOver") && item.data != null)
clearHighlight(item);
}
}
/**
* Handles <code>MouseEvent.MOUSE_MOVE</code> events from any mouse
* targets contained in the list including the renderers. This method
* watches for a gesture that constitutes the beginning of a
* drag drop and send a <code>DragEvent.DRAG_START</code> event.
* It also checks to see if the mouse is over a non-target area of a
* renderer so that Flex can try to make it look like that renderer was
* the target.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseMoveHandler(event:MouseEvent):void
{
if (!enabled || !selectable)
return;
var pt:Point = new Point(event.localX, event.localY);
pt = DisplayObject(event.target).localToGlobal(pt);
pt = globalToLocal(pt);
if (isPressed && mouseDownPoint &&
(Math.abs(mouseDownPoint.x - pt.x) > DRAG_THRESHOLD ||
Math.abs(mouseDownPoint.y - pt.y) > DRAG_THRESHOLD))
{
if (dragEnabled && !DragManager.isDragging && mouseDownPoint)
{
var dragEvent:DragEvent = new DragEvent(DragEvent.DRAG_START);
dragEvent.dragInitiator = this;
dragEvent.localX = mouseDownPoint.x;
dragEvent.localY = mouseDownPoint.y;
dragEvent.buttonDown = true;
dispatchEvent(dragEvent);
}
}
// we used to put shields into each of the renderers so leftover space was hittable
// but that's makes too many shields at startup and scrolling. The gamble is that we
// can run the code even on a large grid very quickly compared to mouse move intervals.
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (item && highlightItemRenderer)
{
var rowData:BaseListData = rowMap[item.name];
if (highlightItemRenderer && highlightUID && rowData.uid != highlightUID)
{
if (!isPressed)
{
if (getStyle("useRollOver") && highlightItemRenderer.data != null)
{
clearHighlight(highlightItemRenderer)
}
}
}
}
else if (!item && highlightItemRenderer)
{
if (!isPressed)
{
if (getStyle("useRollOver") && highlightItemRenderer.data)
{
clearHighlight(highlightItemRenderer)
}
}
}
if (item && !highlightItemRenderer)
{
mouseOverHandler(event);
}
}
/**
* Handles <code>MouseEvent.MOUSE_DOWN</code> events from any mouse
* targets contained in the list including the renderers. This method
* finds the renderer that was pressed and prepares to receive
* a <code>MouseEvent.MOUSE_UP</code> event.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseDownHandler(event:MouseEvent):void
{
if (!enabled || !selectable)
return;
// trace("mouseDown");
isPressed = true;
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (!item)
return;
bSelectOnRelease = false;
var pt:Point = new Point(event.localX, event.localY);
pt = DisplayObject(event.target).localToGlobal(pt);
mouseDownPoint = globalToLocal(pt);
systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, 0, true);
systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseLeaveHandler, false, 0, true);
if (!dragEnabled)
{
dragScrollingInterval = setInterval(dragScroll, 15);
}
if (dragEnabled)
mouseDownIndex = itemRendererToIndex(item);
// If dragEnabled is true, clicks on selected contents should cause
// a selection change on mouse up instead of mouse down. Otherwise,
// clicking in a selection to drag will deselect any multiple selection
// before the drag occurs.
if (dragEnabled && selectedData[rowMap[item.name].uid])
{
bSelectOnRelease = true;
}
else
{
if (selectItem(item, event.shiftKey, event.ctrlKey))
mouseDownItem = item;
}
}
private function mouseIsUp():void
{
systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true);
systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseLeaveHandler);
if (!dragEnabled && dragScrollingInterval != 0)
{
clearInterval(dragScrollingInterval);
dragScrollingInterval = 0;
}
}
private function mouseLeaveHandler(event:Event):void
{
mouseDownPoint = null;
mouseDownIndex = -1;
mouseIsUp();
if (!enabled || !selectable)
return;
if (mouseDownItem)
{
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = mouseDownItem;
var pt:Point = itemRendererToIndices(mouseDownItem);
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
dispatchEvent(evt);
mouseDownItem = null;
}
isPressed = false;
}
/**
* Handles <code>MouseEvent.MOUSE_DOWN</code> events from any mouse
* targets contained in the list including the renderers. This method
* finds the renderer that was pressed and prepares to receive
* a <code>MouseEvent.MOUSE_UP</code> event.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseUpHandler(event:MouseEvent):void
{
mouseDownPoint = null;
mouseDownIndex = -1;
//trace("mouseUp");
var item:IListItemRenderer = mouseEventToItemRenderer(event);
var pt:Point = itemRendererToIndices(item);
var evt:ListEvent;
mouseIsUp();
if (!enabled || !selectable)
return;
if (mouseDownItem)
{
evt = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = mouseDownItem;
pt = itemRendererToIndices(mouseDownItem);
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
dispatchEvent(evt);
mouseDownItem = null;
}
if (!item || !hitTestPoint(event.stageX, event.stageY))
{
isPressed = false;
return;
}
if (bSelectOnRelease)
{
bSelectOnRelease = false;
if (selectItem(item, event.shiftKey, event.ctrlKey))
{
evt = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = item;
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
dispatchEvent(evt);
}
}
isPressed = false;
}
/**
* Handles <code>MouseEvent.MOUSE_CLICK</code> events from any mouse
* targets contained in the list including the renderers. This method
* determines which renderer was clicked
* and dispatches a <code>ListEvent.ITEM_CLICK</code> event.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseClickHandler(event:MouseEvent):void
{
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (!item)
return;
var pt:Point = itemRendererToIndices(item);
if (pt) // during tweens, we may get null
{
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_CLICK);
listEvent.columnIndex = pt.x;
listEvent.rowIndex = pt.y;
listEvent.itemRenderer = item;
dispatchEvent(listEvent);
}
}
/**
* Handles <code>MouseEvent.MOUSE_DOUBLE_CLICK</code> events from any
* mouse targets contained in the list including the renderers.
* This method determines which renderer was clicked
* and dispatches a <code>ListEvent.ITEM_DOUBLE_CLICK</code> event.
*
* @param event The MouseEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseDoubleClickHandler(event:MouseEvent):void
{
var item:IListItemRenderer = mouseEventToItemRenderer(event);
if (!item)
return;
var pt:Point = itemRendererToIndices(item);
if (pt) // during tweens, we may get null
{
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_DOUBLE_CLICK);
listEvent.columnIndex = pt.x;
listEvent.rowIndex = pt.y;
listEvent.itemRenderer = item;
dispatchEvent(listEvent);
}
}
/**
* The default handler for the <code>dragStart</code> event.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragStartHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
var dragSource:DragSource = new DragSource();
addDragData(dragSource);
DragManager.doDrag(this, dragSource, event, dragImage,
0, 0, 0.5, dragMoveEnabled);
}
/**
* Handles <code>DragEvent.DRAG_ENTER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>showDropFeedback()</code> method to set up the UI feedback.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragEnterHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
lastDragEvent = event;
if (enabled && iteratorValid &&
(event.dragSource.hasFormat("items") || event.dragSource.hasFormat("itemsByIndex")))
{
DragManager.acceptDragDrop(this);
DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
showDropFeedback(event);
return;
}
hideDropFeedback(event);
DragManager.showFeedback(DragManager.NONE);
}
/**
* Handles <code>DragEvent.DRAG_OVER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>showDropFeedback()</code> method to set up the UI feeback.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragOverHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
lastDragEvent = event;
if (enabled && iteratorValid &&
(event.dragSource.hasFormat("items") || event.dragSource.hasFormat("itemsByIndex")))
{
DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE);
showDropFeedback(event);
return;
}
hideDropFeedback(event);
DragManager.showFeedback(DragManager.NONE);
}
/**
* Handles <code>DragEvent.DRAG_EXIT</code> events. This method hides
* the UI feeback by calling the <code>hideDropFeedback()</code> method.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragExitHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
lastDragEvent = null;
hideDropFeedback(event);
resetDragScrolling();
DragManager.showFeedback(DragManager.NONE);
}
/**
* Handles <code>DragEvent.DRAG_DROP events</code>. This method hides
* the UI feeback by calling the <code>hideDropFeedback()</code> method.
*
* @param event The DragEvent object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function dragDropHandler(event:DragEvent):void
{
if (event.isDefaultPrevented())
return;
hideDropFeedback(event);
lastDragEvent = null;
resetDragScrolling();
if (!enabled)
return;
var dragSource:DragSource = event.dragSource;
if (!dragSource.hasFormat("items") && !dragSource.hasFormat("itemsByIndex"))
return;
if (!dataProvider)
// Create an empty collection to drop items into.
dataProvider = [];
var dropIndex:int = calculateDropIndex(event);
if (dragSource.hasFormat("items"))
insertItems(dropIndex, dragSource, event);
else
insertItemsByIndex(dropIndex, dragSource, event);
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
*/
protected function dragCompleteHandler(event:DragEvent):void
{
isPressed = false;
if (event.isDefaultPrevented())
return;
if (event.action == DragManager.MOVE && dragMoveEnabled)
{
if (event.relatedObject != this)
{
var indices:Array = selectedIndices;
// clear the selection, otherwise we'll be adjusting it on every element being removed
clearSelected(false);
indices.sort(Array.NUMERIC);
var n:int = indices.length;
for (var i:int = n - 1; i >= 0; i--)
{
collectionIterator.seek(CursorBookmark.FIRST, indices[i]);
collectionIterator.remove();
}
clearSelected(false);
}
}
// this can probably be removed b/c it's in dragExit and dragDrop, but leaving these two
// lines for now
lastDragEvent = null;
resetDragScrolling();
}
/**
* @private
*/
private function selectionTween_updateHandler(event:TweenEvent):void
{
Sprite(event.target.listener).alpha = Number(event.value);
}
/**
* @private
*/
private function selectionTween_endHandler(event:TweenEvent):void
{
selectionTween_updateHandler(event);
}
/**
* @private
* Handles item renderers moving after initiateSelectionTracking() has been
* called. This is used during data effects to redraw selections after
* item renderers move.
*
*/
private function rendererMoveHandler(event:MoveEvent):void
{
var renderer:IListItemRenderer = event.currentTarget as IListItemRenderer;
// currently, the assumption is that this is only called for renderers that
// are representing selected items.
drawItem(renderer,true);
}
}
}