//////////////////////////////////////////////////////////////////////////////// // // 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.

* * @eventType mx.events.FlexEvent.DATA_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="dataChange", type="mx.events.FlexEvent")] /** * Dispatched when the user rolls the mouse pointer over an item in the control. * * @eventType mx.events.ListEvent.ITEM_ROLL_OVER * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="itemRollOver", type="mx.events.ListEvent")] /** * Dispatched when the user rolls the mouse pointer out of an item in the control. * * @eventType mx.events.ListEvent.ITEM_ROLL_OUT * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="itemRollOut", type="mx.events.ListEvent")] /** * Dispatched when the user clicks on an item in the control. * * @eventType mx.events.ListEvent.ITEM_CLICK * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="itemClick", type="mx.events.ListEvent")] /** * Dispatched when the user double-clicks on an item in the control. * * @eventType mx.events.ListEvent.ITEM_DOUBLE_CLICK * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="itemDoubleClick", type="mx.events.ListEvent")] //-------------------------------------- // Styles //-------------------------------------- include "../../styles/metadata/FocusStyles.as" include "../../styles/metadata/PaddingStyles.as" /** * The colors to use for the backgrounds of the items in the list. * The value is an array of two or more colors. * The backgrounds of the list items alternate among the colors in the array. * *

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.

* * @default undefined * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")] /** * The skin to use to indicate where a dragged item can be dropped. * When an AdvancedListBase-derived component is a potential drop target in a * drag-and-drop operation, a call to the 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 the iterator * 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 * to false 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 the dataProvider 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.

* * @default null * @see mx.collections.ICollectionView * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get dataProvider():Object { // if we are running a data change effect, return the true // data provider, rather than the ModifiedCollectionView wrapper. if (actualCollection) return actualCollection; return collection; } /** * @private */ public function set dataProvider(value:Object):void { if (collection) { collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler); } if (value is Array) { collection = new ArrayCollection(value as Array); } else if (value is ICollectionView) { collection = ICollectionView(value); } else if (value is IList) { collection = new ListCollectionView(IList(value)); } else if (value is XMLList) { collection = new XMLListCollection(value as XMLList); } else if (value is XML) { var xl:XMLList = new XMLList(); xl += value; collection = new XMLListCollection(xl); } else { // convert it to an array containing this one item var tmp:Array = []; if (value != null) tmp.push(value); collection = new ArrayCollection(tmp); } // get an iterator for the displaying rows. The CollectionView's // main iterator is left unchanged so folks can use old DataSelector // methods if they want to iterator = collection.createCursor(); collectionIterator = collection.createCursor(); //IViewCursor(collection); // trace("ListBase added change listener"); collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true); clearSelectionData(); var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); event.kind = CollectionEventKind.RESET; collectionChangeHandler(event); dispatchEvent(event); itemsNeedMeasurement = true; invalidateProperties(); invalidateSize(); invalidateDisplayList(); } //---------------------------------- // dataTipField //---------------------------------- /** * @private * Storage for the dataTipField property. */ private var _dataTipField:String = "label"; [Bindable("dataTipFieldChanged")] [Inspectable(category="Data", defaultValue="label")] /** * Name of the field in the data provider items to display as the * data tip. By default, the list looks for a property named * 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.

* * @default false * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get dropEnabled():Boolean { return _dropEnabled; } /** * @private */ public function set dropEnabled(value:Boolean):void { if (_dropEnabled && !value) { removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false); removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false); removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false); removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false); } _dropEnabled = value; if (value) { addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false, EventPriority.DEFAULT_HANDLER); } } //---------------------------------- // iconField //---------------------------------- /** * @private * Storage for iconField property. */ private var _iconField:String = "icon"; [Bindable("iconFieldChanged")] [Inspectable(category="Data", defaultValue="")] /** * The name of the field in the data provider object that determines what to * display as the icon. By default, the list class does not try to display * icons with the text in the rows. However, by specifying an icon * field, you can specify a graphic that is created and displayed as an * icon in the row. This property is ignored by DataGrid. * *

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 named label * 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.

* * @default null * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get labelFunction():Function { return _labelFunction; } /** * @private */ public function set labelFunction(value:Function):void { _labelFunction = value; itemsSizeChanged = true; invalidateDisplayList(); dispatchEvent(new Event("labelFunctionChanged")); } //---------------------------------- // listData //---------------------------------- /** * @private * Storage for the listData property. */ private var _listData:BaseListData; [Bindable("dataChange")] [Inspectable(environment="none")] /** * * When a component is used as a drop-in item renderer or drop-in * item editor, Flex initializes the 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.

* * @default 4 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get rowCount():int { return _rowCount; } /** * @private */ public function set rowCount(value:int):void { explicitRowCount = value; if (_rowCount != value) { setRowCount(value); rowCountChanged = true; invalidateProperties(); invalidateSize(); itemsSizeChanged = true; invalidateDisplayList(); dispatchEvent(new Event("rowCountChanged")); } } //---------------------------------- // rowHeight //---------------------------------- /** * @private * Storage for the rowHeight property. */ private var _rowHeight:Number; /** * @private */ private var rowHeightChanged:Boolean = false; [Inspectable(category="General")] /** * The height of the rows in pixels. * Unless the 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. * If true, 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

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get value():Object { var item:Object = selectedItem; if (!item) return null; if (typeof(item) != "object") return item; return item.data != null ? item.data : item.label; } //---------------------------------- // variableRowHeight //---------------------------------- /** * @private * Storage for the variableRowHeight property. */ private var _variableRowHeight:Boolean = false; [Inspectable(category="General")] /** * A flag that indicates whether the individual rows can have different * height. This property is ignored by TileList and HorizontalList. * If 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.

