blob: 46302e77e61930f512a9156133eb0fccdde5c316 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.controls
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import flash.utils.Dictionary;
import mx.collections.CursorBookmark;
import mx.collections.IList;
import mx.collections.ItemResponder;
import mx.collections.ItemWrapper;
import mx.collections.ModifiedCollectionView;
import mx.collections.errors.ItemPendingError;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBase;
import mx.controls.listClasses.ListBaseContentHolder;
import mx.controls.listClasses.ListBaseSeekPending;
import mx.controls.listClasses.ListData;
import mx.controls.listClasses.ListItemRenderer;
import mx.controls.listClasses.ListRowInfo;
import mx.controls.scrollClasses.ScrollBar;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.EventPriority;
import mx.core.FlexShape;
import mx.core.FlexSprite;
import mx.core.IChildList;
import mx.core.IFactory;
import mx.core.IIMESupport;
import mx.core.IInvalidating;
import mx.core.IPropertyChangeNotifier;
import mx.core.IUIComponent;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.ListEvent;
import mx.events.ListEventReason;
import mx.events.SandboxMouseEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.managers.IFocusManager;
import mx.managers.IFocusManagerComponent;
import mx.managers.ISystemManager;
use namespace mx_internal;
[IconFile("List.png")]
[DataBindingInfo("acceptedTypes", "{ dataProvider: { label: "String" } }")]
[DefaultProperty("dataProvider")]
[DefaultBindingProperty(source="selectedItem", destination="dataProvider")]
[DefaultTriggerEvent("change")]
[AccessibilityClass(implementation="mx.accessibility.ListAccImpl")]
/**
* Dispatched when the user releases the mouse button while over an item,
* tabs to the List or within the List, or in any other way
* attempts to edit an item.
*
* @eventType mx.events.ListEvent.ITEM_EDIT_BEGINNING
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemEditBeginning", type="mx.events.ListEvent")]
/**
* Dispatched when the <code>editedItemPosition</code> property is set
* and the item can be edited.
*
* @eventType mx.events.ListEvent.ITEM_EDIT_BEGIN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemEditBegin", type="mx.events.ListEvent")]
/**
* Dispatched when an item editing session is ending for any reason.
*
* @eventType mx.events.ListEvent.ITEM_EDIT_END
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemEditEnd", type="mx.events.ListEvent")]
/**
* Dispatched when an item renderer gets focus, which can occur if the user
* clicks on an item in the List control or navigates to the item using a
* keyboard.
* Only dispatched if the list item is editable.
*
* @eventType mx.events.ListEvent.ITEM_FOCUS_IN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemFocusIn", type="mx.events.ListEvent")]
/**
* Dispatched when an item renderer loses the focus, which can occur if the
* user clicks another item in the List control or outside the list,
* or uses the keyboard to navigate to another item in the List control
* or outside the List control.
* Only dispatched if the list item is editable.
*
* @eventType mx.events.ListEvent.ITEM_FOCUS_OUT
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemFocusOut", type="mx.events.ListEvent")]
//--------------------------------------
// Effects
//--------------------------------------
/**
* The data effect to play when a change occur to the control's data provider.
*
* <p>By default, the List control does not use a data effect.
* For the List control, use an instance of the the DefaultListEffect class
* to configure the data effect. </p>
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Effect(name="itemsChangeEffect", event="itemsChange")]
[Alternative(replacement="spark.components.List", since="4.0")]
/**
* The List control displays a vertical list of items.
* Its functionality is very similar to that of the SELECT
* form element in HTML.
* If there are more items than can be displayed at once, it
* can display a vertical scroll bar so the user can access
* all items in the list.
* An optional horizontal scroll bar lets the user view items
* when the full width of the list items is unlikely to fit.
* The user can select one or more items from the list, depending
* on the value of the <code>allowMultipleSelection</code> property.
*
* <p>The List control has the following default sizing
* characteristics:</p>
* <table class="innertable">
* <tr>
* <th>Characteristic</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>Default size</td>
* <td>Wide enough to fit the widest label in the first seven
* visible items (or all visible items in the list, if
* there are less than seven); seven rows high, where
* each row is 20 pixels high.</td>
* </tr>
* <tr>
* <td>Minimum size</td>
* <td>0 pixels.</td>
* </tr>
* <tr>
* <td>Maximum size</td>
* <td>5000 by 5000.</td>
* </tr>
* </table>
*
* @mxml
*
* <p>The <code>&lt;mx:List&gt;</code> tag inherits all the tag attributes
* of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:List
* <b>Properties</b>
* editable="false|true"
* editedItemPosition="<i>No default</i>"
* editorDataField="text"
* editorHeightOffset="0"
* editorUsesEnterKey="false|true"
* editorWidthOffset="0"
* editorXOffset="0"
* editorYOffset="0"
* imeMode="null"
* itemEditor="TextInput"
* itemEditorInstance="<i>Current item editor</i>"
* rendererIsEditor="false|true"
*
* <b>Styles</b>
* backgroundDisabledColor="0xDDDDDD"
*
* <b>Events</b>
* itemEditBegin="<i>No default</i>"
* itemEditEnd="<i>No default</i>"
* itemEditBeginning="<i>No default</i>"
* itemFocusIn="<i>No default</i>"
* itemFocusOut="<i>No default</i>"
* /&gt;
* </pre>
*
* @includeExample examples/SimpleList.mxml
*
* @see mx.events.ListEvent
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class List extends ListBase implements IIMESupport
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ListAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function List()
{
super();
listType = "vertical";
bColumnScrolling = false;
itemRenderer = new ClassFactory(ListItemRenderer);
_horizontalScrollPolicy = ScrollPolicy.OFF;
_verticalScrollPolicy = ScrollPolicy.AUTO;
defaultColumnCount = 1;
defaultRowCount = 7;
// Register default handlers for item editing and sorting events.
addEventListener(ListEvent.ITEM_EDIT_BEGINNING,
itemEditorItemEditBeginningHandler,
false, EventPriority.DEFAULT_HANDLER);
addEventListener(ListEvent.ITEM_EDIT_BEGIN,
itemEditorItemEditBeginHandler,
false, EventPriority.DEFAULT_HANDLER);
addEventListener(ListEvent.ITEM_EDIT_END,
itemEditorItemEditEndHandler,
false, EventPriority.DEFAULT_HANDLER);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* A reference to the currently active instance of the item editor,
* if it exists.
*
* <p>To access the item editor instance and the new item value when an
* item is being edited, you use the <code>itemEditorInstance</code>
* property. The <code>itemEditorInstance</code> property
* is not valid until after the event listener for
* the <code>itemEditBegin</code> event executes. Therefore, you typically
* only access the <code>itemEditorInstance</code> property from within
* the event listener for the <code>itemEditEnd</code> event.</p>
*
* <p>The <code>itemEditor</code> property defines the
* class of the item editor
* and, therefore, the data type of the item editor instance.</p>
*
* <p>You do not set this property in MXML.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var itemEditorInstance:IListItemRenderer;
/**
* A reference to the item renderer
* in the DataGrid control whose item is currently being edited.
*
* <p>From within an event listener for the <code>itemEditBegin</code>
* and <code>itemEditEnd</code> events,
* you can access the current value of the item being edited
* using the <code>editedItemRenderer.data</code> property.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get editedItemRenderer():IListItemRenderer
{
if (!itemEditorInstance) return null;
return listItems[actualRowIndex][actualColIndex];
}
/**
* @private
* true if we want to block editing on mouseUp
*/
private var dontEdit:Boolean = false;
/**
* @private
* true if we want to block editing on mouseUp
*/
private var losingFocus:Boolean = false;
/**
* @private
* true if we're in the endEdit call. Used to handle
* some timing issues with collection updates
*/
private var inEndEdit:Boolean = false;
// last known position of item editor
private var actualRowIndex:int;
private var actualColIndex:int = 0;
/**
* cache of measuring objects by factory
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var measuringObjects:Dictionary;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// maxHorizontalScrollPosition
//----------------------------------
/**
* @private
* The maximum value of <code>horizontalScrollPosition</code> in pixels.
* The default value is NaN.
* If this value is NaN, the first time the List is layed out
* it sets <code>horizontalScrollPosition</code> to twice the width.
* You can calculate the exact value of
* <code>maxHorizontalScrollPosition</code> by calling
* the <code>measureWidthOfItems()</code> method on the widest string,
* and then subtracting the width of the List and the width of its borders.
*
* <p>For example if the fifth item is the widest,
* you set <code>maxHorizontalScrollPosition</code> like this:</p>
* <pre>list.maxHorizontalScrollPosition = list.measureWidthOfItems(5, 1) - (list.width -
* list.viewMetrics.left - list.viewMetrics.right)</pre>
*/
override public function set maxHorizontalScrollPosition(value:Number):void
{
super.maxHorizontalScrollPosition = value;
scrollAreaChanged = true;
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// editable
//----------------------------------
private var _editable:Boolean = false;
[Inspectable(category="General")]
/**
* A flag that indicates whether or not the user can edit
* items in the data provider.
* If <code>true</code>, the item renderers in the control are editable.
* The user can click on an item renderer to open an editor.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get editable():Boolean
{
return _editable;
}
/**
* @private
*/
public function set editable(value:Boolean):void
{
_editable = value;
}
//----------------------------------
// itemEditor
//----------------------------------
[Inspectable(category="Data")]
/**
* The class factory for the item editor to use for the control, if the
* <code>editable</code> property is set to <code>true</code>.
*
* @default new ClassFactory(mx.controls.TextInput)
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var itemEditor:IFactory;
//----------------------------------
// editorDataField
//----------------------------------
[Inspectable(category="Data")]
/**
* The name of the property of the item editor that contains the new
* data for the list item.
* For example, the default <code>itemEditor</code> is
* TextInput, so the default value of the <code>editorDataField</code> property is
* <code>"text"</code>, which specifies the <code>text</code> property of the
* the TextInput control.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorDataField:String = "text";
//----------------------------------
// editorHeightOffset
//----------------------------------
[Inspectable(defaultValue="0")]
/**
* The height of the item editor, in pixels, relative to the size of the
* item renderer. This property can be used to make the editor overlap
* the item renderer by a few pixels to compensate for a border around the
* editor.
* <p>Changing these values while the editor is displayed
* will have no effect on the current editor, but will affect the next
* item renderer that opens an editor.</p>
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorHeightOffset:Number = 0;
//----------------------------------
// editorWidthOffset
//----------------------------------
[Inspectable(defaultValue="0")]
/**
* The width of the item editor, in pixels, relative to the size of the
* item renderer. This property can be used to make the editor overlap
* the item renderer by a few pixels to compensate for a border around the
* editor.
* <p>Changing these values while the editor is displayed
* will have no effect on the current editor, but will affect the next
* item renderer that opens an editor.</p>
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorWidthOffset:Number = 0;
//----------------------------------
// editorXOffset
//----------------------------------
[Inspectable(defaultValue="0")]
/**
* The x location of the upper-left corner of the item editor,
* in pixels, relative to the upper-left corner of the item.
* This property can be used to make the editor overlap
* the item renderer by a few pixels to compensate for a border around the
* editor.
* <p>Changing these values while the editor is displayed
* will have no effect on the current editor, but will affect the next
* item renderer that opens an editor.</p>
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorXOffset:Number = 0;
//----------------------------------
// editorYOffset
//----------------------------------
[Inspectable(defaultValue="0")]
/**
* The y location of the upper-left corner of the item editor,
* in pixels, relative to the upper-left corner of the item.
* This property can be used to make the editor overlap
* the item renderer by a few pixels to compensate for a border around the
* editor.
* <p>Changing these values while the editor is displayed
* will have no effect on the current editor, but will affect the next
* item renderer that opens an editor.</p>
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorYOffset:Number = 0;
//----------------------------------
// editorUsesEnterKey
//----------------------------------
[Inspectable(defaultValue="false")]
/**
* A flag that indicates whether the item editor uses Enter key.
* If this property is set to <code>true</code>, the item editor uses the Enter key and the
* List will not look for the Enter key and move the editor in
* response.
* <p>Changing this value while the editor is displayed
* will have no effect on the current editor, but will affect the next
* item renderer that opens an editor.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var editorUsesEnterKey:Boolean = false;
//----------------------------------
// enabled
//----------------------------------
[Inspectable(category="General", enumeration="true,false", defaultValue="true")]
/**
* @private
*/
override public function set enabled(value:Boolean):void
{
super.enabled = value;
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
invalidateDisplayList();
}
//----------------------------------
// editedItemPosition
//----------------------------------
/**
* @private
*/
private var bEditedItemPositionChanged:Boolean = false;
/**
* @private
* undefined means we've processed it
* null means don't put up an editor
* {} is the coordinates for the editor
*/
private var _proposedEditedItemPosition:*;
/**
* @private
* the last editedItemPosition. We restore editing
* to this point if we get focus from the TAB key
*/
private var lastEditedItemPosition:*;
/**
* @private
*/
private var _editedItemPosition:Object;
[Bindable("itemFocusIn")]
/**
* The column and row index of the item renderer for the
* data provider item being edited, if any.
*
* <p>This Object has two fields, <code>columnIndex</code> and
* <code>rowIndex</code>,
* the zero-based column and item indexes of the item.
* For a List control, the <code>columnIndex</code> property is always 0;
* for example: <code>{columnIndex:0, rowIndex:3}</code>.</p>
*
* <p>Setting this property scrolls the item into view and
* dispatches the <code>itemEditBegin</code> event to
* open an item editor on the specified item,
* </p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get editedItemPosition():Object
{
if (_editedItemPosition)
return {rowIndex: _editedItemPosition.rowIndex,
columnIndex: 0};
else
return _editedItemPosition;
}
/**
* @private
*/
public function set editedItemPosition(value:Object):void
{
var newValue:Object = {rowIndex: value.rowIndex,
columnIndex: 0};
setEditedItemPosition(newValue);
}
//----------------------------------
// enableIME
//----------------------------------
/**
* A flag that indicates whether the IME should
* be enabled when the component receives focus.
*
* If there is an editor, it will set enableIME
* accordingly.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get enableIME():Boolean
{
return false;
}
//----------------------------------
// 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.
* Rows above this one remain fixed in view.
*
* @default 0
* @private
*/
public function get lockedRowCount():int
{
return _lockedRowCount;
}
/**
* @private
*/
public function set lockedRowCount(value:int):void
{
_lockedRowCount = value;
invalidateDisplayList();
}
//----------------------------------
// rendererIsEditor
//----------------------------------
[Inspectable(category="Data", defaultValue="false")]
/**
* Specifies whether the item renderer is also an item
* editor. If this property is <code>true</code>, Flex
* ignores the <code>itemEditor</code> property.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var rendererIsEditor:Boolean = false;
//----------------------------------
// imeMode
//----------------------------------
/**
* @private
* Storage for the imeMode property.
*/
private var _imeMode:String;
/**
* Specifies the IME (input method editor) mode.
* The IME enables users to enter text in Chinese, Japanese, and Korean.
* Flex sets the specified IME mode when the control gets focus,
* and sets it back to the previous value when the control loses focus.
*
* <p>The flash.system.IMEConversionMode class defines constants for the
* valid values for this property.
* You can also specify <code>null</code> to specify no IME.</p>
*
* @see flash.system.IME
* @see flash.system.IMEConversionMode
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get imeMode():String
{
return _imeMode;
}
/**
* @private
*/
public function set imeMode(value:String):void
{
_imeMode = value;
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
[Inspectable(category="Data", defaultValue="undefined")]
/**
* @private
*/
override public function set dataProvider(value:Object):void
{
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
super.dataProvider = value;
}
/**
* @private
* Called by the initialize() method of UIComponent
* to hook in the accessibility code.
*/
override protected function initializeAccessibility():void
{
if (createAccessibilityImplementation != null)
createAccessibilityImplementation(this);
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (itemsNeedMeasurement)
{
itemsNeedMeasurement = false;
if (isNaN(explicitRowHeight))
{
if (iterator)
{
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
// trace("calculate height " + index + " " + count);
var item:IListItemRenderer = getMeasuringRenderer(iterator.current);
var ww:Number = 200;
if (listContent.width)
ww = listContent.width;
item.explicitWidth = ww;
setupRendererFromData(item, iterator.current);
var rh:int = item.getExplicitOrMeasuredHeight() + paddingTop + paddingBottom;
// unless specified otherwise, rowheight defaults to 20
setRowHeight(Math.max(rh, 20));
}
else
setRowHeight(20);
}
if (isNaN(explicitColumnWidth))
setColumnWidth(measureWidthOfItems(0, (explicitRowCount < 1) ? defaultRowCount : explicitRowCount));
}
}
/**
* @private
* The measuredWidth is widest of the items in the first set of rows it will display.
* If the rowCount property has been set it will measure that many rows, otherwise
* it will measure 7 rows and use the widest.
* The measuredHeight is based on the height of one line of text or 20 pixels, whichever
* is greater. Thus the measuredHeight will depend on fonts related styles like fontSize.
* Then that height is multiplied by 7 or rowCount if it has been specified.
*
*/
override protected function measure():void
{
super.measure();
var o:EdgeMetrics = viewMetrics;
measuredMinWidth = DEFAULT_MEASURED_MIN_WIDTH; // room for scrollbar and room for some of the renderer
// if we've been layed out once (initialized)
// then if no explicit rowCount or rowHeight, just use the current
// height. Otherwise, if the data changes and has variable rowheight
// the height can shift and cause re-layout.
if (initialized && variableRowHeight && explicitRowCount < 1 && isNaN(explicitRowHeight))
measuredHeight = height;
}
/**
* @private
*/
override protected function configureScrollBars():void
{
var rowCount:int = listItems.length;
if (rowCount == 0) return;
// ignore nonvisible rows off the top
var yy:Number;
var i:int;
var n:int = listItems.length;
// if there is more than one row and it is a partial row we dont count it
while (rowCount > 1 && rowInfo[n - 1].y + rowInfo[n-1].height > listContent.height - listContent.bottomOffset)
{
rowCount--;
n--;
}
// offset, when added to rowCount, is the index of the dataProvider
// item for that row. IOW, row 10 in listItems is showing dataProvider
// item 10 + verticalScrollPosition - lockedRowCount - 1;
var offset:int = verticalScrollPosition - lockedRowCount - 1;
// don't count filler rows at the bottom either.
var fillerRows:int = 0;
// don't count filler rows at the bottom either.
while (rowCount && listItems[rowCount - 1].length == 0)
{
if (collection && rowCount + offset >= collection.length)
{
rowCount--;
++fillerRows;
}
else
break;
}
// we have to scroll up. We can't have filler rows unless the scrollPosition is 0
// We don't do the adjustment if a data effect is running, because that prevents
// a smooth effect. Effectively, we pin the scroll position while the effect is
// running.
if (verticalScrollPosition > 0 && fillerRows > 0 && !runningDataEffect)
{
var bookmark:CursorBookmark = iterator.bookmark;
var rowIndex:int = bookmark.getViewIndex();
if (verticalScrollPosition != rowIndex - lockedRowCount)
{
// we got totally out of sync, probably because a filter
// removed or added rows
super.verticalScrollPosition = Math.max(rowIndex - lockedRowCount, 0);
}
if (adjustVerticalScrollPositionDownward(Math.max(rowCount, 1)))
return;
}
if (listContent.topOffset)
{
yy = Math.abs(listContent.topOffset);
i = 0;
while (rowInfo[i].y + rowInfo[i].height <= yy)
{
rowCount--;
i++;
if (i == rowCount)
break;
}
}
var colCount:int = listItems[0].length;
var oldHorizontalScrollBar:Object = horizontalScrollBar;
var oldVerticalScrollBar:Object = verticalScrollBar;
var roundedWidth:int = Math.round(unscaledWidth);
var length:int = collection ? collection.length - lockedRowCount: 0;
var numRows:int = rowCount - lockedRowCount;
setScrollBarProperties((isNaN(_maxHorizontalScrollPosition)) ?
Math.round(listContent.width) :
Math.round(_maxHorizontalScrollPosition + roundedWidth),
roundedWidth, length, numRows);
maxVerticalScrollPosition = Math.max(length - numRows, 0);
}
/**
* @private
* Makes verticalScrollPosition smaller until it is 0 or there
* are no empty rows. This is needed if we're scrolled to the
* bottom and something is deleted or the rows resize so more
* rows can be shown.
*/
private function adjustVerticalScrollPositionDownward(rowCount:int):Boolean
{
var bookmark:CursorBookmark = iterator.bookmark;
// add up how much space we're currently taking with valid items
var h:Number = 0;
var ch:Number = 0;
var n:int;
var j:int;
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
h = rowInfo[rowCount - 1].y + rowInfo[rowCount - 1].height;
h = listContent.heightExcludingOffsets - listContent.topOffset - h;
// back up one
var numRows:int = 0;
try
{
if (iterator.afterLast)
iterator.seek(CursorBookmark.LAST, 0)
else
var more:Boolean = iterator.movePrevious();
}
catch(e:ItemPendingError)
{
more = false;
}
if (!more)
{
// reset to 0;
super.verticalScrollPosition = 0;
try
{
iterator.seek(CursorBookmark.FIRST, 0);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, 0);
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
iteratorValid = false;
invalidateList();
return true;
}
updateList();
return true;
}
var item:IListItemRenderer = getMeasuringRenderer(iterator.current);
item.explicitWidth = listContent.width - paddingLeft - paddingRight;
// now work backwards to see how many more rows we need to create
while (h > 0 && more)
{
var data:Object;
if (more)
{
data = iterator.current;
setupRendererFromData(item, data);
ch = variableRowHeight ?
item.getExplicitOrMeasuredHeight() + paddingBottom + paddingTop :
rowHeight;
}
h -= ch;
try
{
more = iterator.movePrevious();
numRows++;
}
catch(e:ItemPendingError)
{
// if we run out of data, assume all remaining rows are the size of the previous row
more = false;
}
}
// if we overrun, go back one.
if (h < 0)
numRows--;
iterator.seek(bookmark, 0);
verticalScrollPosition = Math.max(0, verticalScrollPosition - numRows);
// make sure we get through configureScrollBars w/o coming in here.
if (numRows > 0 && !variableRowHeight)
configureScrollBars();
return numRows > 0;
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
setRowCount(listItems.length);
// if this code doesn't execute because editing is prevented while
// an effect is running, the editor should appear after the effect
// finishes
if (bEditedItemPositionChanged && !editingTemporarilyPrevented(_proposedEditedItemPosition))
{
bEditedItemPositionChanged = false;
commitEditedItemPosition(_proposedEditedItemPosition);
_proposedEditedItemPosition = undefined;
}
drawRowBackgrounds();
}
/**
* @private
*/
override protected function adjustListContent(unscaledWidth:Number = -1,
unscaledHeight:Number = -1):void
{
// Can't currently call this without valid parameters...
var lcx:Number = viewMetrics.left + Math.max(listContent.leftOffset, 0);
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;
var scrollableWidth:Number = unscaledWidth + ww;
// if they want the scrollbar, we don't know how much to scroll, without rendering
// everything (on and/or off-screen). Since this could be expensive, we use a heuristic:
// if maxHorizontalScrollPosition is specified, we let them scroll that much. If it's not
// specified, we double the amount of viewable space and let them scroll that much. This is
// just a heuristic and may not work perfectly for really long content or short content.
if (horizontalScrollPolicy == ScrollPolicy.ON ||
(horizontalScrollPolicy == ScrollPolicy.AUTO && !isNaN(_maxHorizontalScrollPosition)))
{
if (isNaN(_maxHorizontalScrollPosition))
scrollableWidth *= 2;
else
scrollableWidth += _maxHorizontalScrollPosition;
}
listContent.setActualSize(scrollableWidth, unscaledHeight + hh);
}
/**
* @private
*/
override protected function drawRowBackgrounds():void
{
var rowBGs:Sprite = Sprite(listContent.getChildByName("rowBGs"));
if (!rowBGs)
{
rowBGs = new FlexSprite();
rowBGs.mouseEnabled = false;
rowBGs.name = "rowBGs";
listContent.addChildAt(rowBGs, 0)
}
var colors:Array;
var colorsStyle:Object = getStyle("alternatingItemColors");
if (colorsStyle)
colors = (colorsStyle is Array) ? (colorsStyle as Array) : [colorsStyle];
if (!colors || colors.length == 0)
{
while (rowBGs.numChildren > n)
{
rowBGs.removeChildAt(rowBGs.numChildren - 1);
}
return;
}
styleManager.getColorNames(colors);
var curRow:int = 0;
var actualRow:int = verticalScrollPosition;
var i:int = 0;
var n:int = listItems.length;
while (curRow < n)
{
drawRowBackground(rowBGs, i++, rowInfo[curRow].y, rowInfo[curRow].height, colors[actualRow % colors.length], actualRow);
curRow++;
actualRow++;
}
while (rowBGs.numChildren > n)
{
rowBGs.removeChildAt(rowBGs.numChildren - 1);
}
}
/**
* Draws a row background
* at the position and height specified. This creates a Shape as a
* child of the input Sprite and fills it with the appropriate color.
* This method also uses the <code>backgroundAlpha</code> style property
* setting to determine the transparency of the background color.
*
* @param s A Sprite that will contain a display object
* that contains the graphics for that row.
*
* @param rowIndex The row's index in the set of displayed rows. The
* header does not count; the top most visible row has a row index of 0.
* This is used to keep track of the objects used for drawing
* backgrounds so that a particular row can reuse the same display object
* even though the index of the item that the row is rendering has changed.
*
* @param y The suggested y position for the background.
*
* @param height The suggested height for the indicator.
*
* @param color The suggested color for the indicator.
*
* @param dataIndex The index of the item for that row in the
* data provider. For example, this can be used to color the 10th item differently.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawRowBackground(s:Sprite, rowIndex:int,
y:Number, height:Number, color:uint, dataIndex:int):void
{
// trace("drawRowBackground " + rowIndex + " " + color);
var bg:Shape;
if (rowIndex < s.numChildren)
{
bg = Shape(s.getChildAt(rowIndex));
}
else
{
bg = new FlexShape();
bg.name = "rowBackground";
s.addChild(bg);
}
// Height is usually as tall is the items in the row,
// but not if it would extend below the bottom of listContent.
var minHeight:Number = Math.min( rowInfo[rowIndex].height,
listContent.height - rowInfo[rowIndex].y);
bg.y = rowInfo[rowIndex].y;
var g:Graphics = bg.graphics;
g.clear();
g.beginFill(color, getStyle("backgroundAlpha"));
g.drawRect(0, 0, listContent.width, minHeight);
g.endFill();
}
/**
* @private
*/
override protected function makeRowsAndColumns(left:Number, top:Number,
right:Number, bottom:Number,
firstCol:int, firstRow:int,
byCount:Boolean = false, rowsNeeded:uint = 0):Point
{
// trace(this, "makeRowsAndColumns " + left + " " + top + " " + right + " " + bottom + " " + firstCol + " " + firstRow);
listContent.allowItemSizeChangeNotification = false;
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var xx:Number = left + paddingLeft - horizontalScrollPosition;
var ww:Number = right - paddingLeft - paddingRight;
var yy:Number;
var hh:Number;
var bSelected:Boolean = false;
var bHighlight:Boolean = false;
var bCaret:Boolean = false;
var i:int;
var j:int;
var colNum:int = 0;
var rowNum:int = lockedRowCount;
var rowsMade:int = 0;
var item:IListItemRenderer;
var oldItem:IListItemRenderer;
var rowData:BaseListData;
var data:Object;
var wrappedData:Object;
var uid:String;
var more:Boolean = true;
var valid:Boolean = true;
var rh:Number;
yy = top;
rowNum = firstRow;
more = (iterator != null && !iterator.afterLast && iteratorValid);
while ((!byCount && yy < bottom) || (byCount && rowsNeeded > 0))
{
if (byCount)
rowsNeeded--;
valid = more;
wrappedData = more ? iterator.current : null;
data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
uid = null;
if (!listItems[rowNum])
listItems[rowNum] = [];
if (valid)
{
item = listItems[rowNum][colNum];
uid = itemToUID(wrappedData);
// If we have an item at the current location, we want to
// re use it if the data is "the same", but the test
// is a little complex.
// If we're running a data effect, and the renderer is already
// presenting wrapped data, make sure the wrappers are the same.
// If running a data effect, but not presenting wrapped data
// (meaning the data effect was just initiated), or if
// not running data effect, just check that the data is the same.
if (!item ||
((runningDataEffect && dataItemWrappersByRenderer[item])
? (dataItemWrappersByRenderer[item] != wrappedData)
: (item.data != data)))
{
// if we're allowed to re-use existing renderers
if (allowRendererStealingDuringLayout)
{
// first try to steal an item renderer
item = visibleData[uid];
// if we can't steal a renderer with the same wrapper, try to
// steal one with the same actual data (if the data is wrapped).
// This is to re-use renderers when we start running a data effect.
if (!item && (wrappedData != data))
item = visibleData[itemToUID(data)];
}
if (item) // if we've stolen a renderer from somewhere else...
{
// update data structures so we're not pointing to it twice
var ld:BaseListData = BaseListData(rowMap[item.name]);
// don't steal a previous one...only a later one
if (ld && (ld.rowIndex > rowNum))
listItems[ld.rowIndex] = [];
else
item = null;
}
if (!item)
item = getReservedOrFreeItemRenderer(wrappedData);
if (!item)
{
item = createItemRenderer(data);
item.owner = this;
item.styleName = listContent;
// trace("created item " + item);
listContent.addChild(DisplayObject(item));
}
oldItem = listItems[rowNum][colNum];
if (oldItem)
addToFreeItemRenderers(oldItem);
listItems[rowNum][colNum] = item;
}
rowData = makeListData(data, uid, rowNum);
rowMap[item.name] = rowData;
if (item is IDropInListItemRenderer)
{
if (data != null)
IDropInListItemRenderer(item).listData = rowData;
else
IDropInListItemRenderer(item).listData = null;
}
item.data = data;
item.enabled = enabled;
item.visible = true;
if (uid != null)
visibleData[uid] = item;
if (wrappedData != data)
dataItemWrappersByRenderer[item] = wrappedData;
item.explicitWidth = ww;
if ((item is IInvalidating)
&& (wordWrapChanged || variableRowHeight))
IInvalidating(item).invalidateSize();
UIComponentGlobals.layoutManager.validateClient(item, true);
hh = Math.ceil(variableRowHeight ?
item.getExplicitOrMeasuredHeight() +
cachedPaddingTop + cachedPaddingBottom :
rowHeight);
rh = item.getExplicitOrMeasuredHeight();
item.setActualSize(ww, variableRowHeight ? rh : rowHeight - cachedPaddingTop - cachedPaddingBottom);
item.move(xx, yy + cachedPaddingTop);
}
else
{
// trace("not valid");
// if we've run out of data, we dont make renderers
// and we inherit the previous row's height or rowHeight
// if it is the first row.
hh = rowNum > 0 ? rowInfo[rowNum - 1].height : rowHeight;
if (hh == 0)
hh = rowHeight;
oldItem = listItems[rowNum][colNum];
if (oldItem)
{
addToFreeItemRenderers(oldItem);
listItems[rowNum].splice(colNum, 1);
}
}
bSelected = selectedData[uid] != null;
if (wrappedData != data)
{
bSelected = bSelected || selectedData[itemToUID(data)];
bSelected = bSelected && (!getRendererSemanticValue(item,ModifiedCollectionView.REPLACEMENT))
&& (!getRendererSemanticValue(item,ModifiedCollectionView.ADDED));
}
bHighlight = highlightUID == uid;
bCaret = caretUID == uid;
rowInfo[rowNum] = new ListRowInfo(yy, hh, uid, data);
if (valid)
drawItem(item, bSelected, bHighlight, bCaret);
yy += hh;
rowNum++;
rowsMade++;
if (iterator && more)
{
try
{
more = iterator.moveNext();
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
more = false;
iteratorValid = false;
}
}
}
// byCount means we're making rows and wont get all the way to the bottom
// so we skip this cleanup pass
if (!byCount)
{
// delete extra rows
while (rowNum < listItems.length)
{
var rr:Array = listItems.pop();
rowInfo.pop();
while (rr.length)
{
item = rr.pop();
addToFreeItemRenderers(item);
}
}
}
if (itemEditorInstance)
{
listContent.setChildIndex(DisplayObject(itemEditorInstance),
listContent.numChildren - 1);
item = listItems[actualRowIndex][actualColIndex];
var listRowInfo:ListRowInfo = rowInfo[actualRowIndex];
if (item && !rendererIsEditor)
{
var dx:Number = editorXOffset;
var dy:Number = editorYOffset;
var dw:Number = editorWidthOffset;
var dh:Number = editorHeightOffset;
layoutEditor(item.x + dx, listRowInfo.y + dy,
Math.min(item.width + dw, listContent.width - listContent.x - itemEditorInstance.x),
Math.min(listRowInfo.height + dh, listContent.height - listContent.y - itemEditorInstance.y));
}
}
listContent.allowItemSizeChangeNotification = variableRowHeight;
return new Point(colNum, rowsMade);
}
/**
* Positions the item editor instance at the suggested position
* with the suggested dimensions. The Tree control overrides this
* method and adjusts the position to compensate for indentation
* of the renderer.
*
* @param x The suggested x position for the indicator.
* @param y The suggested y position for the indicator.
* @param w The suggested width for the indicator.
* @param h The suggested height for the indicator.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function layoutEditor(x:int, y:int, w:int, h:int):void
{
itemEditorInstance.move(x, y);
itemEditorInstance.setActualSize(w, h);
}
/**
* @private
*/
override protected function scrollHandler(event:Event):void
{
// TextField.scroll bubbles so you might see it here
if (event is ScrollEvent)
{
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
if (!liveScrolling &&
ScrollEvent(event).detail == ScrollEventDetail.THUMB_TRACK)
{
return;
}
var scrollBar:ScrollBar = ScrollBar(event.target);
var pos:Number = scrollBar.scrollPosition;
var delta:int;
var o:EdgeMetrics;
removeClipMask();
if (scrollBar == verticalScrollBar)
{
delta = pos - verticalScrollPosition;
super.scrollHandler(event);
if (Math.abs(delta) >= listItems.length - lockedRowCount || !iteratorValid)
{
try
{
if (!iteratorValid)
iterator.seek(CursorBookmark.FIRST, pos);
else
iterator.seek(CursorBookmark.CURRENT, delta);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, pos);
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 bookmark:CursorBookmark = iterator.bookmark;
//if we scrolled more than the number of scrollable rows
clearIndicators();
clearVisibleData();
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
iterator.seek(bookmark, 0);
}
else if (delta != 0)
scrollVertically(pos, Math.abs(delta), Boolean(delta > 0));
// 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();
}
else
{
delta = pos - _horizontalScrollPosition;
super.scrollHandler(event);
scrollHorizontally(pos, Math.abs(delta), Boolean(delta > 0));
}
// if needed, add a clip mask to the items in the last row of the list
addClipMask(false);
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
* List scrolls horizontally by pixels.
*/
override protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void
{
var n:int = listItems.length;
var paddingLeft:Number = getStyle("paddingLeft");
for (var i:int = 0; i < n; i++)
{
if (listItems[i].length)
listItems[i][0].x = -pos + paddingLeft;
}
}
/**
* Creates a new ListData instance and populates the fields based on
* the input data provider item.
*
* @param data The data provider item used to populate the ListData.
* @param uid The UID for the item.
* @param rowNum The index of the item in the data provider.
*
* @return A newly constructed ListData object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function makeListData(data:Object, uid:String,
rowNum:int):BaseListData
{
return new ListData(itemToLabel(data), itemToIcon(data), labelField, uid, this, rowNum);
}
/**
* @private
*/
mx_internal function setupRendererFromData(item:IListItemRenderer, wrappedData:Object):void
{
var data:Object = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
if (item is IDropInListItemRenderer)
{
if (data != null)
IDropInListItemRenderer(item).listData = makeListData(data, itemToUID(wrappedData), 0);
else
IDropInListItemRenderer(item).listData = null;
}
item.data = data;
if (item is IInvalidating)
IInvalidating(item).invalidateSize();
UIComponentGlobals.layoutManager.validateClient(item, true);
}
/**
* @private
*/
override public function measureWidthOfItems(index:int = -1, count:int = 0):Number
{
if (count == 0)
count = (collection) ? collection.length : 0;
// if empty collection, don't measure anything
if (collection && collection.length == 0)
count = 0;
var item:IListItemRenderer
var w:Number = 0;
var bookmark:CursorBookmark = (iterator) ? iterator.bookmark : null;
if (index != -1 && iterator)
{
try
{
iterator.seek(CursorBookmark.FIRST, index);
}
catch (e:ItemPendingError)
{
// even the first item isn't paged in
return 0;
}
}
var rw:Number;
var more:Boolean = iterator != null;
for (var i:int = 0; i < count; i++)
{
var data:Object;
if (more)
{
data = iterator.current;
var factory:IFactory = getItemRendererFactory(data);
item = measuringObjects[factory];
if (!item)
{
item = getMeasuringRenderer(data);
}
item.explicitWidth = NaN; // gets set in measureHeightOfItems
setupRendererFromData(item, data);
rw = item.measuredWidth;
w = Math.max(w, rw);
}
if (more)
{
try
{
more = iterator.moveNext();
}
catch(e:ItemPendingError)
{
// if we run out of data, assume all remaining rows are the size of the previous row
more = false;
}
}
}
if (iterator)
iterator.seek(bookmark, 0);
if (w == 0)
{
if (explicitWidth)
return explicitWidth;
else
return DEFAULT_MEASURED_WIDTH;
}
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
w += paddingLeft + paddingRight
return w;
}
/**
* @private
*/
override public function measureHeightOfItems(index:int = -1, count:int = 0):Number
{
if (count == 0)
count = (collection) ? collection.length : 0;
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var ww:Number = 200;
if (listContent.width)
ww = listContent.width;
var h:Number = 0;
var bookmark:CursorBookmark = (iterator) ? iterator.bookmark : null;
if (index != -1 && iterator)
iterator.seek(CursorBookmark.FIRST, index);
var rh:Number = rowHeight;
var more:Boolean = iterator != null;
for (var i:int = 0; i < count; i++)
{
var data:Object;
if (more)
{
rh = rowHeight;
data = iterator.current;
// trace("calculate height " + index + " " + count);
var item:IListItemRenderer = getMeasuringRenderer(data);
item.explicitWidth = ww;
setupRendererFromData(item, data);
if (variableRowHeight)
rh = item.getExplicitOrMeasuredHeight() + paddingTop + paddingBottom;
}
h += rh;
if (more)
{
// if we run out of data, assume all remaining rows are the size of the previous row
more = false;
try
{
more = iterator.moveNext();
}
catch(e:ItemPendingError)
{
}
}
}
if (iterator)
iterator.seek(bookmark, 0);
return h;
}
/**
* @private
*/
override protected function mouseEventToItemRenderer(event:MouseEvent):IListItemRenderer
{
var r:IListItemRenderer = super.mouseEventToItemRenderer(event);
return r == itemEditorInstance ? null : r;
}
[Inspectable(category="Data")]
/**
* @private
*/
mx_internal function getMeasuringRenderer(data:Object):IListItemRenderer
{
var item:IListItemRenderer;
if (!measuringObjects)
measuringObjects = new Dictionary(true);
var factory:IFactory = getItemRendererFactory(data);
item = measuringObjects[factory];
if (!item)
{
item = createItemRenderer(data);
item.owner = this;
item.name = "hiddenItem";
item.visible = false;
item.styleName = listContent;
listContent.addChild(DisplayObject(item));
measuringObjects[factory] = item;
}
return item;
}
/**
* @private
*/
mx_internal function purgeMeasuringRenderers():void
{
var item:IListItemRenderer;
for each (item in measuringObjects)
if (item.parent)
item.parent.removeChild(DisplayObject(item));
if (!measuringObjects)
measuringObjects = new Dictionary(true);
}
/**
* @private
*/
override public function set itemRenderer(value:IFactory):void
{
super.itemRenderer = value;
purgeMeasuringRenderers();
}
/**
* Get the appropriate renderer, using the default renderer if none is specified.
*
* @param data The object from which the item renderer is created.
*
* @return The renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function createItemRenderer(data:Object):IListItemRenderer
{
var factory:IFactory;
// get the factory for the data
factory = getItemRendererFactory(data);
if (!factory)
{
if (data == null)
factory = nullItemRenderer;
if (!factory)
factory = itemRenderer;
}
var renderer:IListItemRenderer;
// if it is the default column factory, see if
// the freeItemRenderers table has a free one
if (factory == itemRenderer)
{
if (freeItemRenderers && freeItemRenderers.length)
{
renderer = freeItemRenderers.pop();
delete freeItemRenderersByFactory[factory][renderer];
}
}
else if (freeItemRenderersByFactory)
{
// other re-usable renderers are in the FactoryMap
var d:Dictionary = freeItemRenderersByFactory[factory];
if (d)
{
for (var p:* in d)
{
renderer = IListItemRenderer(p);
delete d[p];
break;
}
}
}
if (!renderer)
{
renderer = factory.newInstance();
renderer.styleName = this;
factoryMap[renderer] = factory;
}
renderer.owner = this;
return renderer;
}
/**
* @private
*
* Determines whether editing is prevented for a specific location
*/
private function editingTemporarilyPrevented(coord:Object):Boolean
{
// This code prevents possible race conditions when trying to
// edit an item that is either being removed or replaced when
// an effect is running. (E.g., after editing the last item of
// a list, we can't have the editor pick up the old value of
// the data, which might appear to persist while the effect is
// running)
if (runningDataEffect && coord)
{
var rowIndex:int = coord.rowIndex - verticalScrollPosition + offscreenExtraRowsTop;
if ((rowIndex < 0) || rowIndex >= listItems.length)
return false;
var item:IListItemRenderer = listItems[rowIndex][0];
if (item && (getRendererSemanticValue(item,"replaced") ||
getRendererSemanticValue(item,"removed")))
return true;
}
return false;
}
/**
* @private
*/
private function setEditedItemPosition(coord:Object):void
{
bEditedItemPositionChanged = true;
_proposedEditedItemPosition = coord;
invalidateDisplayList();
}
/**
* @private
* focus an item in the grid - harder than it looks
*/
private function commitEditedItemPosition(coord:Object):void
{
if (!enabled || !editable)
return;
// just give focus back to the itemEditorInstance
if (itemEditorInstance && coord &&
itemEditorInstance is IFocusManagerComponent &&
_editedItemPosition.rowIndex == coord.rowIndex)
{
IFocusManagerComponent(itemEditorInstance).setFocus();
return;
}
// dispose of any existing editor, saving away its data first
if (itemEditorInstance)
{
var reason:String;
if (!coord)
reason = ListEventReason.OTHER;
else
reason = ListEventReason.NEW_ROW;
// trace("calling endEdit from commitEditedItemPosition", _editedItemPosition.rowIndex);
if (!endEdit(reason) && reason != ListEventReason.OTHER)
return;
}
// store the value
_editedItemPosition = coord;
// allow setting of undefined to dispose item editor
if (!coord || dontEdit)
return;
var rowIndex:int = coord.rowIndex;
var colIndex:int = coord.columnIndex;
// trace("setEditedItemPosition ", coord.rowIndex, selectedIndex);
if (selectedIndex != coord.rowIndex)
commitSelectedIndex(coord.rowIndex);
var actualLockedRows:int = lockedRowCount;
// determine last *visible* row
var lastRowIndex:int = verticalScrollPosition + listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom - 1;
var partialRow:int = (rowInfo[listItems.length - offscreenExtraRowsBottom - 1].y +
rowInfo[listItems.length - offscreenExtraRowsBottom - 1].height > listContent.height) ? 1 : 0;
// actual row/column is the offset into listItems
if (rowIndex > actualLockedRows)
{
// not a locked editable row make sure it is on screen
if (rowIndex < verticalScrollPosition + actualLockedRows)
verticalScrollPosition = rowIndex - actualLockedRows;
else
{
// variable row heights means that we can't know how far to scroll sometimes so we loop
// until we get it right
while (rowIndex > lastRowIndex ||
// we're the last row, and we're partially visible, but we're not
// the top scrollable row already
(rowIndex == lastRowIndex && rowIndex > verticalScrollPosition + actualLockedRows &&
partialRow))
{
if (verticalScrollPosition == maxVerticalScrollPosition)
break;
verticalScrollPosition = Math.min(verticalScrollPosition + (rowIndex > lastRowIndex ? rowIndex - lastRowIndex : partialRow),
maxVerticalScrollPosition);
lastRowIndex = verticalScrollPosition + listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom - 1;
partialRow = (rowInfo[listItems.length - offscreenExtraRowsBottom - 1].y +
rowInfo[listItems.length - offscreenExtraRowsBottom - 1].height > listContent.height) ? 1 : 0;
}
}
actualRowIndex = rowIndex - verticalScrollPosition;
}
else
{
if (rowIndex == actualLockedRows)
verticalScrollPosition = 0;
actualRowIndex = rowIndex;
}
var bm:EdgeMetrics = borderMetrics;
actualColIndex = colIndex;
// get the actual references for the column, row, and item
var item:IListItemRenderer = listItems[actualRowIndex][actualColIndex];
if (!item)
{
// assume that editing was cancelled
commitEditedItemPosition(null);
return;
}
if (!isItemEditable(item.data))
{
// assume that editing was cancelled
commitEditedItemPosition(null);
return;
}
var event:ListEvent =
new ListEvent(ListEvent.ITEM_EDIT_BEGIN, false, true);
// ITEM_EDIT events are cancelable
event.rowIndex = _editedItemPosition.rowIndex;
event.itemRenderer = item;
dispatchEvent(event);
lastEditedItemPosition = _editedItemPosition;
// user may be trying to change the edited item
if (bEditedItemPositionChanged)
{
bEditedItemPositionChanged = false;
commitEditedItemPosition(_proposedEditedItemPosition);
_proposedEditedItemPosition = undefined;
}
if (!itemEditorInstance)
{
// assume that editing was cancelled
commitEditedItemPosition(null);
}
}
/**
* Creates the item editor for the item renderer at the
* <code>editedItemPosition</code> using the editor
* specified by the <code>itemEditor</code> property.
*
* <p>This method sets the editor instance as the
* <code>itemEditorInstance</code> property.</p>
*
* <p>You can call this method only from within the event listener
* for the <code>itemEditBegin</code> event. To create an editor
* at other times, set the <code>editedItemPosition</code> property
* to generate the <code>itemEditBegin</code> event.</p>
*
* @param colIndex The column index. Flex sets the value of this property to 0 for a List control.
*
* @param rowIndex The index in the data provider of the item to be
* edited.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createItemEditor(colIndex:int, rowIndex:int):void
{
// If the itemEditor wasn't set by the user, get the default one.
if (!itemEditor)
{
var textInputClass:Class = getStyle("textInputClass");
if (textInputClass)
itemEditor = new ClassFactory(textInputClass);
else
itemEditor = new ClassFactory(TextInput);
}
colIndex = 0;
if (rowIndex > lockedRowCount)
rowIndex -= verticalScrollPosition;
var item:IListItemRenderer = listItems[rowIndex][colIndex];
var rowData:ListRowInfo = rowInfo[rowIndex];
// rendererIsEditor is part of the IListItemRenderer interface. It allows the item itself to do the editing
if (!rendererIsEditor)
{
var dx:Number = 0;
var dy:Number = -2;
var dw:Number = 0;
var dh:Number = 4;
// if this isn't implemented, use an input control as editor
if (!itemEditorInstance)
{
dx = editorXOffset;
dy = editorYOffset;
dw = editorWidthOffset;
dh = editorHeightOffset;
itemEditorInstance = itemEditor.newInstance();
itemEditorInstance.owner = this;
itemEditorInstance.styleName = this;
listContent.addChild(DisplayObject(itemEditorInstance));
}
listContent.setChildIndex(DisplayObject(itemEditorInstance), listContent.numChildren - 1);
// give it the right size, look and placement
itemEditorInstance.visible = true;
layoutEditor(item.x + dx, rowData.y + dy,
Math.min(item.width + dw, listContent.width - listContent.x - itemEditorInstance.x),
Math.min(rowData.height + dh, listContent.height - listContent.y - itemEditorInstance.y));
DisplayObject(itemEditorInstance).addEventListener("focusOut", itemEditorFocusOutHandler);
}
else
{
// if the item is an itemEditorInstance, we'll use it
itemEditorInstance = item;
}
// listen for keyStrokes on the itemEditorInstance (which lets the grid supervise for ESC/ENTER)
DisplayObject(itemEditorInstance).addEventListener(KeyboardEvent.KEY_DOWN, editorKeyDownHandler);
// Disable framework default button support when editor is enabled.
if (focusManager)
focusManager.defaultButtonEnabled = false;
// we disappear on any mouse down outside the editor
// use weak reference
systemManager.getSandboxRoot().
addEventListener(MouseEvent.MOUSE_DOWN, editorMouseDownHandler, true, 0, true);
systemManager.getSandboxRoot().
addEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, editorMouseDownHandler, false, 0, true);
}
/**
* @private
* Determines the next item to navigate to by using the Tab key.
* If the item to be focused falls out of range (the end or beginning
* of the grid), moves the focus outside the grid.
*/
private function findNextItemRenderer(shiftKey:Boolean):Boolean
{
if (!lastEditedItemPosition)
return false;
// some other thing like a collection change has changed the
// position, so bail and wait for commit to reset the editor.
if (_proposedEditedItemPosition !== undefined)
return true;
_editedItemPosition = lastEditedItemPosition;
var rowIndex:int = _editedItemPosition.rowIndex;
var columnIndex:int = _editedItemPosition.columnIndex;
// modify direction with SHIFT (up or down)
var newIndex:int = _editedItemPosition.rowIndex +
(shiftKey ? -1 : 1);
// only move if we're within range
if (newIndex < collection.length && newIndex >= 0)
rowIndex = newIndex;
else
{
// if we've fallen off the rows, we need to leave the grid. get rid of the editor
setEditedItemPosition(null);
// set focus back to the grid so default handler will move it to the next component
losingFocus = true;
setFocus();
return false;
}
// send event to create the new one
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
// ITEM_EDIT events are cancelable
listEvent.rowIndex = rowIndex;
listEvent.columnIndex = columnIndex;
dispatchEvent(listEvent);
return true;
}
/**
* Closes an item editor that is currently open on an item.
* You typically call this method only from within the event listener
* for the <code>itemEditEnd</code> event, after
* you call the <code>preventDefault()</code> method to prevent
* the default event listener from executing.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function destroyItemEditor():void
{
// trace("destroyItemEditor");
if (itemEditorInstance)
{
DisplayObject(itemEditorInstance).removeEventListener(KeyboardEvent.KEY_DOWN, editorKeyDownHandler);
// Re-enable framework default button support.
if (focusManager)
focusManager.defaultButtonEnabled = true;
systemManager.getSandboxRoot().
removeEventListener(MouseEvent.MOUSE_DOWN, editorMouseDownHandler, true);
systemManager.getSandboxRoot().
removeEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, editorMouseDownHandler);
var event:ListEvent =
new ListEvent(ListEvent.ITEM_FOCUS_OUT);
event.rowIndex = _editedItemPosition.rowIndex;
event.itemRenderer = editedItemRenderer;
dispatchEvent(event);
if (!rendererIsEditor)
{
if (itemEditorInstance && itemEditorInstance is UIComponent)
UIComponent(itemEditorInstance).drawFocus(false);
// must call removeChild() so FocusManager.lastFocus becomes null
listContent.removeChild(DisplayObject(itemEditorInstance));
}
itemEditorInstance = null;
_editedItemPosition = null;
}
}
/**
* Stops the editing of an item in the data provider.
* When the user finished editing an item, the control calls this method.
* It dispatches the <code>itemEditEnd</code> event to start the process
* of copying the edited data from
* the <code>itemEditorInstance</code> to the data provider and hiding the
* <code>itemEditorInstance</code>.
*
* @param reason A constant defining the reason for the event
* (such as "CANCELLED", "NEW_ROW", or "OTHER").
* The value must be a member of the ListEventReason class.
*
* @return Returns <code>true</code> if <code>preventDefault()</code> is not called.
* Otherwise, <code>false</code>.
*
* @see mx.events.ListEventReason
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function endEdit(reason:String):Boolean
{
// this happens if the renderer is removed asynchronously ususally with FDS
if (!editedItemRenderer)
return true;
inEndEdit = true;
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_EDIT_END, false, true);
// ITEM_EDIT events are cancelable
listEvent.rowIndex = editedItemPosition.rowIndex;
listEvent.itemRenderer = editedItemRenderer;
listEvent.reason = reason;
dispatchEvent(listEvent);
// set a flag to not open another edit session if the item editor is still up
// this means somebody wants the old edit session to stay.
dontEdit = itemEditorInstance != null;
// trace("dontEdit", dontEdit);
if (!dontEdit && reason == ListEventReason.CANCELLED)
{
losingFocus = true;
setFocus();
}
inEndEdit = false;
return !(listEvent.isDefaultPrevented())
}
/**
* Determines if the item renderer for a data provider item
* is editable.
*
* @param data The data provider item
* @return <code>true</code> if the item is editable
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function isItemEditable(data:Object):Boolean
{
if (!editable)
return false;
if (data == null)
return false;
return true;
}
/**
* @private
*/
override protected function mouseDownHandler(event:MouseEvent):void
{
// trace(">>mouseDownHandler");
var r:IListItemRenderer;
var s:Sprite;
r = mouseEventToItemRenderer(event);
var isItemEditor:Boolean = itemRendererContains(itemEditorInstance, DisplayObject(event.target));
// If it isn't an item, or an external item editor do default behavior
if (!isItemEditor)
{
if (r && r.data)
{
var pos:Point = itemRendererToIndices(r);
var bEndedEdit:Boolean = true;
if (itemEditorInstance)
{
bEndedEdit = endEdit(ListEventReason.NEW_ROW);
}
// if we didn't end edit session, don't do default behavior (call super)
if (!bEndedEdit)
return;
}
else
{
// trace("end edit?");
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
}
super.mouseDownHandler(event);
}
// trace("<<mouseDownHandler");
}
/**
* @private
*/
override protected function mouseUpHandler(event:MouseEvent):void
{
var listEvent:ListEvent;
var r:IListItemRenderer;
var s:Sprite;
var n:int;
var i:int;
r = mouseEventToItemRenderer(event);
super.mouseUpHandler(event);
if (r && r.data && r != itemEditorInstance)
{
var pos:Point = itemRendererToIndices(r);
if (editable && !dontEdit)
{
listEvent = new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
// ITEM_EDIT events are cancelable
listEvent.rowIndex = pos.y;
listEvent.columnIndex = 0;
listEvent.itemRenderer = r;
dispatchEvent(listEvent);
//dispatchReplayableInteraction(listEvent);
}
}
}
/**
* @private
* when the grid gets focus, focus an item
*/
override protected function focusInHandler(event:FocusEvent):void
{
// trace(">>DGFocusIn ", selectedIndex);
if (event.target != this)
{
// trace("subcomponent got focus ignoring");
// trace("<<DGFocusIn ");
return;
}
if (losingFocus)
{
losingFocus = false;
// trace("losing focus via tab");
// trace("<<DGFocusIn ");
return;
}
super.focusInHandler(event);
if (editable && !isPressed) // don't do this if we're mouse focused
{
_editedItemPosition = lastEditedItemPosition;
var foundOne:Boolean = editedItemPosition != null;
// start somewhere
if (!_editedItemPosition)
{
_editedItemPosition = { rowIndex: 0, columnIndex: 0 };
foundOne = (listItems.length && listItems[0].length > 0);
}
if (foundOne)
{
// trace("setting focus", _editedItemPosition.columnIndex, _editedItemPosition.rowIndex);
setEditedItemPosition(_editedItemPosition);
}
// if (foundOne)
// callLater(setEditedItemPosition, [ _editedItemPosition ]);
}
if (editable)
{
addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
addEventListener(MouseEvent.MOUSE_DOWN, mouseFocusChangeHandler);
}
// trace("<<DGFocusIn ");
}
/**
* @private
* when the grid loses focus, close the editor
*/
override protected function focusOutHandler(event:FocusEvent):void
{
// trace(">>DGFocusOut " + itemEditorInstance + " " + event.relatedObject, event.target);
if (event.target == this)
{
super.focusOutHandler(event);
}
// we're done if item editor is losing focus back to grid. Usually happens
// when someone clicks out of the editor onto a new item.
if (event.relatedObject == this && itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
return;
// just leave if the cell renderer is losing focus to nothing while its editor exists.
// this happens when we make the cell renderer invisible as we put up the editor
// if the renderer can have focus.
if (event.relatedObject == null && itemRendererContains(editedItemRenderer, DisplayObject(event.target)))
return;
// just leave if item editor is losing focus to nothing. Usually happens
// when someone clicks out of the textfield
if (event.relatedObject == null && itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
return;
// however, if we're losing focus to anything other than the editor or the grid
// hide the editor;
if (itemEditorInstance && (!event.relatedObject || !itemRendererContains(itemEditorInstance, event.relatedObject)))
{
// trace("call edit item from focus out");
endEdit(ListEventReason.OTHER);
removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
removeEventListener(MouseEvent.MOUSE_DOWN, mouseFocusChangeHandler);
}
// trace("<<DGFocusOut " + itemEditorInstance + " " + event.relatedObject);
}
/**
* @private
*/
private function deactivateHandler(event:Event):void
{
// trace("List deactivating");
// if stage losing activation, set focus to DG so when we get it back
// we popup an editor again
if (itemEditorInstance)
{
endEdit(ListEventReason.OTHER);
losingFocus = true;
setFocus();
}
}
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
if (itemEditorInstance)
return;
super.keyDownHandler(event);
}
/**
* @private
*/
private function editorMouseDownHandler(event:Event):void
{
if (event is MouseEvent && itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
return;
endEdit(ListEventReason.OTHER);
// set focus back to the grid so grid logic will deal if focus doesn't
// end up somewhere else
losingFocus = true;
setFocus();
}
/**
* @private
*/
private function editorKeyDownHandler(event:KeyboardEvent):void
{
// ESC just kills the editor, no new data
if (event.keyCode == Keyboard.ESCAPE)
{
endEdit(ListEventReason.CANCELLED);
}
else if (event.ctrlKey && event.charCode == 46)
{ // Check for Ctrl-.
endEdit(ListEventReason.CANCELLED);
}
else if (event.charCode == Keyboard.ENTER && event.keyCode != 229)
{
// multiline editors can take the enter key.
if (editorUsesEnterKey)
return;
// Enter edits the item, moves down a row
// The 229 keyCode is for IME compatability. When entering an IME expression,
// the enter key is down, but the keyCode is 229 instead of the enter key code.
// Thanks to Yukari for this little trick...
if (endEdit(ListEventReason.NEW_ROW) && !dontEdit)
{
findNextEnterItemRenderer(event);
if (focusManager)
focusManager.defaultButtonEnabled = false;
}
}
}
/**
* @private
* find the next item down from the currently edited item, and focus it.
*/
private function findNextEnterItemRenderer(event:KeyboardEvent):void
{
// some other thing like a collection change has changed the
// position, so bail and wait for commit to reset the editor.
if (_proposedEditedItemPosition !== undefined)
return;
_editedItemPosition = lastEditedItemPosition;
var rowIndex:int = _editedItemPosition.rowIndex;
var columnIndex:int = _editedItemPosition.columnIndex;
// modify direction with SHIFT (up or down)
var newIndex:int = _editedItemPosition.rowIndex +
(event.shiftKey ? -1 : 1);
// only move if we're within range
if (newIndex < collection.length && newIndex >= 0)
rowIndex = newIndex;
// send event to create the new one
var listEvent:ListEvent =
new ListEvent(ListEvent.ITEM_EDIT_BEGINNING, false, true);
// ITEM_EDIT events are cancelable
listEvent.rowIndex = rowIndex;
listEvent.columnIndex = 0;
dispatchEvent(listEvent);
}
/**
* @private
* This gets called when the focus is changed by using the mouse.
*/
private function mouseFocusChangeHandler(event:MouseEvent):void
{
// trace("mouseFocus handled by " + this);
if (itemEditorInstance &&
!event.isDefaultPrevented() &&
itemRendererContains(itemEditorInstance, DisplayObject(event.target)))
{
event.preventDefault();
}
}
/**
* @private
* This gets called when the focus is changed by pressing the Tab key.
*/
private function keyFocusChangeHandler(event:FocusEvent):void
{
// trace("tabHandled by " + this);
if (event.keyCode == Keyboard.TAB &&
! event.isDefaultPrevented() &&
findNextItemRenderer(event.shiftKey))
{
event.preventDefault();
}
}
/**
* @private
* Hides the itemEditorInstance if it loses focus.
*/
private function itemEditorFocusOutHandler(event:FocusEvent):void
{
// trace("itemEditorFocusOut " + event.relatedObject);
if (event.relatedObject && contains(event.relatedObject))
return;
// ignore textfields losing focus on mousedowns
if (!event.relatedObject)
return;
// trace("endEdit from itemEditorFocusOut");
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
}
/**
* @private
*/
private function itemEditorItemEditBeginningHandler(event:ListEvent):void
{
// trace("itemEditorItemEditBeginningHandler");
if (!event.isDefaultPrevented())
setEditedItemPosition({columnIndex: event.columnIndex, rowIndex: event.rowIndex});
else if (!itemEditorInstance)
{
_editedItemPosition = null;
// return focus to the grid w/o selecting an item
editable = false;
setFocus();
editable = true;
}
}
/**
* @private
* create the editor for the item.
*/
private function itemEditorItemEditBeginHandler(event:ListEvent):void
{
// trace("listening for deactivate");
// weak reference to stage or systemManager
if (root) // we're on the display list
systemManager.addEventListener(Event.DEACTIVATE, deactivateHandler, false, 0, true);
if (!event.isDefaultPrevented() && listItems[actualRowIndex][actualColIndex].data != null)
{
createItemEditor(event.columnIndex, event.rowIndex);
// trace("beginEditHandler", event.rowIndex, editedItemRenderer.data);
if (editedItemRenderer is IDropInListItemRenderer && itemEditorInstance is IDropInListItemRenderer)
IDropInListItemRenderer(itemEditorInstance).listData = IDropInListItemRenderer(editedItemRenderer).listData;
// if rendererIsEditor, don't apply the data as the data may have already changed in some way.
// This can happen if clicking on a checkbox rendererIsEditor as the checkbox will try to change
// its value as we try to stuff in an old value here.
if (!rendererIsEditor)
itemEditorInstance.data = editedItemRenderer.data;
if (itemEditorInstance is IInvalidating)
IInvalidating(itemEditorInstance).validateNow();
if (itemEditorInstance is IIMESupport)
IIMESupport(itemEditorInstance).imeMode = imeMode;
var fm:IFocusManager = focusManager;
// trace("setting focus to item editor");
if (itemEditorInstance is IFocusManagerComponent)
fm.setFocus(IFocusManagerComponent(itemEditorInstance));
var itemFocusInEvent:ListEvent =
new ListEvent(ListEvent.ITEM_FOCUS_IN);
itemFocusInEvent.rowIndex = _editedItemPosition.rowIndex;
itemFocusInEvent.itemRenderer = itemEditorInstance;
dispatchEvent(itemFocusInEvent);
}
}
/**
* @private
* save off the data and get rid of the editor
*/
private function itemEditorItemEditEndHandler(event:ListEvent):void
{
if (!event.isDefaultPrevented())
{
var bChanged:Boolean = false;
var bFieldChanged:Boolean = false;
var newData:Object = itemEditorInstance[editorDataField];
var data:Object = event.itemRenderer.data;
// trace("itemEditEndHandler", event.rowIndex, data);
if (data is String)
{
if (!(newData is String))
newData = newData.toString();
}
else if (data is uint)
{
if (!(newData is uint))
newData = uint(newData);
}
else if (data is int)
{
if (!(newData is int))
newData = int(newData);
}
else if (data is Number)
{
if (!(newData is int))
newData = Number(newData);
}
else // assume some sort of object
{
bFieldChanged = true;
try
{
data[labelField] = newData;
if (!(data is IPropertyChangeNotifier))
{
// update the underlying collection if a data effect is running
if (actualCollection)
actualCollection.itemUpdated(data, labelField);
else
collection.itemUpdated(data, labelField);
}
}
catch(e:Error)
{
trace("attempt to write to", labelField, "failed. You may need a custom ITEM_EDIT_END handler");
}
}
if (!bFieldChanged)
{
if (data !== newData)
{
bChanged = true;
data = newData;
}
if (bChanged)
{
// if running a data effect try to update the underlying collection
var editCollection:IList = actualCollection ? actualCollection as IList : collection as IList;
// if editCollection is null here, underlying collection doesn't implement IList
if (editCollection)
IList(editCollection).setItemAt(data, event.rowIndex);
else
trace("attempt to update collection failed. You may need a custom ITEM_EDIT_END handler");
}
}
if (event.itemRenderer is IDropInListItemRenderer)
{
var listData:BaseListData = BaseListData(IDropInListItemRenderer(event.itemRenderer).listData);
listData.label = itemToLabel(data);
IDropInListItemRenderer(event.itemRenderer).listData = listData;
}
// re-key the entry for this item renderer in visibleData
delete visibleData[itemToUID(event.itemRenderer.data)];
event.itemRenderer.data = data;
visibleData[itemToUID(data)] = event.itemRenderer;
}
else
{
if (event.reason != ListEventReason.OTHER)
{
if (itemEditorInstance && _editedItemPosition)
{
// edit session is continued so restore focus and selection
if (selectedIndex != _editedItemPosition.rowIndex)
selectedIndex = _editedItemPosition.rowIndex;
var fm:IFocusManager = focusManager;
// trace("setting focus to item editor", selectedIndex);
if (itemEditorInstance is IFocusManagerComponent)
fm.setFocus(IFocusManagerComponent(itemEditorInstance));
}
}
}
if (event.reason == ListEventReason.OTHER || !event.isDefaultPrevented())
{
destroyItemEditor();
}
}
/**
* @private
*/
override protected function drawHighlightIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawHighlightIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
}
/**
* @private
*/
override protected function drawCaretIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawCaretIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
}
/**
* @private
*/
override protected function drawSelectionIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawSelectionIndicator(indicator, 0, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
}
/**
* @private
*/
override protected function mouseWheelHandler(event:MouseEvent):void
{
if (itemEditorInstance)
endEdit(ListEventReason.OTHER);
super.mouseWheelHandler(event);
}
/**
* @private
* Catches any events from the model. Optimized for editing one item.
* @param eventObj
*/
override protected function collectionChangeHandler(event:Event):void
{
if (event is CollectionEvent)
{
var ceEvent:CollectionEvent = CollectionEvent(event)
// if we get a remove while editing it may invalidate the edit position
if (ceEvent.kind == CollectionEventKind.REMOVE)
{
if (editedItemPosition)
{
//trace("editedItemPosition", editedItemPosition.rowIndex);
if (collection.length == 0)
{
if (itemEditorInstance)
endEdit(ListEventReason.CANCELLED);
setEditedItemPosition(null); // nothing left to edit
}
else if (ceEvent.location <= editedItemPosition.rowIndex)
{
if (inEndEdit)
_editedItemPosition = { columnIndex : editedItemPosition.columnIndex,
rowIndex : Math.max(0, editedItemPosition.rowIndex - ceEvent.items.length)};
else
setEditedItemPosition({ columnIndex : editedItemPosition.columnIndex,
rowIndex : Math.max(0, editedItemPosition.rowIndex - ceEvent.items.length)});
}
}
}
}
super.collectionChangeHandler(event);
}
/**
* @private
*/
mx_internal function callSetupRendererFromData(item:IListItemRenderer, data:Object):void
{
setupRendererFromData(item, data);
}
/**
* @private
*/
mx_internal function callMakeListData(data:Object, uid:String,
rowNum:int):BaseListData
{
return makeListData(data, uid, rowNum);
}
}
}