| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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><mx:List></code> tag inherits all the tag attributes |
| * of its superclass, and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <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>" |
| * /> |
| * </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); |
| } |
| |
| } |
| |
| } |