* * @param index The data provider item at which to start measuring * the width. * * @param count The number of items to measure in calculating the width. * * @return The widest of the measured items. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function measureWidthOfItems(index:int = -1, count:int = 0):Number { return NaN; } /** * Measures a set of items from the data provider using the * current item renderer and returns the sum of the heights * of those items. * *

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.

* * @param index The data provider item at which to start calculating * the height. * * @param count The number of items to use in calculating the height. * * @return The sum of the height of the measured items. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function measureHeightOfItems(index:int = -1, count:int = 0):Number { return NaN; } //-------------------------------------------------------------------------- // // Methods: Item fields // //-------------------------------------------------------------------------- /** * Returns the string the renderer would display for the given data object * based on the labelField and labelFunction properties. * If the method cannot convert the parameter to a string, it returns a * single space. * * @param data Object to be rendered. * * @return The string to be displayed based on the data. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function itemToLabel(data:Object):String { if (data == null) return " "; if (labelFunction != null) return labelFunction(data); if (data is XML) { try { if (data[labelField].length() != 0) data = data[labelField]; //by popular demand, this is a default XML labelField //else if (data.@label.length() != 0) // data = data.@label; } catch (e:Error) { } } else if (data is Object) { try { if (data[labelField] != null) data = data[labelField]; } catch(e:Error) { } } if (data is String) return String(data); try { return data.toString(); } catch(e:Error) { } return " "; } /** * Returns the dataTip string the renderer would display for the given * data object based on the dataTipField and dataTipFunction properties. * If the method cannot convert the parameter to a string, it returns a * single space. *

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.

* * @param left Horizontal pixel offset of area to fill. * * @param top Vertical pixel offset of area to fill. * * @param right Horizontal pixel offset of area to fill * (from left side of component). * * @param bottom Vertical pixel offset of area to fill * (from top of component). * * @param firstColumn Offset into 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 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 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.

* * @param pos The new scroll position. * * @param deltaPos The change in position. It is always * a positive number. * * @param scrollUp 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 scrollUp true 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. { // Copy the vector so that we don't modify the original // since selectedIndices returns a reference. var draggedIndices:Array = selectedIndices.slice(0, selectedIndices.length); var result:Vector. = new Vector.(draggedIndices.length); // Sort in the order of the data source draggedIndices.sort(); // Copy the items var count:int = draggedIndices.length; for (var i:int = 0; i < count; i++) result[i] = dataProvider.getItemAt(draggedIndices[i]); return result; } /** * @private * Insert for drag and drop, handles the Spark "itemsByIndex" data format. */ private function insertItemsByIndex(dropIndex:int, dragSource:DragSource, event:DragEvent):void { var items:Vector. = dragSource.dataForFormat("itemsByIndex") as Vector.; // Copy or move the items. No need to check whether the operation is // reorder within this list, as ListBase never creates the // "itemsByIndex" data format. This is a new data format from Spark List only. collectionIterator.seek(CursorBookmark.FIRST, dropIndex); var count:int = items.length; for (var i:int = 0; i < count; i++) { if (event.action == DragManager.COPY) { collectionIterator.insert(copyItemWithUID(items[i])); } else if (event.action == DragManager.MOVE) { collectionIterator.insert(items[i]); } } } /** * @private * Insert for drag and drop, handles the Halo "items" data format. */ private function insertItems(dropIndex:int, dragSource:DragSource, event:DragEvent):void { var items:Array = dragSource.dataForFormat("items") as Array; if (event.action == DragManager.MOVE && dragMoveEnabled && event.dragInitiator == this) { var indices:Array = selectedIndices; indices.sort(Array.NUMERIC); for (var i:int = indices.length - 1; i >= 0; i--) { collectionIterator.seek(CursorBookmark.FIRST, indices[i]); if (indices[i] < dropIndex) dropIndex--; collectionIterator.remove(); } clearSelected(false); } collectionIterator.seek(CursorBookmark.FIRST, dropIndex); for (i = items.length - 1; i >= 0; i--) { if (event.action == DragManager.COPY) { collectionIterator.insert(copyItemWithUID(items[i])); } else if (event.action == DragManager.MOVE) { collectionIterator.insert(items[i]); } } } //-------------------------------------------------------------------------- // // Methods: Support for pending data // //-------------------------------------------------------------------------- /** * The default failure handler when a seek fails due to a page fault. * * @param data The data that caused the error. * * @param info Data about a seek operation * that was interrupted by an ItemPendingError error. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function seekPendingFailureHandler(data:Object, info:ListBaseSeekPending):void { } /** * The default result handler when a seek fails due to a page fault. * This method checks to see if it has the most recent page fault result: * if not it simply exits; if it does, it sets the iterator to * the correct position. * * @param data The data that caused the error. * * @param info Data about a seek operation * that was interrupted by an ItemPendingError error. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function seekPendingResultHandler(data:Object, info:ListBaseSeekPending):void { // trace("seekPendingResultHandler", this); if (info != lastSeekPending) { return; } lastSeekPending = null; iteratorValid = true; try { iterator.seek(info.bookmark, info.offset); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(info.bookmark, info.offset) e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; } if (bSortItemPending) { bSortItemPending = false; adjustAfterSort(); } itemsSizeChanged = true; invalidateDisplayList(); } /** * @private */ private function findPendingFailureHandler(data:Object, info:ListBaseFindPending):void { } /** * @private */ private function findPendingResultHandler(data:Object, info:ListBaseFindPending):void { // trace("findPendingResultHandler", this); iterator.seek(info.bookmark, info.offset); findStringLoop(info.searchString, info.startingBookmark, info.currentIndex, info.stopIndex); } /** * @private */ private function selectionPendingFailureHandler( data:Object, info:ListBaseSelectionPending):void { } /** * @private */ private function selectionPendingResultHandler( data:Object, info:ListBaseSelectionPending):void { // trace("selectionPendingResultHandler", this); iterator.seek(info.bookmark, info.offset); shiftSelectionLoop(info.incrementing, info.index, info.stopData, info.transition, info.placeHolder); } /** * @private */ private function selectionDataPendingFailureHandler( data:Object, info:ListBaseSelectionDataPending):void { } /** * @private */ private function selectionDataPendingResultHandler( data:Object, info:ListBaseSelectionDataPending):void { // trace("selectionDataPendingResultHandler", this); if (info.bookmark) iterator.seek(info.bookmark, info.offset); setSelectionDataLoop(info.items, info.index, info.useFind); } /** * @private */ private function selectionIndicesPendingFailureHandler( data:Object, info:ListBaseSelectionDataPending):void { } /** * @private */ private function selectionIndicesPendingResultHandler( data:Object, info:ListBaseSelectionDataPending):void { // trace("selectionIndicesPendingResultHandler", this); if (info.bookmark) iterator.seek(info.bookmark, info.offset); setSelectionIndicesLoop(info.index, info.items, info.useFind); } //-------------------------------------------------------------------------- // // Methods: Keyboard lookup // //-------------------------------------------------------------------------- /** * Tries to find the next item in the data provider that * starts with the character in the eventCode parameter. * You can override this to do fancier typeahead lookups. The search * starts at the selectedIndex location; if it reaches * the end of the data provider it starts over from the beginning. * * @param eventCode The key that was pressed on the keyboard. * @return true if a match was found. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function findKey(eventCode:int):Boolean { var tmpCode:int = eventCode; return tmpCode >= 33 && tmpCode <= 126 && findString(String.fromCharCode(tmpCode)); } /** * Finds an item in the list based on a string * and moves the selection to it. The search * starts at the selectedIndex location; if it reaches * the end of the data provider it starts over from the beginning. * * @param str The string to match. * @return true if a match was found. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function findString(str:String):Boolean { if (!collection || collection.length == 0) return false; var cursorPos:CursorBookmark; cursorPos = iterator.bookmark; var stopIndex:int = selectedIndex; var i:int = stopIndex + 1; // start at next if (selectedIndex == -1) { try { iterator.seek(CursorBookmark.FIRST, 0); } catch (e1:ItemPendingError) { e1.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, CursorBookmark.FIRST, 0, 0, collection.length))); iteratorValid = false; return false; } stopIndex = collection.length; i = 0; } else { try { iterator.seek(CursorBookmark.FIRST, stopIndex); } catch (e2:ItemPendingError) { if (anchorIndex == collection.length - 1) { e2.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, CursorBookmark.FIRST, 0, 0, collection.length))); } else { e2.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, anchorBookmark, 1, anchorIndex + 1, anchorIndex))); } iteratorValid = false; return false; } var bMovedNext:Boolean = false; // If we ran off the end, go back to beginning. try { bMovedNext = iterator.moveNext(); } catch (e3:ItemPendingError) { // Assume we don't fault unless there is more data. e3.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, anchorBookmark, 1, anchorIndex + 1, anchorIndex))); iteratorValid = false; return false; } if (!bMovedNext) { try { iterator.seek(CursorBookmark.FIRST, 0); } catch (e4:ItemPendingError) { e4.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, CursorBookmark.FIRST, 0, 0, collection.length))); iteratorValid = false; return false; } stopIndex = collection.length; i = 0; } } return findStringLoop(str, cursorPos, i, stopIndex); } /** * @private */ private function findStringLoop(str:String, cursorPos:CursorBookmark, i:int, stopIndex:int):Boolean { // Search from the current index. // Jump back to beginning if we hit the end. for (i; i != stopIndex; i++) { var itmStr:String = itemToLabel(iterator.current); itmStr = itmStr.substring(0, str.length); if (str == itmStr || str.toUpperCase() == itmStr.toUpperCase()) { iterator.seek(cursorPos, 0); scrollToIndex(i); commitSelectedIndex(i); var item:IListItemRenderer = indexToItemRenderer(i); var pt:Point = itemRendererToIndices(item); var evt:ListEvent = new ListEvent(ListEvent.CHANGE); evt.itemRenderer = item; if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } dispatchEvent(evt); return true; } try { var more:Boolean = iterator.moveNext(); } catch (e1:ItemPendingError) { e1.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, CursorBookmark.CURRENT, 1, i + 1, stopIndex))); iteratorValid = false; return false; } // Start from beginning if we hit the end if (!more && stopIndex != collection.length) { i = -1; try { iterator.seek(CursorBookmark.FIRST, 0); } catch (e2:ItemPendingError) { e2.addResponder(new ItemResponder( findPendingResultHandler, findPendingFailureHandler, new ListBaseFindPending(str, cursorPos, CursorBookmark.FIRST, 0, 0, stopIndex))); iteratorValid = false; return false; } } } iterator.seek(cursorPos, 0); iteratorValid = true; return false; } //-------------------------------------------------------------------------- // // Methods: Sorting // //-------------------------------------------------------------------------- /** * @private */ private function adjustAfterSort():void { var i:int = 0; for (var p:String in selectedData) { i++; } var index:int = anchorBookmark ? anchorBookmark.getViewIndex() : -1; if (index >= 0) { // If only one thing selected, then we're done. if (i == 1) { _selectedIndex = anchorIndex = caretIndex = index; var data:ListBaseSelectionData = selectedData[p]; data.index = index; } var newVerticalScrollPosition:int = indexToRow(index); newVerticalScrollPosition = Math.min(maxVerticalScrollPosition, newVerticalScrollPosition); var newHorizontalScrollPosition:int = indexToColumn(index); newHorizontalScrollPosition = Math.min(maxHorizontalScrollPosition, newHorizontalScrollPosition); // Prepare to refresh from there. var pos:int = scrollPositionToIndex(newHorizontalScrollPosition, newVerticalScrollPosition); try { iterator.seek(CursorBookmark.CURRENT, pos - index); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending( CursorBookmark.CURRENT, pos - index) e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; return; } super.verticalScrollPosition = newVerticalScrollPosition; if (listType != "vertical") super.horizontalScrollPosition = newHorizontalScrollPosition; } else { try { iterator.seek(CursorBookmark.FIRST, verticalScrollPosition); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending( CursorBookmark.FIRST, verticalScrollPosition); e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; return; } } // If there's more than one selection, find their new indices. if (i > 1) commitSelectedItems(selectedItems); } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Initiates a data change effect when there have been changes * in the data provider. * * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates, regardless of the value of the * 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. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function initiateDataChangeEffect(unscaledWidth:Number, unscaledHeight:Number):void { // as optimization, we might get targets from visibleData // if we know that nothing else has changed. // otherwise, rerender using our preserved collection view // store original collection and iterator so we can // return to using it after the effect has finished actualCollection = collection; actualIterator = iterator; // switch to a view of the collection before the data change // and call updateDisplayList() collection = modifiedCollectionView; modifiedCollectionView.showPreservedState = true; iterator = collection.createCursor(); var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition - offscreenExtraRowsTop); iterator.seek(CursorBookmark.FIRST,index); updateDisplayList(unscaledWidth,unscaledHeight); var targets:Array = []; var targetHash:Dictionary = new Dictionary(true); // record the initial set of targets for the // data change effect var n:int = listItems.length; var m:int = 0; for (var i:int = 0; i < n; i++) { var rowItems:Array = listItems[i]; if (rowItems && (rowItems.length > 0)) { m = rowItems.length; for (var j:int = 0; j < m; j++) { var target:Object = rowItems[j]; if (target) { targets.push(target); targetHash[target] = true; } } } } cachedDataChangeEffect.targets = targets; if (cachedDataChangeEffect.effectTargetHost != this) cachedDataChangeEffect.effectTargetHost = this; cachedDataChangeEffect.captureStartValues(); // Now get additional targets that are only in endstate modifiedCollectionView.showPreservedState = false; // This is not great from a usability point of view if all the items have // been added offscreen...we're scrolling the view as well. // Also, ideally we wouldn't have to do this seek; the iterator // would notice via events that the modifiedCollectionView had changed iterator.seek(CursorBookmark.FIRST,index); itemsSizeChanged = true; updateDisplayList(unscaledWidth,unscaledHeight); // Acquire additional targets var newTargets:Array = []; var oldTargets:Array = cachedDataChangeEffect.targets; n = listItems.length; for (i = 0; i < n; i++) { rowItems = listItems[i]; if (rowItems && (rowItems.length > 0)) { m = rowItems.length; for (j = 0; j < m; j++) { target = rowItems[j]; if (target && !targetHash[target]) { oldTargets.push(target); newTargets.push(target); } } } } // Get start values for additional targets, // and end values for all targets if (newTargets.length > 0) { cachedDataChangeEffect.targets = oldTargets; cachedDataChangeEffect.captureMoreStartValues(newTargets); } cachedDataChangeEffect.captureEndValues(); // Do the layout for the control one more time, to ensure // that only the items visible before the data change // occurred are visible modifiedCollectionView.showPreservedState = true; iterator.seek(CursorBookmark.FIRST,index); itemsSizeChanged = true; updateDisplayList(unscaledWidth,unscaledHeight); initiateSelectionTracking(oldTargets); // Start the data effect, which will rewind to start state cachedDataChangeEffect.addEventListener(EffectEvent.EFFECT_END,finishDataChangeEffect); cachedDataChangeEffect.play(); } /** * @private * Sets up listeners for MoveEvents for a set of renderers. Listeners are only * created for renderers representing selected items. * * This functionality is used by data change effects, to update selections * when the item renderers move. */ private function initiateSelectionTracking(renderers:Array):void { var n:int = renderers.length; for (var i:int = 0; i < n; i++) { var renderer:IListItemRenderer = renderers[i] as IListItemRenderer; if (selectedData[itemToUID(renderer.data)]) { renderer.addEventListener(mx.events.MoveEvent.MOVE,rendererMoveHandler); trackedRenderers.push(renderer); } } } /** * @private * Removes event listeners for MoveEvents set up by initiateSelectionTracking(). * */ private function terminateSelectionTracking():void { var n:int = trackedRenderers.length; for (var i:int = 0; i < n; i++) { var renderer:IListItemRenderer = trackedRenderers[i] as IListItemRenderer; renderer.removeEventListener(mx.events.MoveEvent.MOVE,rendererMoveHandler); } trackedRenderers = []; } /** * Removes an item renderer if a data change effect is running. * The item renderer must correspond to data that has already * been removed from the data provider collection. * * This function will be called by a RemoveItemAction * effect as part of a data change effect to specify the point * at which a data item ceases to displayed by the control using * an item renderer. * * @param item The item renderer to remove from the control's layout. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function removeDataEffectItem(item:Object):void { // Shouldn't need to check this, but there // currently appears to be a race condition // (perhaps a bug in when effectEnd occurs) if (modifiedCollectionView) modifiedCollectionView.removeItem(dataItemWrappersByRenderer[item]); // again, this should not really be necessary iterator.seek(CursorBookmark.CURRENT); // force validation, otherwise it can be delayed until the // end of a data effect if (mx_internal::invalidateDisplayListFlag) { itemsSizeChanged = true; validateDisplayList(); } else invalidateList(); } /** * Adds an item renderer if a data change effect is running. * The item renderer should correspond to a recently added * data item in the data provider's collection that isn't * yet being displayed. * * This function will be called by an AddItemAction * effect as part of a data change effect to specify the point * at which a data item added to a collection begins to be displayed * by the control using an item renderer. * * @param item The item renderer to add to the control's layout. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addDataEffectItem(item:Object):void { if (modifiedCollectionView) modifiedCollectionView.addItem(dataItemWrappersByRenderer[item]); // if it was previously empty, set cursor to first item // We shouldn't really have to do this here. if (iterator.afterLast) iterator.seek(CursorBookmark.FIRST) else iterator.seek(CursorBookmark.CURRENT); // force validation, otherwise it can be delayed until the // end of a data effect if (mx_internal::invalidateDisplayListFlag) { itemsSizeChanged = true; validateDisplayList(); } else { invalidateList(); } } /** * Temporarily stops an item renderer from being positioned * by the control's layout algorithm. * * This function will be called * as part of a data change effect if the item renderers corresponding * to certain data items need to move outside the normal positions * of item renderers in the control. * * @param item The data for the item renderer. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function unconstrainRenderer(item:Object):void { unconstrainedRenderers[item] = true; } /** * Returns the value for a particular semantic property of * an item renderer, or null if that property is not defined, * while a data effect is running. * * This function is used by filters in data change effects * to restrict effects to renderers corresponding to removed * or added data items. * * @param target An item renderer. * * @param semanticProperty The semantic property of the renderer * whose value will be returned. * * @return The value for a particular semantic property of * an item renderer, or null if that property is not defined, * while a data effect is running. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function getRendererSemanticValue(target:Object,semanticProperty:String):Object { // this is really only a temporary solution, assuming single boolean semantics // e.g. "removed" or "added" return (modifiedCollectionView.getSemantics(dataItemWrappersByRenderer[target]) == semanticProperty); } /** * Returns true if an item renderer is no longer being positioned * by the list's layout algorithm while a data change effect is * running as a result of a call to the unconstrainRenderer() method. * * @param item An item renderer. * * @return true if an item renderer is no longer being positioned * by the list's layout algorithm. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function isRendererUnconstrained(item:Object):Boolean { return (unconstrainedRenderers[item] != null); } /** * Cleans up after a data change effect has finished running * by restoring the original collection and iterator and removing * any cached values used by the effect. This method is called by * the Flex framework; you do not need to call it from your code. * * @param event The effect that has finished running. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function finishDataChangeEffect(event:EffectEvent):void { // We are doing a bunch of stuff here, possibly more // than necessary, with the goal of guaranteeing that we leave // the control in an acceptable state. collection = actualCollection; actualCollection = null; modifiedCollectionView = null; iterator = actualIterator; runningDataEffect = false; unconstrainedRenderers = {}; // rendererChanged = true; terminateSelectionTracking(); // at the end of a data effect, in order to make sure we // can re-use the renderers intelligently, we have to // re-key the item renderers in visibleData var newVisibleData:Object = {}; for each (var item:Object in visibleData) if (item.data) newVisibleData[itemToUID(item.data)] = item; visibleData = newVisibleData; // TODO This code seems redundant with the code in cleanupAfterDataChangeEffect(); // need to investigate what's actually needed & where. var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition - offscreenExtraRowsTop); iterator.seek(CursorBookmark.FIRST,index); callLater(cleanupAfterDataChangeEffect); } /** * @private * * Initiates a somewhat expensive relayout of the control after finishing up * a data change effect. */ private function cleanupAfterDataChangeEffect():void { if (runningDataEffect || runDataEffectNextUpdate) return; var index:int = scrollPositionToIndex(horizontalScrollPosition - offscreenExtraColumnsLeft, verticalScrollPosition - offscreenExtraRowsTop); iterator.seek(CursorBookmark.FIRST,index); dataEffectCompleted = true; itemsSizeChanged = true; //rendererChanged = true; invalidateList(); dataItemWrappersByRenderer = new Dictionary(); } /** * Called from the updateDisplayList() method to * adjust the size and position of list content. * * @param unscaledWidth Specifies the width of the component, in pixels, * in the component's coordinates. * * @param unscaledHeight Specifies the height of the component, in pixels, * in the component's coordinates. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function adjustListContent(unscaledWidth:Number = -1, unscaledHeight:Number = -1):void { if (unscaledHeight < 0) { unscaledHeight = oldUnscaledHeight; unscaledWidth = oldUnscaledWidth } var lcx:Number = viewMetrics.left + listContent.leftOffset; var lcy:Number = viewMetrics.top + listContent.topOffset; listContent.move(lcx, lcy); var ww:Number = Math.max(0, listContent.rightOffset) - lcx - viewMetrics.right; var hh:Number = Math.max(0, listContent.bottomOffset) - lcy - viewMetrics.bottom; listContent.setActualSize(unscaledWidth + ww, unscaledHeight + hh); } /** * @private * * Called by updateDisplayList() to make adjustments to vertical and * horizontal scroll position. */ private function adjustScrollPosition():void { if (!isNaN(horizontalScrollPositionPending)) { var hPos:Number = Math.min(horizontalScrollPositionPending, maxHorizontalScrollPosition); horizontalScrollPositionPending = NaN; super.horizontalScrollPosition = hPos; } if (!isNaN(verticalScrollPositionPending)) { var vPos:Number = Math.min(verticalScrollPositionPending, maxVerticalScrollPosition); verticalScrollPositionPending = NaN; super.verticalScrollPosition = vPos; } } /** * @private * * Called by updateDisplayList() to remove existing item renderers * and clean up various caching structures when renderer changes. */ protected function purgeItemRenderers():void { rendererChanged = false; while (listItems.length) { var row:Array = listItems.pop(); while (row.length) { var item:IListItemRenderer = IListItemRenderer(row.pop()); if (item) { listContent.removeChild(DisplayObject(item)); if (dataItemWrappersByRenderer[item]) delete visibleData[itemToUID(dataItemWrappersByRenderer[item])]; else delete visibleData[itemToUID(item.data)]; // addToFreeItemRenderers(item); } } } while (freeItemRenderers.length) { var freeRenderer:DisplayObject = DisplayObject(freeItemRenderers.pop()); if (freeRenderer.parent) listContent.removeChild(freeRenderer); } rowMap = {}; rowInfo = []; } /** * @private * * Called by updateDisplayList() to remove existing item renderers * and clean up various internal structures at the end of running * a data change effect. */ private function partialPurgeItemRenderers():void { dataEffectCompleted = false; // We make sure all free and reserved itemRenderers are de-parented // after a data change effect finishes, and also blow away visibleData // and rowMap. Ideally, this would not be necessary, but it seems // safest at the moment. // Note that the makeRowsAndColumns pass after this function is // called should not make any changes to listItems. while (freeItemRenderers.length) { var freeRenderer:DisplayObject = DisplayObject(freeItemRenderers.pop()); if (freeRenderer.parent) listContent.removeChild(freeRenderer); } for (var p:String in reservedItemRenderers) { freeRenderer = DisplayObject(reservedItemRenderers[p]); if (freeRenderer.parent) listContent.removeChild(freeRenderer); } reservedItemRenderers = {} rowMap = {}; visibleData = {}; } /** * @private * * Called by updateDisplayList() */ private function reduceRows(rowIndex:int):void { while (rowIndex >= 0) { if (rowInfo[rowIndex].y >= listContent.height) { var colLen:int = listItems[rowIndex].length; for (var j:int = 0; j < colLen; j++) addToFreeItemRenderers(listItems[rowIndex][j]); var uid:String = rowInfo[rowIndex].uid; delete visibleData[uid]; removeIndicators(uid); listItems.pop(); // remove the row rowInfo.pop(); rowIndex--; } else break; } } /** * @private * * Called from updateDisplayList() */ private function makeAdditionalRows(rowIndex:int):void { var cursorPos:CursorBookmark; // do layout for additional rows if (iterator) { cursorPos = iterator.bookmark; try { iterator.seek(CursorBookmark.CURRENT, listItems.length - lockedRowCount); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, listItems.length - lockedRowCount) e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); // trace("IPE in UpdateDisplayList"); iteratorValid = false; // don't do anything, we'll repaint when the data arrives } } var curY:Number = rowInfo[rowIndex].y + rowInfo[rowIndex].height; // fill it in makeRowsAndColumns(0, curY, listContent.width, listContent.height, 0, rowIndex + 1); // restore iterator to original position seekPositionIgnoreError(iterator,cursorPos); } /** * @private * * Called from updateDisplayList() to make adjustments to internal * properties representing selections. */ private function adjustSelectionSettings(collectionHasItems:Boolean):void { if (bSelectionChanged) { bSelectionChanged = false; // bSelectedIndexChanged can be true if the dp was reset. // if selectedItem or cousins are also set, we will resolve // selectedIndex in there and therefore don't need to process // it //in each of these cases allow the "unsetting" of a value regardless //of whether the collection has items if (bSelectedIndicesChanged && (collectionHasItems || (_selectedIndices == null))) { bSelectedIndicesChanged = false; bSelectedIndexChanged = false; commitSelectedIndices(_selectedIndices); } if (bSelectedItemChanged && (collectionHasItems || (_selectedItem == null))) { bSelectedItemChanged = false; bSelectedIndexChanged = false; commitSelectedItem(_selectedItem); } if (bSelectedItemsChanged && (collectionHasItems || (_selectedItems == null))) { bSelectedItemsChanged = false; bSelectedIndexChanged = false; commitSelectedItems(_selectedItems); } if (bSelectedIndexChanged && (collectionHasItems || (_selectedIndex == -1))) { commitSelectedIndex(_selectedIndex); bSelectedIndexChanged = false; } } } /** * @private * * Called from updateDisplayList() to seek to a cursorPosition while ignoring any errors */ private function seekPositionIgnoreError(iterator:IViewCursor, cursorPos:CursorBookmark):void { if (iterator) { try { iterator.seek(cursorPos, 0); } catch (e:ItemPendingError) { // we don't recover here since we'd only get here if the first seek failed. } } } /** * @private * * A convenience function to move the iterator to the next position and handle * errors if necessary. */ private function seekNextSafely(iterator:IViewCursor, pos:int):Boolean { try { iterator.moveNext(); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos) e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; } // assumption is that iteratorValid was true when we were called. return iteratorValid; } private function seekPreviousSafely(iterator:IViewCursor, pos:int):Boolean { try { iterator.movePrevious(); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos) e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; } // assumption is that iteratorValid was true when we were called. return iteratorValid; } // TODO make this mx_internal? /** * @private */ protected function seekPositionSafely(index:int):Boolean { try { iterator.seek(CursorBookmark.FIRST, index); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, index); e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; } return iteratorValid; } /** * @private * for automation delegate access */ mx_internal function getListVisibleData():Object { return visibleData; } /** * @private * for automation delegate access */ mx_internal function getItemUID(data:Object):String { return itemToUID(data); } /** * @private * for automation delegate access */ mx_internal function getItemRendererForMouseEvent(event:MouseEvent):IListItemRenderer { return mouseEventToItemRenderer(event); } /** * @private * for automation delegate access */ mx_internal function getListContentHolder():AdvancedListBaseContentHolder { return listContent; } /** * @private * for automation delegate access */ mx_internal function getRowInfo():Array { return rowInfo; } /** * @private * for automation delegate access */ mx_internal function convertIndexToRow(index:int):int { return indexToRow(index); } /** * @private * for automation delegate access */ mx_internal function convertIndexToColumn(index:int):int { return indexToColumn(index); } /** * @private * for automation delegate access */ mx_internal function getCaretIndex():int { return caretIndex; } /** * @private * for automation delegate access */ mx_internal function getIterator():IViewCursor { return iterator; } /** * @private * Move the indicators up or down by the given offset. * This method assumes that all the selection indicators in * this row are at the same 'y' position. */ protected function moveIndicators(uid:String, offset:int, absolute:Boolean):void { if (selectionIndicators[uid]) { if (absolute) selectionIndicators[uid].y = offset; else selectionIndicators[uid].y += offset; } if (highlightUID == uid) { if (absolute) highlightIndicator.y = offset; else highlightIndicator.y += offset; } if (caretUID == uid) { if (absolute) caretIndicator.y = offset; else caretIndicator.y += offset; } } /** * Internal version of setting columnWidth * without invalidation or notification. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ mx_internal function setColumnWidth(value:Number):void { _columnWidth = value; } /** * Sets the rowCount property without causing * invalidation or setting the explicitRowCount * property, which permanently locks in the number of rows. * * @param v The row count. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function setRowCount(v:int):void { //trace("setRowCount " + v); _rowCount = v; } /** * Sets the rowHeight property without causing invalidation or * setting of explicitRowHeight which * permanently locks in the height of the rows. * * @param v The row height, in pixels. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function setRowHeight(v:Number):void { _rowHeight = v; } //-------------------------------------------------------------------------- // // Overridden event handlers: UIComponent // //-------------------------------------------------------------------------- /** * @private */ override protected function keyDownHandler(event:KeyboardEvent):void { if (!selectable) return; if (!iteratorValid) return; if (!collection) return; switch (event.keyCode) { case Keyboard.UP: case Keyboard.DOWN: { moveSelectionVertically( event.keyCode, event.shiftKey, event.ctrlKey); event.stopPropagation(); break; } case Keyboard.LEFT: case Keyboard.RIGHT: { moveSelectionHorizontally( event.keyCode, event.shiftKey, event.ctrlKey); event.stopPropagation(); break; } case Keyboard.END: case Keyboard.HOME: case Keyboard.PAGE_UP: case Keyboard.PAGE_DOWN: { moveSelectionVertically( event.keyCode, event.shiftKey, event.ctrlKey); event.stopPropagation(); break; } case Keyboard.SPACE: { if (caretIndex != -1 && ((caretIndex - verticalScrollPosition + lockedRowCount) >= 0) && ((caretIndex - verticalScrollPosition + lockedRowCount) < listItems.length)) { var li:IListItemRenderer = listItems[caretIndex - verticalScrollPosition + lockedRowCount][0]; if (selectItem(li, event.shiftKey, event.ctrlKey)) { var pt:Point = itemRendererToIndices(li); var evt:ListEvent = new ListEvent(ListEvent.CHANGE); if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } evt.itemRenderer = li; dispatchEvent(evt); } } break; } default: { if (findKey(event.keyCode)) event.stopPropagation(); } } } /** * Handles mouseWheel events by changing scroll positions. * This is a copy of the version in the ScrollControlBase class, * modified to change the horizontalScrollPosition property * if the target is run horizontally. * * @param event The MouseEvent object. * * @see mx.core.ScrollControlBase#mouseWheelHandler() * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override protected function mouseWheelHandler(event:MouseEvent):void { if (verticalScrollBar && verticalScrollBar.visible) { event.stopPropagation(); var oldPosition:Number = verticalScrollPosition; var newPos:int = verticalScrollPosition; newPos -= event.delta * verticalScrollBar.lineScrollSize; newPos = Math.max(0, Math.min(newPos, verticalScrollBar.maxScrollPosition)); verticalScrollPosition = newPos; if (oldPosition != verticalScrollPosition) { var scrollEvent:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL); scrollEvent.direction = ScrollEventDirection.VERTICAL; scrollEvent.position = verticalScrollPosition; scrollEvent.delta = verticalScrollPosition - oldPosition; dispatchEvent(scrollEvent); } } } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * Handles CollectionEvents dispatched from the data provider * as the data changes. * Updates the renderers, selected indices and scrollbars as needed. * * @param event The CollectionEvent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function collectionChangeHandler(event:Event):void { var len:int; var i:int; var n:int; var data:ListBaseSelectionData; var p:String; var selectedUID:String; if (event is CollectionEvent) { var ce:CollectionEvent = CollectionEvent(event); // trace("ListBase collectionEvent", ce.kind); if (ce.kind == CollectionEventKind.ADD) { prepareDataEffect(ce); // trace("ListBase collectionEvent ADD", ce.location, verticalScrollPosition); // special case when we have less than a screen full of stuff if (ce.location == 0 && verticalScrollPosition == 0) { try { // trace("ListBase collectionEvent ADD adjust"); iterator.seek(CursorBookmark.FIRST); } catch (e:ItemPendingError) { // trace("IPE in ADD event"); lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0) e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; // do nothing, we'll repaint when the data arrives } } else if (listType == "vertical" && verticalScrollPosition >= ce.location) { super.verticalScrollPosition = super.verticalScrollPosition + ce.items.length; } len = ce.items.length; for (p in selectedData) { data = selectedData[p]; if (data.index > ce.location) data.index += len; } if (_selectedIndex >= ce.location) { _selectedIndex += len; dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } } else if (ce.kind == CollectionEventKind.REPLACE) { // trace("ListBase collectionEvent REPLACE"); selectedUID = selectedItem ? itemToUID(selectedItem) : null; n = ce.items.length; for (i = 0; i < n; i++) { var oldUID:String = itemToUID(ce.items[i].oldValue); var sd:ListBaseSelectionData = selectedData[oldUID]; if (sd) { sd.data = ce.items[i].newValue; delete selectedData[oldUID]; selectedData[itemToUID(sd.data)] = sd; if (selectedUID == oldUID) { _selectedItem = sd.data; dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } } } prepareDataEffect(ce); } else if (ce.kind == CollectionEventKind.REMOVE) { prepareDataEffect(ce); var requiresValueCommit:Boolean = false; // trace("ListBase collectionEvent REMOVE", ce.location, verticalScrollPosition); // make sure we've generated rows for the actual data // at startup time we might just have blank rows if (listItems.length && listItems[lockedRowCount].length) { // special case when we have less than a screen full of stuff var firstUID:String = rowMap[listItems[lockedRowCount][0].name].uid; selectedUID = selectedItem ? itemToUID(selectedItem) : null; n = ce.items.length; for (i = 0; i < n; i++) { var uid:String = itemToUID(ce.items[i]); if (uid == firstUID && verticalScrollPosition == 0) { try { iterator.seek(CursorBookmark.FIRST); } catch (e1:ItemPendingError) { // trace("IPE in REMOVE event"); lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0) e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; // do nothing, we'll repaint when the data arrives } } if (selectedData[uid]) removeSelectionData(uid); if (selectedUID == uid) { _selectedItem = null; _selectedIndex = -1; requiresValueCommit = true; } removeIndicators(uid); } // Decrement verticalScrollPosition by the number of items that have // been removed from the top. if (listType == "vertical" && verticalScrollPosition >= ce.location) { if (verticalScrollPosition > ce.location) { super.verticalScrollPosition = verticalScrollPosition - Math.min(ce.items.length, verticalScrollPosition - ce.location); } else { // else the underlying collection goes to invalid cuz we removed the current // so we force a re-seek next, but we want to keep everything in range if (verticalScrollPosition >= collection.length) super.verticalScrollPosition = Math.max(collection.length - 1, 0); } try { offscreenExtraRowsTop = Math.min(offscreenExtraRowsTop, verticalScrollPosition); iterator.seek(CursorBookmark.FIRST, verticalScrollPosition - offscreenExtraRowsTop); } catch (e2:ItemPendingError) { // trace("IPE in Remove 2"); lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition - offscreenExtraRowsTop) e2.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; // do nothing, we'll repaint when the data arrives } } var emitEvent:Boolean = adjustAfterRemove(ce.items, ce.location, requiresValueCommit); if (emitEvent) dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } } else if (ce.kind == CollectionEventKind.MOVE) { if (ce.oldLocation < ce.location) { for (p in selectedData) { data = selectedData[p]; if (data.index > ce.oldLocation && data.index < ce.location) data.index --; else if (data.index == ce.oldLocation) data.index = ce.location; } if (_selectedIndex > ce.oldLocation && _selectedIndex < ce.location) _selectedIndex --; else if (_selectedIndex == ce.oldLocation) _selectedIndex = ce.location; } else if (ce.location < ce.oldLocation) { for (p in selectedData) { data = selectedData[p]; if (data.index > ce.location && data.index < ce.oldLocation) data.index ++; else if (data.index == ce.oldLocation) data.index = ce.location; } if (_selectedIndex > ce.location && _selectedIndex < ce.oldLocation) _selectedIndex ++; else if (_selectedIndex == ce.oldLocation) _selectedIndex = ce.location; } // if the current item got moved if (ce.oldLocation == verticalScrollPosition) { // iterator is at new position, jump to it, // but make sure we don't max out first if (ce.location > maxVerticalScrollPosition) { iterator.seek(CursorBookmark.CURRENT, maxVerticalScrollPosition - ce.location); } super.verticalScrollPosition = Math.min(ce.location, maxVerticalScrollPosition); } // if the old location and new location are on // different sides of the scrollposition else if ((ce.location >= verticalScrollPosition) && (ce.oldLocation < verticalScrollPosition)) seekNextSafely(iterator,verticalScrollPosition); else if ((ce.location <= verticalScrollPosition) && (ce.oldLocation > verticalScrollPosition)) seekPreviousSafely(iterator,verticalScrollPosition); } else if (ce.kind == CollectionEventKind.REFRESH) { if (anchorBookmark) { try { iterator.seek(anchorBookmark, 0); } catch (e:ItemPendingError) { bSortItemPending = true; lastSeekPending = new ListBaseSeekPending(anchorBookmark, 0); e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); // trace("IPE in UpdateDisplayList"); iteratorValid = false; } catch (cursorError:CursorError) { // might have been filtered out so // clear selections clearSelected(); } adjustAfterSort(); } else { try { iterator.seek(CursorBookmark.FIRST, verticalScrollPosition); } catch (e:ItemPendingError) { bSortItemPending = true; lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, verticalScrollPosition) e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); // trace("IPE in UpdateDisplayList"); iteratorValid = false; } } } else if (ce.kind == CollectionEventKind.RESET) { // RemoveAll() on ArrayCollection currently triggers a reset // Special handling for this case. if ((collection.length == 0) || (runningDataEffect && actualCollection.length == 0)) { // All the data is already gone, so in order to run a data // effect, we reconstruct it from the renderers var deletedItems:Array = reconstructDataFromListItems(); if (deletedItems.length) { var fakeRemove:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); fakeRemove.kind = CollectionEventKind.REMOVE; fakeRemove.items = deletedItems; fakeRemove.location = 0; prepareDataEffect(fakeRemove); } } try { iterator.seek(CursorBookmark.FIRST); collectionIterator.seek(CursorBookmark.FIRST); } catch (e:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0); e.addResponder(new ItemResponder( seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); iteratorValid = false; } if (bSelectedIndexChanged || bSelectedItemChanged || bSelectedIndicesChanged || bSelectedItemsChanged) { bSelectionChanged = true; } else { commitSelectedIndex(-1); } if (isNaN(verticalScrollPositionPending)) { verticalScrollPositionPending = 0; super.verticalScrollPosition = 0; } if (isNaN(horizontalScrollPositionPending)) { horizontalScrollPositionPending = 0; super.horizontalScrollPosition = 0; } invalidateSize(); } } itemsSizeChanged = true; invalidateDisplayList(); } /** * @private * Reconstructs an array of items for a pseudo-data provider. Used to * leverage the data effects infrastructure after removeAll() has been * called on the data provider. * * Subclasses may need to override this function, e.g. for TileLists * with vertical layout. */ mx_internal function reconstructDataFromListItems():Array { if (!listItems) return []; var items:Array = []; // might make sense to ignore offscreen rows here var n:int = listItems.length; for (var i:int = 0; i < n; i++) { if (listItems[i]) { var renderer:IListItemRenderer = listItems[i][0] as IListItemRenderer; var data:Object; var data2:Object; if (renderer) { data = renderer.data; items.push(data); var m:int = listItems[i].length; for (var j:int = 0; j < m; j++) { renderer = listItems[i][j] as IListItemRenderer; if (renderer) { data2 = renderer.data; if (data2 != data) items.push(data2); } } } } } return items; } /** * @private */ protected function prepareDataEffect(ce:CollectionEvent):void { if (!cachedDataChangeEffect) { // Style can set dataChangeEffect to an Effect object // or a Class which is a subclass of Effect var dce:Object = getStyle("dataChangeEffect"); var dceClass:Class = dce as Class; if (dceClass) dce = new dceClass(); cachedDataChangeEffect = dce as Effect; } if (runningDataEffect) { collection = actualCollection; iterator = actualIterator; cachedDataChangeEffect.end(); modifiedCollectionView = null; } // For now, if iterator is not valid, don't run the data effect. if (cachedDataChangeEffect && iteratorValid) { var firstItemIndex:int = iterator.bookmark.getViewIndex(); var lastItemIndex:int = firstItemIndex + (rowCount * columnCount) - 1; if (!modifiedCollectionView && (collection is IList)) modifiedCollectionView = new ModifiedCollectionView(ICollectionView(collection)); if (modifiedCollectionView) { modifiedCollectionView.processCollectionEvent(ce, firstItemIndex, lastItemIndex); runDataEffectNextUpdate = true; if (mx_internal::invalidateDisplayListFlag) callLater(invalidateList); else invalidateList(); } } } /** * @private */ protected function adjustAfterRemove(items:Array, location:int, emitEvent:Boolean):Boolean { var data:ListBaseSelectionData; var requiresValueCommit:Boolean = emitEvent; var i:int = 0; var length:int = items.length; for (var p:String in selectedData) { i++; data = selectedData[p]; if (data.index > location) data.index -= length; } if (_selectedIndex > location) { _selectedIndex -= length; requiresValueCommit = true; } // selected the last thing if the selected item // got removed. if (i > 0 && _selectedIndex == -1) { _selectedIndex = data.index; _selectedItem = data.data; requiresValueCommit = true; } if (i == 0) { _selectedIndex = -1; bSelectionChanged = true; bSelectedIndexChanged = true; invalidateDisplayList(); } return requiresValueCommit; } /** * Handles MouseEvent.MOUSE_OVER events from any mouse * targets contained in the list, including the renderers. * This method finds out which renderer the mouse is over * and shows it as highlighted. * *

The list classes also call this from a * MouseEvent.MOUSE_MOVE event. * This event is used to detect movement in non-target areas of the * renderers and in padded areas around the renderers.

* * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseOverHandler(event:MouseEvent):void { var evt:ListEvent; if (!enabled || !selectable) return; if (dragScrollingInterval != 0 && !event.buttonDown) { // trace("mouseOver found mouse not pressed but dragInterval"); mouseIsUp(); } isPressed = event.buttonDown; var item:IListItemRenderer = mouseEventToItemRenderer(event); var pt:Point = itemRendererToIndices(item); if (!item) return; var uid:String = itemToUID(item.data); if (!isPressed || allowDragSelection) { // we're rolling onto different subpieces of ourself or our highlight indicator if (event.relatedObject) { var lastUID:String; if (lastHighlightItemRenderer && highlightUID) { var rowData:BaseListData = rowMap[item.name]; lastUID = rowData.uid; } if (itemRendererContains(item, event.relatedObject) || uid == lastUID || event.relatedObject == highlightIndicator) return; } if (getStyle("useRollOver") && (item.data != null)) { if (allowDragSelection) bSelectOnRelease = true; drawItem(visibleData[uid], isItemSelected(item.data), true, uid == caretUID); if (pt) // during tweens, we may get null { evt = new ListEvent(ListEvent.ITEM_ROLL_OVER); evt.columnIndex = pt.x; evt.rowIndex = pt.y; evt.itemRenderer = item; dispatchEvent(evt); lastHighlightItemIndices = pt; lastHighlightItemRendererAtIndices = item; } } } else { if (DragManager.isDragging) return; if ((dragScrollingInterval != 0 && allowDragSelection) || menuSelectionMode) { if (selectItem(item, event.shiftKey, event.ctrlKey)) { evt = new ListEvent(ListEvent.CHANGE); evt.itemRenderer = item; if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } dispatchEvent(evt); } } } } /** * Handles MouseEvent.MOUSE_OUT events from any mouse targets * contained in the list including the renderers. This method * finds out which renderer the mouse has left * and removes the highlights. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseOutHandler(event:MouseEvent):void { if (!enabled || !selectable) return; isPressed = event.buttonDown; var item:IListItemRenderer = mouseEventToItemRenderer(event); if (!item) return; if (!isPressed) { // either we're rolling onto different subpieces of ourself or our // highlight indicator, or the clearing of the highlighted item has // already happened care of the mouseMove handler if (itemRendererContains(item, event.relatedObject) || event.relatedObject == listContent || event.relatedObject == highlightIndicator || !highlightItemRenderer) return; if (getStyle("useRollOver") && item.data != null) clearHighlight(item); } } /** * Handles MouseEvent.MOUSE_MOVE events from any mouse * targets contained in the list including the renderers. This method * watches for a gesture that constitutes the beginning of a * drag drop and send a DragEvent.DRAG_START event. * It also checks to see if the mouse is over a non-target area of a * renderer so that Flex can try to make it look like that renderer was * the target. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseMoveHandler(event:MouseEvent):void { if (!enabled || !selectable) return; var pt:Point = new Point(event.localX, event.localY); pt = DisplayObject(event.target).localToGlobal(pt); pt = globalToLocal(pt); if (isPressed && mouseDownPoint && (Math.abs(mouseDownPoint.x - pt.x) > DRAG_THRESHOLD || Math.abs(mouseDownPoint.y - pt.y) > DRAG_THRESHOLD)) { if (dragEnabled && !DragManager.isDragging && mouseDownPoint) { var dragEvent:DragEvent = new DragEvent(DragEvent.DRAG_START); dragEvent.dragInitiator = this; dragEvent.localX = mouseDownPoint.x; dragEvent.localY = mouseDownPoint.y; dragEvent.buttonDown = true; dispatchEvent(dragEvent); } } // we used to put shields into each of the renderers so leftover space was hittable // but that's makes too many shields at startup and scrolling. The gamble is that we // can run the code even on a large grid very quickly compared to mouse move intervals. var item:IListItemRenderer = mouseEventToItemRenderer(event); if (item && highlightItemRenderer) { var rowData:BaseListData = rowMap[item.name]; if (highlightItemRenderer && highlightUID && rowData.uid != highlightUID) { if (!isPressed) { if (getStyle("useRollOver") && highlightItemRenderer.data != null) { clearHighlight(highlightItemRenderer) } } } } else if (!item && highlightItemRenderer) { if (!isPressed) { if (getStyle("useRollOver") && highlightItemRenderer.data) { clearHighlight(highlightItemRenderer) } } } if (item && !highlightItemRenderer) { mouseOverHandler(event); } } /** * Handles MouseEvent.MOUSE_DOWN events from any mouse * targets contained in the list including the renderers. This method * finds the renderer that was pressed and prepares to receive * a MouseEvent.MOUSE_UP event. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseDownHandler(event:MouseEvent):void { if (!enabled || !selectable) return; // trace("mouseDown"); isPressed = true; var item:IListItemRenderer = mouseEventToItemRenderer(event); if (!item) return; bSelectOnRelease = false; var pt:Point = new Point(event.localX, event.localY); pt = DisplayObject(event.target).localToGlobal(pt); mouseDownPoint = globalToLocal(pt); systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, 0, true); systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseLeaveHandler, false, 0, true); if (!dragEnabled) { dragScrollingInterval = setInterval(dragScroll, 15); } if (dragEnabled) mouseDownIndex = itemRendererToIndex(item); // If dragEnabled is true, clicks on selected contents should cause // a selection change on mouse up instead of mouse down. Otherwise, // clicking in a selection to drag will deselect any multiple selection // before the drag occurs. if (dragEnabled && selectedData[rowMap[item.name].uid]) { bSelectOnRelease = true; } else { if (selectItem(item, event.shiftKey, event.ctrlKey)) mouseDownItem = item; } } private function mouseIsUp():void { systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, mouseLeaveHandler); if (!dragEnabled && dragScrollingInterval != 0) { clearInterval(dragScrollingInterval); dragScrollingInterval = 0; } } private function mouseLeaveHandler(event:Event):void { mouseDownPoint = null; mouseDownIndex = -1; mouseIsUp(); if (!enabled || !selectable) return; if (mouseDownItem) { var evt:ListEvent = new ListEvent(ListEvent.CHANGE); evt.itemRenderer = mouseDownItem; var pt:Point = itemRendererToIndices(mouseDownItem); if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } dispatchEvent(evt); mouseDownItem = null; } isPressed = false; } /** * Handles MouseEvent.MOUSE_DOWN events from any mouse * targets contained in the list including the renderers. This method * finds the renderer that was pressed and prepares to receive * a MouseEvent.MOUSE_UP event. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseUpHandler(event:MouseEvent):void { mouseDownPoint = null; mouseDownIndex = -1; //trace("mouseUp"); var item:IListItemRenderer = mouseEventToItemRenderer(event); var pt:Point = itemRendererToIndices(item); var evt:ListEvent; mouseIsUp(); if (!enabled || !selectable) return; if (mouseDownItem) { evt = new ListEvent(ListEvent.CHANGE); evt.itemRenderer = mouseDownItem; pt = itemRendererToIndices(mouseDownItem); if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } dispatchEvent(evt); mouseDownItem = null; } if (!item || !hitTestPoint(event.stageX, event.stageY)) { isPressed = false; return; } if (bSelectOnRelease) { bSelectOnRelease = false; if (selectItem(item, event.shiftKey, event.ctrlKey)) { evt = new ListEvent(ListEvent.CHANGE); evt.itemRenderer = item; if (pt) { evt.columnIndex = pt.x; evt.rowIndex = pt.y; } dispatchEvent(evt); } } isPressed = false; } /** * Handles MouseEvent.MOUSE_CLICK events from any mouse * targets contained in the list including the renderers. This method * determines which renderer was clicked * and dispatches a ListEvent.ITEM_CLICK event. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseClickHandler(event:MouseEvent):void { var item:IListItemRenderer = mouseEventToItemRenderer(event); if (!item) return; var pt:Point = itemRendererToIndices(item); if (pt) // during tweens, we may get null { var listEvent:ListEvent = new ListEvent(ListEvent.ITEM_CLICK); listEvent.columnIndex = pt.x; listEvent.rowIndex = pt.y; listEvent.itemRenderer = item; dispatchEvent(listEvent); } } /** * Handles MouseEvent.MOUSE_DOUBLE_CLICK events from any * mouse targets contained in the list including the renderers. * This method determines which renderer was clicked * and dispatches a ListEvent.ITEM_DOUBLE_CLICK event. * * @param event The MouseEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function mouseDoubleClickHandler(event:MouseEvent):void { var item:IListItemRenderer = mouseEventToItemRenderer(event); if (!item) return; var pt:Point = itemRendererToIndices(item); if (pt) // during tweens, we may get null { var listEvent:ListEvent = new ListEvent(ListEvent.ITEM_DOUBLE_CLICK); listEvent.columnIndex = pt.x; listEvent.rowIndex = pt.y; listEvent.itemRenderer = item; dispatchEvent(listEvent); } } /** * The default handler for the dragStart event. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragStartHandler(event:DragEvent):void { if (event.isDefaultPrevented()) return; var dragSource:DragSource = new DragSource(); addDragData(dragSource); DragManager.doDrag(this, dragSource, event, dragImage, 0, 0, 0.5, dragMoveEnabled); } /** * Handles DragEvent.DRAG_ENTER events. This method * determines if the DragSource object contains valid elements and uses * the showDropFeedback() method to set up the UI feedback. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragEnterHandler(event:DragEvent):void { if (event.isDefaultPrevented()) return; lastDragEvent = event; if (enabled && iteratorValid && (event.dragSource.hasFormat("items") || event.dragSource.hasFormat("itemsByIndex"))) { DragManager.acceptDragDrop(this); DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE); showDropFeedback(event); return; } hideDropFeedback(event); DragManager.showFeedback(DragManager.NONE); } /** * Handles DragEvent.DRAG_OVER events. This method * determines if the DragSource object contains valid elements and uses * the showDropFeedback() method to set up the UI feeback. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragOverHandler(event:DragEvent):void { if (event.isDefaultPrevented()) return; lastDragEvent = event; if (enabled && iteratorValid && (event.dragSource.hasFormat("items") || event.dragSource.hasFormat("itemsByIndex"))) { DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : DragManager.MOVE); showDropFeedback(event); return; } hideDropFeedback(event); DragManager.showFeedback(DragManager.NONE); } /** * Handles DragEvent.DRAG_EXIT events. This method hides * the UI feeback by calling the hideDropFeedback() method. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragExitHandler(event:DragEvent):void { if (event.isDefaultPrevented()) return; lastDragEvent = null; hideDropFeedback(event); resetDragScrolling(); DragManager.showFeedback(DragManager.NONE); } /** * Handles DragEvent.DRAG_DROP events. This method hides * the UI feeback by calling the hideDropFeedback() method. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragDropHandler(event:DragEvent):void { if (event.isDefaultPrevented()) return; hideDropFeedback(event); lastDragEvent = null; resetDragScrolling(); if (!enabled) return; var dragSource:DragSource = event.dragSource; if (!dragSource.hasFormat("items") && !dragSource.hasFormat("itemsByIndex")) return; if (!dataProvider) // Create an empty collection to drop items into. dataProvider = []; var dropIndex:int = calculateDropIndex(event); if (dragSource.hasFormat("items")) insertItems(dropIndex, dragSource, event); else insertItemsByIndex(dropIndex, dragSource, event); lastDragEvent = null; } /** * Handles DragEvent.DRAG_COMPLETE events. This method * removes the item from the data provider. * * @param event The DragEvent object. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function dragCompleteHandler(event:DragEvent):void { isPressed = false; if (event.isDefaultPrevented()) return; if (event.action == DragManager.MOVE && dragMoveEnabled) { if (event.relatedObject != this) { var indices:Array = selectedIndices; // clear the selection, otherwise we'll be adjusting it on every element being removed clearSelected(false); indices.sort(Array.NUMERIC); var n:int = indices.length; for (var i:int = n - 1; i >= 0; i--) { collectionIterator.seek(CursorBookmark.FIRST, indices[i]); collectionIterator.remove(); } clearSelected(false); } } // this can probably be removed b/c it's in dragExit and dragDrop, but leaving these two // lines for now lastDragEvent = null; resetDragScrolling(); } /** * @private */ private function selectionTween_updateHandler(event:TweenEvent):void { Sprite(event.target.listener).alpha = Number(event.value); } /** * @private */ private function selectionTween_endHandler(event:TweenEvent):void { selectionTween_updateHandler(event); } /** * @private * Handles item renderers moving after initiateSelectionTracking() has been * called. This is used during data effects to redraw selections after * item renderers move. * */ private function rendererMoveHandler(event:MoveEvent):void { var renderer:IListItemRenderer = event.currentTarget as IListItemRenderer; // currently, the assumption is that this is only called for renderers that // are representing selected items. drawItem(renderer,true); } } }