////////////////////////////////////////////////////////////////////////////////
//
// 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 selectedIndex
or selectedItem
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 data
property changes.
*
*
When you use a component as an item renderer,
* the data
property contains the data to display.
* You can listen for this event and update the component
* when the data
property changes.
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.
* *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. *
* *Only takes effect if no backgroundColor
is specified.
showDropFeedback()
* 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 true
, rows are highlighted as the mouse rolls over them.
* If false
, 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 "top"
, "middle"
,
* and "bottom"
.
* The DataGrid positions the renderers in a row based on this style
* and the paddingTop
and paddingBottom
styles.
* if the item in the columns for a row have different heights
* Other list classes do not use verticalAlign
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 dataProvider
property
* and displayed via item renderers.
*
* In a model/view architecture, the AdvancedListBase subclass represent * the view, and the data provider represents the model.
* * @mxml * *The AdvancedListBase class inherits all of the tag properties of its superclasses, * and adds the following tag properties:
* ** <mx:tagname * Properties * 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" * * Styles * 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" * * Events * change="No default" * dataChange="No default" * itemClick="No default" * itemDoubleClick="No default" * itemRollOut="No default" * itemRollOver="No default" * itemClick="No default" * /> ** * @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
dataProvider
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 dataProvider
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 iteratorValid
to false
. 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 contentPane
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 listContent
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 listItems
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 dataChangeEffect
* 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 collection
* 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.
*
* 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.
* * @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 <= 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 theiterator
* 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.
*
* 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.
* *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.
* *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.
* *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.
* * @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 * tofalse
when mouse activity is detected and set back to
* true
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 columnWidth
* and rowHeight
properties need to be calculated.
* This property is set to true
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 updateDisplayList()
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
* updateDisplayList()
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
* updateDisplayList()
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 wordWrap
* property has changed since the last time the display list was updated.
* This property is set when you change the wordWrap
* property value, and is reset
* to false
by the updateDisplayList()
method.
* The component usually responds by re-applying the data items to all of
* the renderers on the next updateDisplayList()
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 finishKeySelection()
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
* columnCount
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
* columnWidth
.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var explicitColumnWidth:Number;
/**
* The row count requested by explicitly setting
* rowCount
.
*
* @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
* rowHeight
.
*
* @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 true
, 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.
*
* Note: 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.
* * @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. * *The list classes use the listData
property
* in addition to the data
property to determine what
* to display.
* If the list class is in a DataGrid it expects the dataField
* property of the column to map to a property in the data
* and sets selectedItem
value to that property.
* If it is in a List or TileList control, it expects the
* labelField
property of the list to map to a property
* in the data, and sets selectedItem
value to that property.
* Otherwise it sets the selectedItem
to the data itself.
This property uses the data provider but does not set it. * In all cases, you must set the data provider in some other way.
* *You do not set this property in MXML.
* * @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 thedataProvider
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.
*
* As a consequence of the conversions, when you get the
* dataProvider
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
* dataProvider
property will be detected.
label
on each item and displays it.
* However, if the data objects do not contain a label
* property, you can set the dataTipField
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 label
* on each data provider item and displays it.
* However, some items do not have a label
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
* dataTipFunction
that finds the appropriate
* fields and return a displayable string. The
* dataTipFunction
is also good for handling formatting
* and localization.
*
* The dataTipFunction takes a single argument which is the item * in the data provider and returns a String:
* *
* myDataTipFunction(item:Object):String
*
*
* @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 true
, dragging is enabled for the control.
* If the dropEnabled
property is also true
,
* 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 true
, and the dragEnabled
property
* is true
, 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 dropEnabled
property.
*/
private var _dropEnabled:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether dragged items can be dropped onto the
* control.
*
* If you set this property to true
,
* 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 dragOver
, and call the DragEvent's
* preventDefault()
method to customize
* the way the list class accepts dropped data.
When you set dropEnabled
to true
,
* Flex automatically calls the showDropFeedback()
* and hideDropFeedback()
methods to display the drop indicator.
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.
* * @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. * *The iconFunction
takes a single argument which is the item
* in the data provider and returns a Class.
* Shown below is the signature of the function:
iconFunction(item:Object):Class* * @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. * *
The default item renderer depends on the component class. * For example, the AdvancedDataGrid class uses * AdvancedDataGridItemRenderer.
* * @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 namedlabel
* on each item and displays it.
* However, if the data objects do not contain a label
* property, you can set the labelField
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 label
* on each data provider item and displays it.
* However, some data sets do not have a label
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.
*
* You can supply a labelFunction
that finds the
* appropriate fields and returns a displayable string. The
* labelFunction
is also good for handling formatting and
* localization.
For most components, the label function takes a single argument * which is the item in the data provider and returns a String.
** myLabelFunction(item:Object):String* *
The method signature for the data grid classes is:
** myLabelFunction(item:Object, column:DataGridColumn):String* *
where item
contains the DataGrid item object, and
* column
specifies the DataGrid column.
listData
property
* of the component with the additional data from the list control.
* The component can then use the listData
property
* and the data
property to display the appropriate
* information as a drop-in item renderer or drop-in item editor.
*
* 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.
* * @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. * *For a data grid controls, the rowCount
property includes the
* header row.
* So, for a data grid control with 3 body rows and a header row,
* the rowCount
property is 4.
variableRowHeight
property is
* true
, 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 true
, 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.
*
* The default value is -1 (no selected item).
* * * @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; 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 : 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. * Iftrue
, 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.
*
* Note: Using selectedItem
is often preferable. This
* property exists for backward compatibility with older applications
true
, 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 true
, enables word wrapping for text in the rows.
* Only takes effect if variableRowHeight
is also
* true
*
* @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 listContent
object that will hold all the item
* renderers.
* Note that the item renderers are not created immediately, but later
* when Flex calls the updateDisplayList()
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 columnCount
* columnWidth
, rowHeight
and
* rowCount
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 rowCount
,
* columnCount
, rowHeight
and
* columnWidth
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
* scaleX
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
* scaleY
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.
*
* This method is not implemented in the AdvancedListBase class * and must be implemented in the child class.
* *A negative index
value can be used to specify
* that the width calculation includes any headers.
This method is not implemented in the AdvancedListBase class * and must be implemented in the child class.
* *A negative index
value can be used to specify
* that the height calculation includes any headers.
For use by developers creating subclasses of ListBase or its children. * Not used by application developers.
* * @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 *null
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 listItems
* array starting at (firstColumn, firstRow)(
* and moving downwards.
*
* If byCount
and rowsNeeded
are specified,
* then just make that many rows and ignore the bottom
* and right
parameters.
listItems
to store
* the first renderer to be created.
*
* @param firstRow Offset into listItems
to store
* the first renderer to be created.
*
* @param byCount If true, make rowsNeeded
number of rows
* and ignore bottom
parameter
*
* @param rowsNeeded Number of rows to create if byCount
* 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 lastPrefixRow:ListRowInfo;
var lastOnscreenRow:ListRowInfo;
var lastOffscreenRow:ListRowInfo;
var onscreenRowIndex:int;
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 ? 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 verticalScrollPosition
* 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
* horizontalScrollPosition
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 x
property contains the column index
* and the y
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
* null
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.
*
* 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:
* ** var sampleDP:Array = ["foo", "bar", "foo"] ** *
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:
* ** var sampleDP:Array = [{label: "foo"}, {label: "foo"}] ** *
Note that the following is not supported because the same instance * appears twice:
* ** var foo:Object = {label: "foo"}; * sampleDP:Array = [foo, foo]; ** * @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
null
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 null
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 true
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
* null
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 true
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 true
if the renderer should be drawn in
* its selected state.
* @param highlighted true
if the renderer should be drawn in
* its highlighted state.
* @param caret true
if the renderer should be drawn as if
* it is the selection caret.
* @param transition true
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)
{
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 true
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 true
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 true
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 true
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 true
if the shift key was held down when
* the mouse was clicked.
* @param ctrlKey true
if the ctrl key was held down when
* the mouse was clicked.
* @param transition true
if the graphics for the selected
* state should be faded in using an effect.
*
* @return true
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 data:Object;
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
// seletion 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 true
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;
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 horizontalScrollPosition
,
* verticalScrollPosition
, and caretIndex
* properties, and call the finishKeySelection()
method
* to update the selection.
*
* Not implemented in AdvancedListBase because the default list * is single column and therefore doesn't scroll horizontally.
* * @param code The key that was pressed (e.g. Keyboard.LEFT) * @param shiftKeytrue
if the shift key was held down when
* the keyboard key was pressed.
* @param ctrlKey true
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 horizontalScrollPosition
,
* verticalScrollPosition
, and caretIndex
* properties, and call the finishKeySelection()
method
* to update the selection
*
* @param code The key that was pressed (e.g. Keyboard.DOWN)
* @param shiftKey true
if the shift key was held down when
* the keyboard key was pressed.
* @param ctrlKey true
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 caretIndex
and
* anchorIndex
properties.
* Called by the keyboard selection handlers
* and by the updateDisplayList()
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;
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.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 index:int = bookmark.getViewIndex();
if (index >= 0)
{
addSelectionData(uid, new ListBaseSelectionData(item, index, 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 = index;
_selectedItem = item;
caretIndex = index;
caretBookmark = collectionIterator.bookmark;
anchorIndex = index;
anchorBookmark = collectionIterator.bookmark;
}
}
}
else
{
while (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 true
if the array should
* be filled with the actual items or false
* 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 horizontalScrollPosition
* property value corresponding to the scroll position.
* @param verticalScrollPosition The verticalScrollPosition
* 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 verticalScrollPosition
* property is left unchanged even if the item is not the first visible
* item. If the item is not currently visible, the
* verticalScrollPosition
* property is changed make the item the first visible item, unless there
* aren't enough rows to do so because the
* verticalScrollPosition
value is limited by the
* maxVerticalScrollPosition
property.
*
* @param index The index of the item in the data provider.
*
* @return true
if verticalScrollPosition
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.
*
* 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 makeRowsAndColumns()
* method for the entire viewable area.
true
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.
*
* 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 makeRowsAndColumns()
* method for the entire viewable area.
Not implemented in AdvancedListBase because the default list * is single column and therefore doesn't scroll horizontally.
* * @param pos The new scroll position. * @param deltaPos The change in position. It is always * a positive number. * @param scrollUptrue
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 updateDisplayList()
method
* after the rows and columns have been updated.
* The method should figures out what parameters to pass into the
* setScrollBarProperties()
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 null
the
* method should return the dropIndex
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
* ObjectUtil.copy()
method, and replaces
* the copy's uid
property (if present) with a
* new value by calling the UIDUtil.createUID()
method.
*
* This method is used for a drag and drop copy.
* * @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.