<commit_before>////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2008 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.components.supportClasses
{
import flash.events.Event;

import spark.events.RendererExistenceEvent;
import spark.components.SkinnableDataContainer;
import spark.components.IItemRendererOwner; 
import spark.components.IItemRenderer; 
import spark.layouts.supportClasses.LayoutBase;
import spark.utils.LabelUtil; 

import mx.collections.IList;
import mx.core.IVisualElement;
import mx.core.mx_internal;
import mx.core.UIComponent; 
import mx.events.FlexEvent;
import mx.events.IndexChangedEvent;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;

use namespace mx_internal;  //ListBase and List share selection properties that are mx_internal

/**
 *  Dispatched when the selection is going to change. 
 *  Calling the <code>preventDefault()</code> method
 *  on the event prevents the selection from changing.
 *
 *  @eventType mx.events.IndexChangedEvent.SELECTION_CHANGING
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="selectionChanging", type="mx.events.IndexChangedEvent")]

/**
 *  Dispatched after the selection has changed. 
 *
 *  @eventType mx.events.IndexChangedEvent.SELECTION_CHANGED
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="selectionChanged", type="mx.events.IndexChangedEvent")]

/**
 *  Dispatched after the focus has changed.  
 *
 *  @eventType mx.events.IndexChangedEvent.ITEM_FOCUS_CHANGED
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="itemFocusChanged", type="mx.events.IndexChangedEvent")]

/**
 *  The ListBase class is the base class for all components that support
 *  selection. 
 *
 *  @mxml <p>The <code>&lt;ListBase&gt;</code> tag inherits all of the tag 
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;ListBase
 *
 *    <strong>Properties</strong>
 *    dataProvider="null"
 *    labelField="null"
 *    labelFunction="null"
 *    requiresSelection="false"
 *    selectedIndex="-1"
 *    selectedItem="undefined"
 *    useVirtualLayout="false"
 * 
 *    <strong>Events</strong>
 *    itemFocusChanged="<i>No default</i>"
 *    selectionChanged="<i>No default</i>"
 *    selectionChanging="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class ListBase extends SkinnableDataContainer 
{
    include "../../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Static constant representing the value "no selection".
     * 
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public static const NO_SELECTION:int = -1;
    
    /**
     *  @private
     *  Static constant representing no proposed selection.
     */
    private static const NO_PROPOSED_SELECTION:int = -2;
    
    /**
     *  @private
     *  Static constant representing no item in focus. 
     */
    private static const NO_CARET:int = -1;
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function ListBase()
    {
        super();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  currentCaretIndex
    //----------------------------------
    
    mx_internal var _currentCaretIndex:Number = NO_CARET; 
    
    [Bindable("itemFocusChanged")]
    /**
     *  Item that is currently in focus. 
     *
     *  @default -1
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get currentCaretIndex():Number
    {
        return _currentCaretIndex;
    }
    
    /**
     *  @private
     */
    mx_internal var doingWholesaleChanges:Boolean = false;
    
    //----------------------------------
    //  dataProvider
    //----------------------------------
    private var dataProviderChanged:Boolean;
    
    /**
     *  @private
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function set dataProvider(value:IList):void
    {
        if (dataProvider)
            dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler);
    
        dataProviderChanged = true;
        doingWholesaleChanges = true;
        
        // ensure that our listener is added before the dataGroup which adds a listener during
        // the base class setter if the dataGroup already exists.  If the dataGroup isn't
        // created yet, then we still be first.
        if (value)
            value.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler);

        super.dataProvider = value;
        invalidateProperties();
    }
    
    //----------------------------------
    //  labelField
    //----------------------------------
    private var labelFieldOrFunctionChanged:Boolean; 
    private var _labelField:String;
    
    /**
     *  The name of the field in the data provider items to display 
     *  as the label. 
     *  The <code>labelFunction</code> property overrides this property.
     *
     *  @default null 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get labelField():String
    {
        return _labelField;
    }
    
    /**
     *  @private
     */
    public function set labelField(value:String):void
    {
        if (value == _labelField)
            return 
            
        _labelField = value;
        labelFieldOrFunctionChanged = true;
        invalidateProperties();
    }
    
    //----------------------------------
    //  labelFunction
    //----------------------------------
    
    private var _labelFunction:Function; 
    
    /**
     *  A user-supplied function to run on each item to determine its label.  
     *  The <code>labelFunction</code> property overrides 
     *  the <code>labelField</code> property.
     *
     *  <p>You can supply a <code>labelFunction</code> that finds the 
     *  appropriate fields and returns a displayable string. The 
     *  <code>labelFunction</code> is also good for handling formatting and 
     *  localization. </p>
     *
     *  <p>The label function takes a single argument which is the item in 
     *  the data provider and returns a String.</p>
     *  <pre>
     *  myLabelFunction(item:Object):String</pre>
     *
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get labelFunction():Function
    {
        return _labelFunction;
    }
    
    /**
     *  @private
     */
    public function set labelFunction(value:Function):void
    {
        if (value == _labelFunction)
            return 
            
        _labelFunction = value;
        labelFieldOrFunctionChanged = true;
        invalidateProperties(); 
    }
    
    //----------------------------------
    //  selectedIndex
    //----------------------------------

    /**
     *  @private
     *  The proposed selected index. This is a temporary variable that is
     *  used until the selected index is committed.
     */
    mx_internal var _proposedSelectedIndex:int = NO_PROPOSED_SELECTION;
    
    /** 
     *  @private
     *  Flag that is set when the selectedIndex has been adjusted due to
     *  items being added or removed. When this flag is true, the value
     *  of the selectedIndex has changed, but the actual selected item
     *  is the same. This flag is cleared in commitProperties().
     */
    mx_internal var selectedIndexAdjusted:Boolean = false;
    
    /** 
     *  @private
     *  Flag that is set when the currentCaretIndex has been adjusted due to
     *  items being added or removed. This flag is cleared in 
     *  commitProperties().
     */
    mx_internal var currentCaretIndexAdjusted:Boolean = false;
    
    /**
     *  @private
     *  Internal storage for the selectedIndex property.
     */
    mx_internal var _selectedIndex:int = NO_SELECTION;
    
    [Bindable("selectionChanged")]
    /**
     *  The 0-based index of the selected item, or -1 if no item is selected.
     *  Setting the <code>selectedIndex</code> property deselects the currently selected
     *  item and selects the data item at the specified index.
     *
     *  <p>The value is always between -1 and (<code>dataProvider.length</code> - 1). 
     *  If items at a lower index than <code>selectedIndex</code> are 
     *  removed from the component, the selected index is adjusted downward
     *  accordingly.</p>
     *
     *  <p>If the selected item is removed, the selected index is set to:</p>
     *
     *  <ul>
     *    <li>-1 if <code>requiresSelection</code> = <code>false</code> 
     *     or there are no remaining items.</li>
     *    <li>0 if <code>requiresSelection</code> = <code>true</code> 
     *     and there is at least one item.</li>
     *  </ul>
     *
     *  @default -1
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedIndex():int
    {
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            return _proposedSelectedIndex;
            
        return _selectedIndex;
    }
    
    /**
     *  @private
     */
    public function set selectedIndex(value:int):void
    {
        if (value == selectedIndex)
            return;
            
        _proposedSelectedIndex = value;
        invalidateProperties();
    }

    //----------------------------------
    //  selectedItem
    //----------------------------------
    
    private var _pendingSelectedItem:*;
    
    [Bindable("selectionChanged")]
    /**
     *  The item that is currently selected. 
     *  Setting this property deselects the currently selected 
     *  item and selects the newly specified item.
     *
     *  <p>Setting <code>selectedItem</code> to an item that is not 
     *  in this component results in no selection, 
     *  and <code>selectedItem</code> being set to <code>undefined</code>.</p>
     * 
     *  <p>If the selected item is removed, the selected item is set to:</p>
     *
     *  <ul>
     *    <li><code>undefined</code> if <code>requiresSelection</code> = <code>false</code> 
     *      or there are no remaining items.</li>
     *    <li>The first item if <code>requiresSelection</code> = <code>true</code> 
     *      and there is at least one item.</li>
     *  </ul>
     *
     *  @default undefined
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedItem():*
    {
        if (_pendingSelectedItem !== undefined)
            return _pendingSelectedItem;
            
        if (selectedIndex == NO_SELECTION || dataProvider == null)
           return undefined;
           
        return dataProvider.getItemAt(selectedIndex);
    }
    
    /**
     *  @private
     */
    public function set selectedItem(value:*):void
    {
        if (selectedItem === value)
            return;
        
        _pendingSelectedItem = value;
        invalidateProperties();
    }

    //----------------------------------
    //  requiresSelection
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the requiresSelection property.
     */
    private var _requiresSelection:Boolean = false;
    
    /**
     *  @private
     *  Flag that is set when requiresSelection has changed.
     */
    private var requiresSelectionChanged:Boolean = false;

    /**
     *  If <code>true</code>, a data item must always be selected in the control.
     *  If the value is <code>true</code>, the <code>selectedIndex</code> property 
     *  is always set to a value between 0 and (<code>dataProvider.length</code> - 1), 
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get requiresSelection():Boolean
    {
        return _requiresSelection;
    }

    /**
     *  @private
     */
    public function set requiresSelection(value:Boolean):void
    {
        if (value == _requiresSelection)
            return;
            
        _requiresSelection = value;
        
        // We only need to update if the value is changing 
        // from false to true
        if (value == true)
        {
            requiresSelectionChanged = true;
            invalidateProperties();
        }
    }
    
    //----------------------------------
    //  useVirtualLayout
    //----------------------------------

    /**
     *  @private
     */
    private var _useVirtualLayout:Boolean = false;
    
    /**
     *  Sets the value of the <code>useVirtualLayout</code> property
     *  of the layout associated with this control.  
     *  If the layout is subsequently replaced and the value of this 
     *  property is <code>true</code>, then the new layout's 
     *  <code>useVirtualLayout</code> property is set to <code>true</code>.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get useVirtualLayout():Boolean
    {
        return (layout) ? layout.useVirtualLayout : _useVirtualLayout;
    }

    /**
     *  @private
     *  Note: this property deviates a little from the conventional delegation pattern.
     *  If the user explicitly sets ListBase.useVirtualLayout=false and then sets
     *  the layout property to a layout with useVirtualLayout=true, the layout's value
     *  for this property trumps the ListBase.  The convention dictates opposite
     *  however in this case, always honoring the layout's useVirtalLayout property seems 
     *  less likely to cause confusion.
     */
    public function set useVirtualLayout(value:Boolean):void
    {
        if (value == useVirtualLayout)
            return;
            
        _useVirtualLayout = value;
        if (layout)
            layout.useVirtualLayout = value;
    }
    
    /**
     *  @private
     */
    override public function set layout(value:LayoutBase):void
    {
        if (useVirtualLayout)
            value.useVirtualLayout = true;
        super.layout = value;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden properties: UIComponent
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  baselinePosition
    //----------------------------------

    /**
     *  @private
     *  The baseline position of a ListBase is calculated for the first item renderer.
     *  If there are no items, one is temporarily added to do the calculation.
     */
    override public function get baselinePosition():Number
    {
        if (!validateBaselinePosition())
            return NaN;
        
        // Fabricate temporary data provider if necessary.
        var isNull:Boolean = dataProvider == null;
        var isEmpty:Boolean = dataProvider != null && dataProvider.length == 0;
         
        if (isNull || isEmpty)
        {
            var originalProvider:IList = isEmpty ? dataProvider : null;
            dataProvider = new mx.collections.ArrayList([ new Object() ]);
            validateNow();
        }
        
        if (!dataGroup || dataGroup.numElements == 0)
            return super.baselinePosition;
        
        // Obtain reference to newly generated item element which will be used
        // to compute the baseline.
        var listItem:Object = dataGroup ? dataGroup.getElementAt(0) : undefined;
        if (!listItem)
            return super.baselinePosition;
        
        // Compute baseline position of item relative to our list component.
        if ("baselinePosition" in listItem)
            var result:Number = getSkinPartPosition(IVisualElement(listItem)).y + listItem.baselinePosition;
        else    
            super.baselinePosition;

        // Restore the previous state of our list.
        if (isNull || isEmpty)
        {
            if (isNull)
                dataProvider = null;
            else if (isEmpty)
                dataProvider = originalProvider;
                
            validateNow();
        }
        
        return result;
    }
        
    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    override protected function commitProperties():void
    {
    	var e:IndexChangedEvent; 
        var changedSelection:Boolean = false;
        
        super.commitProperties();
        
        if (dataProviderChanged)
        {
            dataProviderChanged = false;
            doingWholesaleChanges = false;
        
            // TODO: should resetting the dataProvider clear out all of its state?
            // or should we preserve selectedIndex
            if (selectedIndex >= 0 && dataProvider && selectedIndex < dataProvider.length)
               itemSelected(selectedIndex, true);
            else if (requiresSelection)
               _proposedSelectedIndex = 0;
            else
               selectedIndex = -1;
        }
            
        if (requiresSelectionChanged)
        {
            requiresSelectionChanged = false;
            
            if (requiresSelection &&
                    selectedIndex == NO_SELECTION &&
                    dataProvider &&
                    dataProvider.length > 0)
            {
                // Set the proposed selected index here to make sure
                // commitSelection() is called below.
                _proposedSelectedIndex = 0;
            }
        }
        
        if (_pendingSelectedItem !== undefined)
        {
            if (dataProvider)
                _proposedSelectedIndex = dataProvider.getItemIndex(_pendingSelectedItem);
            _pendingSelectedItem = undefined;
        }
        
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            changedSelection = commitSelection();
        
        // If the selectedIndex has been adjusted to account for items that
        // have been added or removed, send out a "selectionChanged" event 
        // so any bindings to selectedIndex are updated correctly.
        if (selectedIndexAdjusted)
        {
            selectedIndexAdjusted = false;
            if (!changedSelection)
            {
                e = new IndexChangedEvent(IndexChangedEvent.SELECTION_CHANGED);
                e.oldIndex = selectedIndex;
                e.newIndex = selectedIndex;
                dispatchEvent(e);
            }
        }
        
        if (currentCaretIndexAdjusted)
        {
        	currentCaretIndexAdjusted = false;
        	if (!changedSelection)
        	{
        		// Put the new currentCaretIndex renderer into the
        		// caret state and dispatch an "itemFocusChanged" 
        		// event to update any bindings. Additionally, update 
        		// the backing variable. 
        		itemInCaret(selectedIndex, true); 
        		_currentCaretIndex = selectedIndex; 
        		
        		e = new IndexChangedEvent(IndexChangedEvent.ITEM_FOCUS_CHANGED); 
        		e.oldIndex = currentCaretIndex; 
        		e.newIndex = currentCaretIndex;
        		dispatchEvent(e);  
        	}
        }
        
        if (labelFieldOrFunctionChanged)
        {
            //Cycle through all instantiated renderers
            if (dataGroup)
            {
                for (var i:int = 0; i < dataGroup.numChildren; i++)
                {
                    //TODO: Ryan, figure out numChildren/numElement vs. getElement/getChild
                    //and which is more performant. 
                    var renderer:IItemRenderer = dataGroup.getElementAt(i) as IItemRenderer; 
                    //Push the correct text into the renderer by settings its labelText
                    //property 
                    if (renderer)
                    {
                        renderer.labelText = itemToLabel(renderer.data); 
                    }
                }
            }

            labelFieldOrFunctionChanged = false; 
        }
    }
    
    /**
     *  @private
     */
    override public function updateRenderer(renderer:IVisualElement):void
    {
        var transitions:Array;
        
        // First clean up any old, stale properties like selected and caret   
        if (renderer is IItemRenderer)
        {
            // If there are transitions bound to the renderer, lets turn them 
            // off while we clear stale properties by setting ItemRenderer's 
            // mx_internal property, playTransitions, to false
            if (renderer is ItemRenderer)
                ItemRenderer(renderer).playTransitions = false; 
            
            // TODO (dsubrama) - Go through helper methods to do this. 
            // Make itemSelected()/itemInCaret() pass around the renderer 
            // instead of index
            IItemRenderer(renderer).selected = false;
            IItemRenderer(renderer).caret = false;
            
            // Now turn the transitions back on by setting playTransitions
            // back to true 
            if (renderer is ItemRenderer)
                ItemRenderer(renderer).playTransitions = true;  
        }    
        
        // Now run through and initialize the renderer correctly
        super.updateRenderer(renderer); 
          
        var index:Number = dataGroup.getElementIndex(renderer);
        
        // Set any new properties on the renderer now that it's going to 
        // come back into use. 
        if (isItemIndexSelected(index))
            itemSelected(index, true);
        if (renderer is IItemRenderer)
            IItemRenderer(renderer).labelText = itemToLabel(IItemRenderer(renderer).data);
    }
    
    /**
     *  @private
     */
    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);
        if (instance == dataGroup)
        {
            // Not your typical delegation, see 'set useVirtualLayout'
            if (_useVirtualLayout && dataGroup.layout)
                dataGroup.layout.useVirtualLayout = true;
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Given a data item, return the correct text a renderer
     *  should display while taking the <code>labelField</code> 
     *  and <code>labelFunction</code> properties into account. 
     *
     *  @param item A data item 
     *  
     *  @return String representing the text to display for the 
     *  data item in the  renderer. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function itemToLabel(item:Object):String
    {
        return LabelUtil.itemToLabel(item, labelField, labelFunction);
    }
    
    /**
     *  Called when an item is selected or deselected. 
     *  Subclasses must override this method to display the selection.
     *
     *  @param index The item index that was selected.
     *
     *  @param selected <code>true</code> if the item is selected, 
     *  and <code>false</code> if it is deselected.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemSelected(index:int, selected:Boolean):void
    {
        // Subclasses must override this method to display the selection.
    }
    
    /**
     *  Called when an item is in its caret state or not. 
     *  Subclasses must override this method to display the caret. 
     *
     *  @param index The item index that was put into caret state. 
     *
     *  @param inFocus <code>true</code> if the item is in the caret state,  
     *  and <code>false</code> if it is not.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemInCaret(index:int, caret:Boolean):void
    {
        // Subclasses must override this method to display the caret.
    }
    
    /**
     *  Returns <code>true</code> if the item is selected by calling 
     *  <code>isItemIndexSelected()</code>.
     * 
     *  <p>If multiple instances of the item are in the list, 
     *  this method only checks to see whether the item at  
     *  <code>dataProvider.getItemIndex()</code> (usually the first 
     *  instance of that item) is selected.</p>
     * 
     *  @param item The item whose selection status is being checked
     *
     *  @return <code>true</code> if the item is selected, 
     *  and <code>false</code> otherwise.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function isItemSelected(item:Object):Boolean
    {
        var index:int = dataProvider.getItemIndex(item);
        
        return isItemIndexSelected(index);
    }
        
    /**
     *  Returns <code>true</code> if the item at the index is selected.
     * 
     *  @param index The index of the item whose selection status is being checked
     *
     *  @return <code>true</code> if the item at that index is selected, 
     *  and <code>false</code> otherwise.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function isItemIndexSelected(index:int):Boolean
    {        
        return index == selectedIndex;
    }
    
    /**
     *  Returns true if the item at the index is the caret item, which is
     *  essentially the item in focus. 
     * 
     *  @param index The index of the item whose caret status is being checked
     *
     *  @return true if the item at that index is the caret item, false otherwise.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function isItemInCaret(index:int):Boolean
    {        
        return index == currentCaretIndex;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Private Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Set current caret index. This function takes the item that was
     *  previously the caret item and sets its caret property to false,
     *  then takes the current proposed caret item and sets its 
     *  caret property to true as well as updating the backing variable. 
     * 
     */
    mx_internal function setCurrentCaretIndex(value:Number):void
    {
        itemInCaret(currentCaretIndex, false); 
        
        _currentCaretIndex = value;
        
        itemInCaret(currentCaretIndex, true);
    }
    
    /**
     *  @private
     *  The selection validation and commitment workhorse method. 
     *  Called to commit the pending selected index. This method dispatches
     *  the "selectionChanging" event, and if the event is not cancelled,
     *  commits the selection change and then dispatches the "selectionChanged"
     *  event.
     * 
     *  Returns true if the selection was committed, or false if the selection
     *  was cancelled.
     */
    protected function commitSelection(dispatchChangedEvents:Boolean = true):Boolean
    {
        // Step 1: make sure the proposed selected index is in range.
        var maxIndex:int = dataProvider ? dataProvider.length - 1 : -1;
        var oldSelectedIndex:int = _selectedIndex;
        var oldCaretIndex:int = _currentCaretIndex;
        
        if (_proposedSelectedIndex < NO_SELECTION)
            _proposedSelectedIndex = NO_SELECTION;
        if (_proposedSelectedIndex > maxIndex)
            _proposedSelectedIndex = maxIndex;
        if (requiresSelection && _proposedSelectedIndex == NO_SELECTION && 
            dataProvider && dataProvider.length > 0)
        {
            _proposedSelectedIndex = NO_PROPOSED_SELECTION;
            return false;
        }
        
        // Step 2: dispatch the "selectionChanging" event. If preventDefault() is called
        // on this event, the selection change will be cancelled.
        var e:IndexChangedEvent = new IndexChangedEvent(IndexChangedEvent.SELECTION_CHANGING, false, true);
        e.oldIndex = _selectedIndex;
        e.newIndex = _proposedSelectedIndex;
        if (!dispatchEvent(e))
        {
            // The event was cancelled. Cancel the selection change and return.
            _proposedSelectedIndex = NO_PROPOSED_SELECTION;
            return false;
        }
        
        // Step 3: commit the selection change and caret change 
        if (_selectedIndex != NO_SELECTION)
            itemSelected(_selectedIndex, false);
        if (_proposedSelectedIndex != NO_SELECTION)
            itemSelected(_proposedSelectedIndex, true);
        _selectedIndex = _proposedSelectedIndex;
        setCurrentCaretIndex(_proposedSelectedIndex); 
        _proposedSelectedIndex = NO_PROPOSED_SELECTION;
        
        // Step 4: dispatch the "selectionChanged" event and "itemFocusChanged" 
        // events based on the dispatchChangeEvents parameter. Overrides may  
        // chose to dispatch the selectionChanged/itemFocusChanged events 
        // themselves, in which case we wouldn't want to dispatch the event 
        // here. 
        if (dispatchChangedEvents)
        {
            // Dispatch the selectionChanged event
            e = new IndexChangedEvent(IndexChangedEvent.SELECTION_CHANGED);
            e.oldIndex = oldSelectedIndex;
            e.newIndex = _selectedIndex;
            dispatchEvent(e);
            
            //Dispatch the itemFocusChanged event 
            e = new IndexChangedEvent(IndexChangedEvent.ITEM_FOCUS_CHANGED); 
            e.oldIndex = oldCaretIndex; 
            e.newIndex = currentCaretIndex; 
            dispatchEvent(e);  
        }
        
        return true;
     }
    
    /**
     *  Adjusts the selected index to account for items being added to or 
     *  removed from this component. This method adjusts the selected index
     *  value and dispatches a <code>selectionChanged</code> event. 
     *  It does not dispatch a <code>selectionChanging</code> event 
     *  or allow the cancellation of the selection. 
     *  It also does not call the <code>itemSelected()</code> method, 
     *  since the same item is selected; 
     *  the only thing that has changed is the index of the item.
     * 
     *  <p>A <code>selectionChanged</code> event is dispatched in the next call to 
     *  the <code>commitProperties()</code> method.</p>
     *
     *  <p>The <code>selectionChanging</code> event is not sent when
     *  the <code>selectedIndex</code> is adjusted.</p>
     *
     *  @param newIndex The new index.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function adjustSelectedIndex(newIndex:int):void
    {
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            _proposedSelectedIndex = newIndex;
        else
            _selectedIndex = newIndex;
        selectedIndexAdjusted = true;
        invalidateProperties();
    }
    
    /**
     *  @private
     *  Called when an item has been added to this component.
     */
    protected function itemAddedHandler(item:*, index:int):void
    {
        if (selectedIndex == NO_SELECTION || doingWholesaleChanges)
            return;
            
        // If an item is added before the selected item, bump up our
        // selected index backing variable. 
        if (index <= selectedIndex)
            adjustSelectedIndex(selectedIndex + 1);
    }
    
    /**
     *  @private
     *  Called when an item has been removed from this component.
     */
    protected function itemRemovedHandler(item:*, index:int):void
    {
        if (selectedIndex == NO_SELECTION || doingWholesaleChanges)
            return;
        
        // If the selected item is being removed, clear the selection (or
        // reset to the first item if requiresSelection is true)
        if (index == selectedIndex)
        {
            if (requiresSelection && dataProvider && dataProvider.length > 0)
            {       
                if (index == 0)
                {
                    //We can't just set selectedIndex to 0 directly
                    //since the previous value was 0 and the new value is
                    //0, so the setter will return early.  
                    _proposedSelectedIndex = 0;
                    invalidateProperties();
                }
                else 
                    selectedIndex = 0;
            }
            else
                adjustSelectedIndex(-1);
        }
        else if (index < selectedIndex)
        {
            // An item below the selected index has been removed, bump
            // the selected index backing variable.
            adjustSelectedIndex(selectedIndex - 1);
        }
    }
    
    //--------------------------------------------------------------------------
    //
    //  Event Handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Called when contents within the dataProvider changes.  
     *
     *  @param event The collection change event
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dataProvider_collectionChangeHandler(event:Event):void
    {
        if (event is CollectionEvent)
        {
            var ce:CollectionEvent = CollectionEvent(event);

            if (ce.kind == CollectionEventKind.ADD)
            {
                itemAddedHandler(ce.items[0], ce.location);
            }
            else if (ce.kind == CollectionEventKind.REMOVE)
            {
                itemRemovedHandler(ce.items[0], ce.location);
            }
            else if (ce.kind == CollectionEventKind.RESET)
            {
                // Data provider is being reset, clear out the selection
                if (dataProvider.length == 0)
                {
                    selectedIndex = NO_SELECTION;
                    setCurrentCaretIndex(NO_CARET);
                }
                else
                {
                    dataProviderChanged = true; 
                    invalidateProperties(); 
                }
            }
            else if (ce.kind == CollectionEventKind.REPLACE ||
                ce.kind == CollectionEventKind.MOVE ||
                ce.kind == CollectionEventKind.REFRESH)
            {
                //These cases are handled by the DataGroup skinpart  
            }
        }
            
    }
}

}
<commit_msg>Transferred from the Adobe Open Source Subversion Repository<commit_after>////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2008 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.components.supportClasses
{
import flash.events.Event;

import spark.events.RendererExistenceEvent;
import spark.components.SkinnableDataContainer;
import spark.components.IItemRendererOwner; 
import spark.components.IItemRenderer; 
import spark.layouts.supportClasses.LayoutBase;
import spark.utils.LabelUtil; 

import mx.collections.IList;
import mx.core.IVisualElement;
import mx.core.mx_internal;
import mx.core.UIComponent; 
import mx.events.FlexEvent;
import mx.events.IndexChangedEvent;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;

use namespace mx_internal;  //ListBase and List share selection properties that are mx_internal

/**
 *  Dispatched when the selection is going to change. 
 *  Calling the <code>preventDefault()</code> method
 *  on the event prevents the selection from changing.
 *
 *  @eventType mx.events.IndexChangedEvent.CHANGING
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="changing", type="mx.events.IndexChangedEvent")]

/**
 *  Dispatched after the selection has changed. 
 *
 *  @eventType mx.events.IndexChangedEvent.CHANGE
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="change", type="mx.events.IndexChangedEvent")]

/**
 *  Dispatched after the focus has changed.  
 *
 *  @eventType mx.events.IndexChangedEvent.CARET_CHANGE
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Event(name="caretChange", type="mx.events.IndexChangedEvent")]

/**
 *  The ListBase class is the base class for all components that support
 *  selection. 
 *
 *  @mxml <p>The <code>&lt;ListBase&gt;</code> tag inherits all of the tag 
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;ListBase
 *
 *    <strong>Properties</strong>
 *    dataProvider="null"
 *    labelField="null"
 *    labelFunction="null"
 *    requireSelection="false"
 *    selectedIndex="-1"
 *    selectedItem="undefined"
 *    useVirtualLayout="false"
 * 
 *    <strong>Events</strong>
 *    caretChange="<i>No default</i>"
 *    change="<i>No default</i>"
 *    changing="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class ListBase extends SkinnableDataContainer 
{
    include "../../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Static constant representing the value "no selection".
     * 
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public static const NO_SELECTION:int = -1;
    
    /**
     *  @private
     *  Static constant representing no proposed selection.
     */
    private static const NO_PROPOSED_SELECTION:int = -2;
    
    /**
     *  @private
     *  Static constant representing no item in focus. 
     */
    private static const NO_CARET:int = -1;
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function ListBase()
    {
        super();
    }
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  caretIndex
    //----------------------------------
    
    mx_internal var _caretIndex:Number = NO_CARET; 
    
    [Bindable("caretChange")]
    /**
     *  Item that is currently in focus. 
     *
     *  @default -1
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get caretIndex():Number
    {
        return _caretIndex;
    }
    
    /**
     *  @private
     */
    mx_internal var doingWholesaleChanges:Boolean = false;
    
    //----------------------------------
    //  dataProvider
    //----------------------------------
    private var dataProviderChanged:Boolean;
    
    /**
     *  @private
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function set dataProvider(value:IList):void
    {
        if (dataProvider)
            dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler);
    
        dataProviderChanged = true;
        doingWholesaleChanges = true;
        
        // ensure that our listener is added before the dataGroup which adds a listener during
        // the base class setter if the dataGroup already exists.  If the dataGroup isn't
        // created yet, then we still be first.
        if (value)
            value.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataProvider_collectionChangeHandler);

        super.dataProvider = value;
        invalidateProperties();
    }
    
    //----------------------------------
    //  labelField
    //----------------------------------
    private var labelFieldOrFunctionChanged:Boolean; 
    private var _labelField:String;
    
    /**
     *  The name of the field in the data provider items to display 
     *  as the label. 
     *  The <code>labelFunction</code> property overrides this property.
     *
     *  @default null 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get labelField():String
    {
        return _labelField;
    }
    
    /**
     *  @private
     */
    public function set labelField(value:String):void
    {
        if (value == _labelField)
            return 
            
        _labelField = value;
        labelFieldOrFunctionChanged = true;
        invalidateProperties();
    }
    
    //----------------------------------
    //  labelFunction
    //----------------------------------
    
    private var _labelFunction:Function; 
    
    /**
     *  A user-supplied function to run on each item to determine its label.  
     *  The <code>labelFunction</code> property overrides 
     *  the <code>labelField</code> property.
     *
     *  <p>You can supply a <code>labelFunction</code> that finds the 
     *  appropriate fields and returns a displayable string. The 
     *  <code>labelFunction</code> is also good for handling formatting and 
     *  localization. </p>
     *
     *  <p>The label function takes a single argument which is the item in 
     *  the data provider and returns a String.</p>
     *  <pre>
     *  myLabelFunction(item:Object):String</pre>
     *
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get labelFunction():Function
    {
        return _labelFunction;
    }
    
    /**
     *  @private
     */
    public function set labelFunction(value:Function):void
    {
        if (value == _labelFunction)
            return 
            
        _labelFunction = value;
        labelFieldOrFunctionChanged = true;
        invalidateProperties(); 
    }
    
    //----------------------------------
    //  selectedIndex
    //----------------------------------

    /**
     *  @private
     *  The proposed selected index. This is a temporary variable that is
     *  used until the selected index is committed.
     */
    mx_internal var _proposedSelectedIndex:int = NO_PROPOSED_SELECTION;
    
    /** 
     *  @private
     *  Flag that is set when the selectedIndex has been adjusted due to
     *  items being added or removed. When this flag is true, the value
     *  of the selectedIndex has changed, but the actual selected item
     *  is the same. This flag is cleared in commitProperties().
     */
    mx_internal var selectedIndexAdjusted:Boolean = false;
    
    /** 
     *  @private
     *  Flag that is set when the caretIndex has been adjusted due to
     *  items being added or removed. This flag is cleared in 
     *  commitProperties().
     */
    mx_internal var caretIndexAdjusted:Boolean = false;
    
    /**
     *  @private
     *  Internal storage for the selectedIndex property.
     */
    mx_internal var _selectedIndex:int = NO_SELECTION;
    
    [Bindable("change")]
    /**
     *  The 0-based index of the selected item, or -1 if no item is selected.
     *  Setting the <code>selectedIndex</code> property deselects the currently selected
     *  item and selects the data item at the specified index.
     *
     *  <p>The value is always between -1 and (<code>dataProvider.length</code> - 1). 
     *  If items at a lower index than <code>selectedIndex</code> are 
     *  removed from the component, the selected index is adjusted downward
     *  accordingly.</p>
     *
     *  <p>If the selected item is removed, the selected index is set to:</p>
     *
     *  <ul>
     *    <li>-1 if <code>requireSelection</code> = <code>false</code> 
     *     or there are no remaining items.</li>
     *    <li>0 if <code>requireSelection</code> = <code>true</code> 
     *     and there is at least one item.</li>
     *  </ul>
     *
     *  @default -1
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedIndex():int
    {
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            return _proposedSelectedIndex;
            
        return _selectedIndex;
    }
    
    /**
     *  @private
     */
    public function set selectedIndex(value:int):void
    {
        if (value == selectedIndex)
            return;
            
        _proposedSelectedIndex = value;
        invalidateProperties();
    }

    //----------------------------------
    //  selectedItem
    //----------------------------------
    
    private var _pendingSelectedItem:*;
    
    [Bindable("change")]
    /**
     *  The item that is currently selected. 
     *  Setting this property deselects the currently selected 
     *  item and selects the newly specified item.
     *
     *  <p>Setting <code>selectedItem</code> to an item that is not 
     *  in this component results in no selection, 
     *  and <code>selectedItem</code> being set to <code>undefined</code>.</p>
     * 
     *  <p>If the selected item is removed, the selected item is set to:</p>
     *
     *  <ul>
     *    <li><code>undefined</code> if <code>requireSelection</code> = <code>false</code> 
     *      or there are no remaining items.</li>
     *    <li>The first item if <code>requireSelection</code> = <code>true</code> 
     *      and there is at least one item.</li>
     *  </ul>
     *
     *  @default undefined
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedItem():*
    {
        if (_pendingSelectedItem !== undefined)
            return _pendingSelectedItem;
            
        if (selectedIndex == NO_SELECTION || dataProvider == null)
           return undefined;
           
        return dataProvider.getItemAt(selectedIndex);
    }
    
    /**
     *  @private
     */
    public function set selectedItem(value:*):void
    {
        if (selectedItem === value)
            return;
        
        _pendingSelectedItem = value;
        invalidateProperties();
    }

    //----------------------------------
    //  requireSelection
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the requireSelection property.
     */
    private var _requireSelection:Boolean = false;
    
    /**
     *  @private
     *  Flag that is set when requireSelection has changed.
     */
    private var requireSelectionChanged:Boolean = false;

    /**
     *  If <code>true</code>, a data item must always be selected in the control.
     *  If the value is <code>true</code>, the <code>selectedIndex</code> property 
     *  is always set to a value between 0 and (<code>dataProvider.length</code> - 1), 
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get requireSelection():Boolean
    {
        return _requireSelection;
    }

    /**
     *  @private
     */
    public function set requireSelection(value:Boolean):void
    {
        if (value == _requireSelection)
            return;
            
        _requireSelection = value;
        
        // We only need to update if the value is changing 
        // from false to true
        if (value == true)
        {
            requireSelectionChanged = true;
            invalidateProperties();
        }
    }
    
    //----------------------------------
    //  useVirtualLayout
    //----------------------------------

    /**
     *  @private
     */
    private var _useVirtualLayout:Boolean = false;
    
    /**
     *  Sets the value of the <code>useVirtualLayout</code> property
     *  of the layout associated with this control.  
     *  If the layout is subsequently replaced and the value of this 
     *  property is <code>true</code>, then the new layout's 
     *  <code>useVirtualLayout</code> property is set to <code>true</code>.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get useVirtualLayout():Boolean
    {
        return (layout) ? layout.useVirtualLayout : _useVirtualLayout;
    }

    /**
     *  @private
     *  Note: this property deviates a little from the conventional delegation pattern.
     *  If the user explicitly sets ListBase.useVirtualLayout=false and then sets
     *  the layout property to a layout with useVirtualLayout=true, the layout's value
     *  for this property trumps the ListBase.  The convention dictates opposite
     *  however in this case, always honoring the layout's useVirtalLayout property seems 
     *  less likely to cause confusion.
     */
    public function set useVirtualLayout(value:Boolean):void
    {
        if (value == useVirtualLayout)
            return;
            
        _useVirtualLayout = value;
        if (layout)
            layout.useVirtualLayout = value;
    }
    
    /**
     *  @private
     */
    override public function set layout(value:LayoutBase):void
    {
        if (useVirtualLayout)
            value.useVirtualLayout = true;
        super.layout = value;
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden properties: UIComponent
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  baselinePosition
    //----------------------------------

    /**
     *  @private
     *  The baseline position of a ListBase is calculated for the first item renderer.
     *  If there are no items, one is temporarily added to do the calculation.
     */
    override public function get baselinePosition():Number
    {
        if (!validateBaselinePosition())
            return NaN;
        
        // Fabricate temporary data provider if necessary.
        var isNull:Boolean = dataProvider == null;
        var isEmpty:Boolean = dataProvider != null && dataProvider.length == 0;
         
        if (isNull || isEmpty)
        {
            var originalProvider:IList = isEmpty ? dataProvider : null;
            dataProvider = new mx.collections.ArrayList([ new Object() ]);
            validateNow();
        }
        
        if (!dataGroup || dataGroup.numElements == 0)
            return super.baselinePosition;
        
        // Obtain reference to newly generated item element which will be used
        // to compute the baseline.
        var listItem:Object = dataGroup ? dataGroup.getElementAt(0) : undefined;
        if (!listItem)
            return super.baselinePosition;
        
        // Compute baseline position of item relative to our list component.
        if ("baselinePosition" in listItem)
            var result:Number = getSkinPartPosition(IVisualElement(listItem)).y + listItem.baselinePosition;
        else    
            super.baselinePosition;

        // Restore the previous state of our list.
        if (isNull || isEmpty)
        {
            if (isNull)
                dataProvider = null;
            else if (isEmpty)
                dataProvider = originalProvider;
                
            validateNow();
        }
        
        return result;
    }
        
    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    override protected function commitProperties():void
    {
    	var e:IndexChangedEvent; 
        var changedSelection:Boolean = false;
        
        super.commitProperties();
        
        if (dataProviderChanged)
        {
            dataProviderChanged = false;
            doingWholesaleChanges = false;
        
            // TODO: should resetting the dataProvider clear out all of its state?
            // or should we preserve selectedIndex
            if (selectedIndex >= 0 && dataProvider && selectedIndex < dataProvider.length)
               itemSelected(selectedIndex, true);
            else if (requireSelection)
               _proposedSelectedIndex = 0;
            else
               selectedIndex = -1;
        }
            
        if (requireSelectionChanged)
        {
            requireSelectionChanged = false;
            
            if (requireSelection &&
                    selectedIndex == NO_SELECTION &&
                    dataProvider &&
                    dataProvider.length > 0)
            {
                // Set the proposed selected index here to make sure
                // commitSelection() is called below.
                _proposedSelectedIndex = 0;
            }
        }
        
        if (_pendingSelectedItem !== undefined)
        {
            if (dataProvider)
                _proposedSelectedIndex = dataProvider.getItemIndex(_pendingSelectedItem);
            _pendingSelectedItem = undefined;
        }
        
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            changedSelection = commitSelection();
        
        // If the selectedIndex has been adjusted to account for items that
        // have been added or removed, send out a "change" event 
        // so any bindings to selectedIndex are updated correctly.
        if (selectedIndexAdjusted)
        {
            selectedIndexAdjusted = false;
            if (!changedSelection)
            {
                e = new IndexChangedEvent(IndexChangedEvent.CHANGE);
                e.oldIndex = selectedIndex;
                e.newIndex = selectedIndex;
                dispatchEvent(e);
            }
        }
        
        if (caretIndexAdjusted)
        {
        	caretIndexAdjusted = false;
        	if (!changedSelection)
        	{
        		// Put the new caretIndex renderer into the
        		// caret state and dispatch an "caretChange" 
        		// event to update any bindings. Additionally, update 
        		// the backing variable. 
        		itemShowingCaret(selectedIndex, true); 
        		_caretIndex = selectedIndex; 
        		
        		e = new IndexChangedEvent(IndexChangedEvent.CARET_CHANGE); 
        		e.oldIndex = caretIndex; 
        		e.newIndex = caretIndex;
        		dispatchEvent(e);  
        	}
        }
        
        if (labelFieldOrFunctionChanged)
        {
            //Cycle through all instantiated renderers
            if (dataGroup)
            {
                for (var i:int = 0; i < dataGroup.numChildren; i++)
                {
                    //TODO: Ryan, figure out numChildren/numElement vs. getElement/getChild
                    //and which is more performant. 
                    var renderer:IItemRenderer = dataGroup.getElementAt(i) as IItemRenderer; 
                    //Push the correct text into the renderer by settings its label
                    //property 
                    if (renderer)
                    {
                        renderer.label = itemToLabel(renderer.data); 
                    }
                }
            }

            labelFieldOrFunctionChanged = false; 
        }
    }
    
    /**
     *  @private
     */
    override public function updateRenderer(renderer:IVisualElement):void
    {
        var transitions:Array;
        
        // First clean up any old, stale properties like selected and caret   
        if (renderer is IItemRenderer)
        {
            // If there are transitions bound to the renderer, lets turn them 
            // off while we clear stale properties by setting ItemRenderer's 
            // mx_internal property, playTransitions, to false
            if (renderer is ItemRenderer)
                ItemRenderer(renderer).playTransitions = false; 
            
            // TODO (dsubrama) - Go through helper methods to do this. 
            // Make itemSelected()/itemShowingCaret() pass around the renderer 
            // instead of index
            IItemRenderer(renderer).selected = false;
            IItemRenderer(renderer).showsCaret = false;
            
            // Now turn the transitions back on by setting playTransitions
            // back to true 
            if (renderer is ItemRenderer)
                ItemRenderer(renderer).playTransitions = true;  
        }    
        
        // Now run through and initialize the renderer correctly
        super.updateRenderer(renderer); 
          
        var index:Number = dataGroup.getElementIndex(renderer);
        
        // Set any new properties on the renderer now that it's going to 
        // come back into use. 
        if (isItemIndexSelected(index))
            itemSelected(index, true);
        if (renderer is IItemRenderer)
            IItemRenderer(renderer).label = itemToLabel(IItemRenderer(renderer).data);
    }
    
    /**
     *  @private
     */
    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);
        if (instance == dataGroup)
        {
            // Not your typical delegation, see 'set useVirtualLayout'
            if (_useVirtualLayout && dataGroup.layout)
                dataGroup.layout.useVirtualLayout = true;
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Given a data item, return the correct text a renderer
     *  should display while taking the <code>labelField</code> 
     *  and <code>labelFunction</code> properties into account. 
     *
     *  @param item A data item 
     *  
     *  @return String representing the text to display for the 
     *  data item in the  renderer. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function itemToLabel(item:Object):String
    {
        return LabelUtil.itemToLabel(item, labelField, labelFunction);
    }
    
    /**
     *  Called when an item is selected or deselected. 
     *  Subclasses must override this method to display the selection.
     *
     *  @param index The item index that was selected.
     *
     *  @param selected <code>true</code> if the item is selected, 
     *  and <code>false</code> if it is deselected.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemSelected(index:int, selected:Boolean):void
    {
        // Subclasses must override this method to display the selection.
    }
    
    /**
     *  Called when an item is in its caret state or not. 
     *  Subclasses must override this method to display the caret. 
     *
     *  @param index The item index that was put into caret state. 
     *
     *  @param showsCaret <code>true</code> if the item is in the caret state,  
     *  and <code>false</code> if it is not.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemShowingCaret(index:int, showsCaret:Boolean):void
    {
        // Subclasses must override this method to display the caret.
    }
        
    /**
     *  Returns <code>true</code> if the item at the index is selected.
     * 
     *  @param index The index of the item whose selection status is being checked
     *
     *  @return <code>true</code> if the item at that index is selected, 
     *  and <code>false</code> otherwise.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    mx_internal function isItemIndexSelected(index:int):Boolean
    {        
        return index == selectedIndex;
    }
    
    /**
     *  Returns true if the item at the index is the caret item, which is
     *  essentially the item in focus. 
     * 
     *  @param index The index of the item whose caret status is being checked
     *
     *  @return true if the item at that index is the caret item, false otherwise.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    mx_internal function isItemIndexShowingCaret(index:int):Boolean
    {        
        return index == caretIndex;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Private Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Set current caret index. This function takes the item that was
     *  previously the caret item and sets its caret property to false,
     *  then takes the current proposed caret item and sets its 
     *  caret property to true as well as updating the backing variable. 
     * 
     */
    mx_internal function setCurrentCaretIndex(value:Number):void
    {
        itemShowingCaret(caretIndex, false); 
        
        _caretIndex = value;
        
        itemShowingCaret(caretIndex, true);
    }
    
    /**
     *  @private
     *  The selection validation and commitment workhorse method. 
     *  Called to commit the pending selected index. This method dispatches
     *  the "changing" event, and if the event is not cancelled,
     *  commits the selection change and then dispatches the "change"
     *  event.
     * 
     *  Returns true if the selection was committed, or false if the selection
     *  was cancelled.
     */
    protected function commitSelection(dispatchChangedEvents:Boolean = true):Boolean
    {
        // Step 1: make sure the proposed selected index is in range.
        var maxIndex:int = dataProvider ? dataProvider.length - 1 : -1;
        var oldSelectedIndex:int = _selectedIndex;
        var oldCaretIndex:int = _caretIndex;
        
        if (_proposedSelectedIndex < NO_SELECTION)
            _proposedSelectedIndex = NO_SELECTION;
        if (_proposedSelectedIndex > maxIndex)
            _proposedSelectedIndex = maxIndex;
        if (requireSelection && _proposedSelectedIndex == NO_SELECTION && 
            dataProvider && dataProvider.length > 0)
        {
            _proposedSelectedIndex = NO_PROPOSED_SELECTION;
            return false;
        }
        
        // Step 2: dispatch the "changing" event. If preventDefault() is called
        // on this event, the selection change will be cancelled.
        var e:IndexChangedEvent = new IndexChangedEvent(IndexChangedEvent.CHANGING, false, true);
        e.oldIndex = _selectedIndex;
        e.newIndex = _proposedSelectedIndex;
        if (!dispatchEvent(e))
        {
            // The event was cancelled. Cancel the selection change and return.
            _proposedSelectedIndex = NO_PROPOSED_SELECTION;
            return false;
        }
        
        // Step 3: commit the selection change and caret change 
        if (_selectedIndex != NO_SELECTION)
            itemSelected(_selectedIndex, false);
        if (_proposedSelectedIndex != NO_SELECTION)
            itemSelected(_proposedSelectedIndex, true);
        _selectedIndex = _proposedSelectedIndex;
        setCurrentCaretIndex(_proposedSelectedIndex); 
        _proposedSelectedIndex = NO_PROPOSED_SELECTION;
        
        // Step 4: dispatch the "change" event and "caretChange" 
        // events based on the dispatchChangeEvents parameter. Overrides may  
        // chose to dispatch the change/caretChange events 
        // themselves, in which case we wouldn't want to dispatch the event 
        // here. 
        if (dispatchChangedEvents)
        {
            // Dispatch the change event
            e = new IndexChangedEvent(IndexChangedEvent.CHANGE);
            e.oldIndex = oldSelectedIndex;
            e.newIndex = _selectedIndex;
            dispatchEvent(e);
            
            //Dispatch the caretChange event 
            e = new IndexChangedEvent(IndexChangedEvent.CARET_CHANGE); 
            e.oldIndex = oldCaretIndex; 
            e.newIndex = caretIndex; 
            dispatchEvent(e);  
        }
        
        return true;
     }
    
    /**
     *  Adjusts the selected index to account for items being added to or 
     *  removed from this component. This method adjusts the selected index
     *  value and dispatches a <code>change</code> event. 
     *  It does not dispatch a <code>changing</code> event 
     *  or allow the cancellation of the selection. 
     *  It also does not call the <code>itemSelected()</code> method, 
     *  since the same item is selected; 
     *  the only thing that has changed is the index of the item.
     * 
     *  <p>A <code>change</code> event is dispatched in the next call to 
     *  the <code>commitProperties()</code> method.</p>
     *
     *  <p>The <code>changing</code> event is not sent when
     *  the <code>selectedIndex</code> is adjusted.</p>
     *
     *  @param newIndex The new index.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function adjustSelectedIndex(newIndex:int):void
    {
        if (_proposedSelectedIndex != NO_PROPOSED_SELECTION)
            _proposedSelectedIndex = newIndex;
        else
            _selectedIndex = newIndex;
        selectedIndexAdjusted = true;
        invalidateProperties();
    }
    
    /**
     *  Called when an item has been added to this component. Selection
     *  and caret related properties are adjusted accordingly. 
     * 
     *  @param index The index of the item being added. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemAdded(index:int):void
    {
        if (selectedIndex == NO_SELECTION || doingWholesaleChanges)
            return;
            
        // If an item is added before the selected item, bump up our
        // selected index backing variable. 
        if (index <= selectedIndex)
            adjustSelectedIndex(selectedIndex + 1);
    }
    
    /**
     *  Called when an item has been removed from this component. 
     *  Selection and caret related properties are adjusted 
     *  accordingly. 
     * 
     *  @param index The index of the item being removed. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function itemRemoved(index:int):void
    {
        if (selectedIndex == NO_SELECTION || doingWholesaleChanges)
            return;
        
        // If the selected item is being removed, clear the selection (or
        // reset to the first item if requireSelection is true)
        if (index == selectedIndex)
        {
            if (requireSelection && dataProvider && dataProvider.length > 0)
            {       
                if (index == 0)
                {
                    //We can't just set selectedIndex to 0 directly
                    //since the previous value was 0 and the new value is
                    //0, so the setter will return early.  
                    _proposedSelectedIndex = 0;
                    invalidateProperties();
                }
                else 
                    selectedIndex = 0;
            }
            else
                adjustSelectedIndex(-1);
        }
        else if (index < selectedIndex)
        {
            // An item below the selected index has been removed, bump
            // the selected index backing variable.
            adjustSelectedIndex(selectedIndex - 1);
        }
    }
    
    //--------------------------------------------------------------------------
    //
    //  Event Handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Called when contents within the dataProvider changes.  
     *
     *  @param event The collection change event
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dataProvider_collectionChangeHandler(event:Event):void
    {
        if (event is CollectionEvent)
        {
            var ce:CollectionEvent = CollectionEvent(event);

            if (ce.kind == CollectionEventKind.ADD)
            {
                itemAdded(ce.location);
            }
            else if (ce.kind == CollectionEventKind.REMOVE)
            {
                itemRemoved(ce.location);
            }
            else if (ce.kind == CollectionEventKind.RESET)
            {
                // Data provider is being reset, clear out the selection
                if (dataProvider.length == 0)
                {
                    selectedIndex = NO_SELECTION;
                    setCurrentCaretIndex(NO_CARET);
                }
                else
                {
                    dataProviderChanged = true; 
                    invalidateProperties(); 
                }
            }
            else if (ce.kind == CollectionEventKind.REPLACE ||
                ce.kind == CollectionEventKind.MOVE ||
                ce.kind == CollectionEventKind.REFRESH)
            {
                //These cases are handled by the DataGroup skinpart  
            }
        }
            
    }
}

}

{'fraction_non_alphanumeric': 0.13237611146885148, 'fraction_numerical': 0.005576182712463522, 'mean_word_length': 1.584082701975501, 'pattern_counts': {'":': 0, '<': 297, '<?xml version=': 0, '>': 297, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2499100', 'n_tokens_mistral': 19326, 'n_tokens_neox': 18504, 'n_words': 5456}