<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
{ 
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;

import mx.core.DragSource;
import mx.core.EventPriority;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IUID;
import mx.core.IVisualElement;
import mx.core.mx_internal;
import mx.events.DragEvent;
import mx.events.SandboxMouseEvent;
import mx.managers.DragManager;
import mx.managers.IFocusManagerComponent;
import mx.utils.ObjectUtil;
import mx.utils.UIDUtil;

import spark.components.supportClasses.ListBase;
import spark.core.NavigationUnit;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
import spark.layouts.supportClasses.DropLocation;

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

/**
 *  @copy spark.components.supportClasses.GroupBase#style:alternatingItemColors
 * 
 *  @default undefined
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  The alpha of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderAlpha", type="Number", inherit="no", theme="spark")]

/**
 *  The color of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderColor", type="uint", format="Color", inherit="no", theme="spark")]

/**
 *  Controls the visibility of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderVisible", type="Boolean", inherit="no", theme="spark")]

/**
 *  The alpha of the content background for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="contentBackgroundAlpha", type="Number", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:contentBackgroundColor
 *   
 *  @default 0xFFFFFF
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="contentBackgroundColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  The class to create instance of for the drag proxy during drag
 *  and drop operations initiated by the List.
 *
 *  Must be of type <code>IFlexDisplayObject</code>.
 *
 *  If the class implements the <code>ILayoutManagerClient</code> interface,
 *  then the instance will be validated by the DragManager.
 *
 *  If the class implements the <code>IVisualElement</code> interface,
 *  then the instance's <code>owner</code> property will be set to the List
 *  that initiates the drag.
 *
 *  The AIR DragManager takes a snapshot of the instance, while
 *  the non-AIR DragManager uses the instance directly.
 *
 *  @default spark.components.supportClasses.ListItemDragProxy
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="dragIndicatorClass", type="Class", inherit="no")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:rollOverColor
 *   
 *  @default 0xCEDBEF
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:selectionColor
 *
 *  @default 0xA8C6EE
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="selectionColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:symbolColor
 *   
 *  @default 0x000000
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */ 
[Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark")]

//--------------------------------------
//  Other metadata
//--------------------------------------

[IconFile("List.png")]
[DefaultTriggerEvent("change")]

/**
 *  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.
 *
 *  @mxml <p>The <code>&lt;s:List&gt;</code> tag inherits all of the tag 
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;s:List
 *    <strong>Properties</strong>
 *    allowMultipleSelection="false"
 *    selectedIndices="null"
 *    selectedItems="null"
 *    useVirtualLayout="true"
 * 
 *    <strong>Styles</strong>
 *    alternatingItemColors="undefined"
 *    contentBackgroundColor="0xFFFFFF"
 *    rollOverColor="0xCEDBEF"
 *    selectionColor="0xA8C6EE"
 *    symbolColor="0x000000"
 *  /&gt;
 *  </pre>
 *
 *  @see spark.skins.spark.ListSkin
 *
 *  @includeExample examples/ListExample.mxml
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class List extends ListBase implements IFocusManagerComponent
{
    include "../core/Version.as";
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function List()
    {
        super();
        useVirtualLayout = true;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  The point where the mouse down event was received.
     *  Used to track whether a drag operation should be initiated when the user
     *  drags further than a certain threshold. 
     */
    private var mouseDownPoint:Point;

    /**
     *  @private
     *  The index of the element the mouse down event was received for. Used to
     *  track which is the "focus item" for a drag and drop operation.
     */
    private var mouseDownIndex:int = -1;
    
    /**
     *  @private
     *  When dragging is enabled with multiple selection, the selection is not
     *  comitted immediately on mouse down, but we wait to see whether the user
     *  intended to start a drag gesture instead. In that case we postpone
     *  comitting the selection until mouse up.
     */
    private var pendingSelectionOnMouseUp:Boolean = false;
    private var pendingSelectionShiftKey:Boolean;
    private var pendingSelectionCtrlKey:Boolean;
    
    //--------------------------------------------------------------------------
    //
    //  Skin Parts
    //
    //--------------------------------------------------------------------------

    [SkinPart(required="false", type="flash.display.DisplayObject")]

    /**
     *  A skin part that defines a drop indicator. The drop indicator is resized
     *  and positioned by the layout to outline the insert location when dragging
     *  over the List.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var dropIndicator:IFactory; 

    //----------------------------------
    //  scroller
    //----------------------------------

    [SkinPart(required="false")]

    /**
     *  The optional Scroller used to scroll the List.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var scroller:Scroller;
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  allowMultipleSelection
    //----------------------------------
    
    private var _allowMultipleSelection:Boolean = false;
    
    /**
     *  If <code>true</code> multiple selections is enabled. 
     *  When switched at run time, the current selection
     *  is cleared. 
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get allowMultipleSelection():Boolean
    {
        return _allowMultipleSelection;
    }
    
    /**
     *  @private
     */
    public function set allowMultipleSelection(value:Boolean):void
    {
        if (value == _allowMultipleSelection)
            return;     
        
        _allowMultipleSelection = value; 
    }
    
    //----------------------------------
    //  dragEnabled
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the dragEnabled property.
     */
    private var _dragEnabled:Boolean = false;
    
    [Inspectable(defaultValue="false")]
    
    /**
     *  A flag that indicates whether you can drag items out of
     *  this control and drop them on other controls.
     *  If <code>true</code>, dragging is enabled for the control.
     *  If the <code>dropEnabled</code> property is also <code>true</code>,
     *  you can drag items and drop them within this control
     *  to reorder the items.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dragEnabled():Boolean
    {
        return _dragEnabled;
    }
    
    /**
     *  @private
     */
    public function set dragEnabled(value:Boolean):void
    {
        if (value == _dragEnabled)
            return;
        _dragEnabled = value;
        
        if (_dragEnabled)
        {
            addEventListener(DragEvent.DRAG_START, dragStartHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false, EventPriority.DEFAULT_HANDLER);
        }
        else
        {
            removeEventListener(DragEvent.DRAG_START, dragStartHandler, false);
            removeEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false);
        }
    }
    
    //----------------------------------
    //  dragMoveEnabled
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the dragMoveEnabled property.
     */
    private var _dragMoveEnabled:Boolean = false;
    
    [Inspectable(defaultValue="false")]
    
    /**
     *  A flag that indicates whether items can be moved instead
     *  of just copied from the control as part of a drag-and-drop
     *  operation.
     *  If <code>true</code>, and the <code>dragEnabled</code> property
     *  is <code>true</code>, items can be moved.
     *  Often the data provider cannot or should not have items removed
     *  from it, so a MOVE operation should not be allowed during
     *  drag-and-drop.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dragMoveEnabled():Boolean
    {
        return _dragMoveEnabled;
    }
    
    /**
     *  @private
     */
    public function set dragMoveEnabled(value:Boolean):void
    {
        _dragMoveEnabled = value;
    }

    //----------------------------------
    //  dropEnabled
    //----------------------------------

    /**
     *  @private
     *  Storage for the <code>dropEnabled</code> property.
     */
    private var _dropEnabled:Boolean = false;

    [Inspectable(defaultValue="false")]

    /**
     *  A flag that indicates whether dragged items can be dropped onto the 
     *  control.
     *
     *  <p>If you set this property to <code>true</code>,
     *  the control accepts all data formats, and assumes that
     *  the dragged data matches the format of the data in the data provider.
     *  If you want to explicitly check the data format of the data
     *  being dragged, you must handle one or more of the drag events,
     *  such as <code>dragEnter</code> and <code>dragOver</code>, 
     *  and call the DragEvent's <code>preventDefault()</code> method 
     *  to customize the way the list class accepts dropped data.</p>
     *
     *  <p>When you set <code>dropEnabled</code> to <code>true</code>, 
     *  Flex automatically calls the <code>showDropFeedback()</code> 
     *  and <code>hideDropFeedback()</code> methods to display the drop
     *  indicator.</p>
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dropEnabled():Boolean
    {
        return _dropEnabled;
    }

    /**
     *  @private
     */
    public function set dropEnabled(value:Boolean):void
    {
        if (value == _dropEnabled)
            return;
        _dropEnabled = value;
        
        if (_dropEnabled)
        {
            addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false, EventPriority.DEFAULT_HANDLER);
        }
        else
        {
            removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false);
            removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false);
            removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false);
            removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false);
        }
    }

    //----------------------------------
    //  selectedIndices
    //----------------------------------
    
    /**
     *  @private
     *  Internal storage for the selectedIndices property and invalidation variables.
     */
    private var _selectedIndices:Vector.<int>;
    private var _proposedSelectedIndices:Vector.<int> = new Vector.<int>(); 
    private var multipleSelectionChanged:Boolean; 
    
    [Bindable("change")]
    /**
     *  A Vector of ints representing the indices of the currently selected  
     *  item or items. 
     *  If multiple selection is disabled by setting 
     *  <code>allowMultipleSelection</code> to <code>false</code>, and this property  
     *  is set, the data item corresponding to the first index in the Vector is selected.  
     *
     *  <p>If multiple selection is enabled by setting 
     *  <code>allowMultipleSelection</code> to <code>true</code>, this property  
     *  contains a list of the selected indices in the reverse order in which they were selected. 
     *  That means the first element in the Vector corresponds to the last item selected.</p>
     *  
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedIndices():Vector.<int>
    {
        return _selectedIndices;
    }
    
    /**
     *  @private
     */
    public function set selectedIndices(value:Vector.<int>):void
    {
        if (_proposedSelectedIndices == value)
            return; 
        
        _proposedSelectedIndices = value;
        multipleSelectionChanged = true;  
        invalidateProperties();
    }
    
    //----------------------------------
    //  selectedItems
    //----------------------------------

    [Bindable("change")]
    /**
     *  An Vector of Objects representing the currently selected data items. 
     *  If multiple selection is disabled by setting <code>allowMultipleSelection</code>
     *  to <code>false</code>, and this property is set, the data item 
     *  corresponding to the first item in the Vector is selected.  
     *
     *  <p>If multiple selection is enabled by setting 
     *  <code>allowMultipleSelection</code> to <code>true</code>, this property  
     *  contains a list of the selected items in the reverse order in which they were selected. 
     *  That means the first element in the Vector corresponds to the last item selected.</p>
     * 
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedItems():Vector.<Object>
    {
        var result:Vector.<Object>;
        
        if (selectedIndices)
        {
            result = new Vector.<Object>();
            
            var count:int = selectedIndices.length;
            
            for (var i:int = 0; i < count; i++)
                result[i] = dataProvider.getItemAt(selectedIndices[i]);  
        }
        
        return result;
    }
    
    /**
     *  @private
     */
    public function set selectedItems(value:Vector.<Object>):void
    {
        var indices:Vector.<int>;
        
        if (value)
        {
            indices = new Vector.<int>();
            
            var count:int = value.length;
            
            for (var i:int = 0; i < count; i++)
            {
                // FIXME (dsubrama): What exactly should we do if an 
                //invalid item is in the selectedItems vector? 
                var index:int = dataProvider.getItemIndex(value[i]);
                if (index != -1)
                { 
                    indices.splice(0, 0, index);   
                }
                if (index == -1)
                {
                    indices = new Vector.<int>();
                    break;  
                }
            }
        }
        
        _proposedSelectedIndices = indices;
        multipleSelectionChanged = true;
        invalidateProperties(); 
    }

    //----------------------------------
    //  useVirtualLayout
    //----------------------------------

    /**
     *  @inheritDoc
     *
     *  @default true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function get useVirtualLayout():Boolean
    {
        return super.useVirtualLayout;
    }

    /**
     *  Overrides the inherited default property , it is true for this class.
     * 
     *  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 true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function set useVirtualLayout(value:Boolean):void
    {
        super.useVirtualLayout = value;
    }
        
    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------
    
     /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties(); 
        
        if (multipleSelectionChanged)
        {
            commitSelection(); 
            multipleSelectionChanged = false; 
        }
    }
    
    /**
     *  @private
     */
    override public function set hasFocusableChildren(value:Boolean):void
    {
        super.hasFocusableChildren = value;
        if (scroller)
            scroller.hasFocusableChildren = value;
    }
    
    /**
     *  @private
     *  Let ListBase handle single selection and afterwards come in and 
     *  handle multiple selection via the commitMultipleSelection() 
     *  helper method. 
     */
    override protected function commitSelection(dispatchChangedEvents:Boolean = true):Boolean
    {
        var oldSelectedIndex:Number = _selectedIndex;
        var oldCaretIndex:Number = _caretIndex;  
        
        // Ensure that multiple selection is allowed and that proposed 
        // selected indices honors it. For example, in the single 
        // selection case, proposedSelectedIndices should only be a 
        // vector of 1 entry. If its not, we pare it down and select the 
        // first item.  
        if (!allowMultipleSelection && !isEmpty(_proposedSelectedIndices))
        {
            var temp:Vector.<int> = new Vector.<int>(); 
            temp.push(_proposedSelectedIndices[0]); 
            _proposedSelectedIndices = temp;  
        }
        // Keep _proposedSelectedIndex in-sync with multiple selection properties. 
        if (!isEmpty(_proposedSelectedIndices))
           _proposedSelectedIndex = getFirstItemValue(_proposedSelectedIndices); 
        
        // Let ListBase handle the validating and commiting of the single-selection
        // properties.  
        var retVal:Boolean = super.commitSelection(false); 
        
        // If super.commitSelection returns a value of false, 
        // the selection was cancelled, so return false and exit. 
        if (!retVal)
            return false; 
        
        // Now keep _proposedSelectedIndices in-sync with single selection 
        // properties now that the single selection properties have been 
        // comitted.  
        if (selectedIndex > NO_SELECTION)
        {
            if (_proposedSelectedIndices && _proposedSelectedIndices.indexOf(selectedIndex) == -1)
                _proposedSelectedIndices.push(selectedIndex);
        }
        
        // Validate and commit the multiple selection related properties. 
        commitMultipleSelection(); 
        
        // Set the caretIndex based on the current selection 
        setCurrentCaretIndex(selectedIndex);
        
        // And dispatch change and caretChange events so that all of 
        // the bindings update correctly. 
        if (dispatchChangedEvents && retVal)
        {
            var e:IndexChangeEvent = new IndexChangeEvent(IndexChangeEvent.CHANGE);
            e.oldIndex = oldSelectedIndex;
            e.newIndex = _selectedIndex;
            dispatchEvent(e);
            
            e = new IndexChangeEvent(IndexChangeEvent.CARET_CHANGE); 
            e.oldIndex = oldCaretIndex; 
            e.newIndex = caretIndex;
            dispatchEvent(e);    
        }
        
        return retVal; 
    }
    
    /**
     *  @private
     *  Given a new selection interval, figure out which
     *  items are newly added/removed from the selection interval and update
     *  selection properties and view accordingly. 
     */
    protected function commitMultipleSelection():void
    {
        var removedItems:Vector.<int> = new Vector.<int>();
        var addedItems:Vector.<int> = new Vector.<int>();
        var i:int;
        var count:int;
        
        if (!isEmpty(_selectedIndices) && !isEmpty(_proposedSelectedIndices))
        {
            // Changing selection, determine which items were added to the 
            // selection interval 
            count = _proposedSelectedIndices.length;
            for (i = 0; i < count; i++)
            {
                if (_selectedIndices.indexOf(_proposedSelectedIndices[i]) < 0)
                    addedItems.push(_proposedSelectedIndices[i]);
            }
            // Then determine which items were removed from the selection 
            // interval 
            count = _selectedIndices.length; 
            for (i = 0; i < count; i++)
            {
                if (_proposedSelectedIndices.indexOf(_selectedIndices[i]) < 0)
                    removedItems.push(_selectedIndices[i]);
            }
        }
        else if (!isEmpty(_selectedIndices))
        {
            // Going to a null selection, remove all
            removedItems = _selectedIndices;
        }
        else if (!isEmpty(_proposedSelectedIndices))
        {
            // Going from a null selection, add all
            addedItems = _proposedSelectedIndices;
        }
         
        // De-select the old items that were selected 
        if (removedItems.length > 0)
        {
            count = removedItems.length;
            for (i = 0; i < count; i++)
            {
                itemSelected(removedItems[i], false);
            }
        }
        
        // Select the new items in the new selection interval 
        if (!isEmpty(_proposedSelectedIndices))
        {
            count = _proposedSelectedIndices.length;
            for (i = 0; i < count; i++)
            {
                itemSelected(_proposedSelectedIndices[i], true);
            }
        }
        
        // Commit the selected indices and put _proposedSelectedIndices
        // back to its default value.  
        _selectedIndices = _proposedSelectedIndices;
        _proposedSelectedIndices = new Vector.<int>();
    }
    
    /**
     *  @private
     */
    override protected function itemSelected(index:int, selected:Boolean):void
    {
        super.itemSelected(index, selected);
        
        var renderer:Object = dataGroup ? dataGroup.getElementAt(index) : null;
        
        if (renderer is IItemRenderer)
        {
            IItemRenderer(renderer).selected = selected;
        }
    }
    
    /**
     *  @private 
     */
    override protected function itemShowingCaret(index:int, showsCaret:Boolean):void
    {
        super.itemShowingCaret(index, showsCaret); 
        
        var renderer:Object = dataGroup ? dataGroup.getElementAt(index) : null;
        
        if (renderer is IItemRenderer)
        {
            IItemRenderer(renderer).showsCaret = showsCaret;
        }
    }
    
    /**
     *  @private
     */
    override mx_internal function isItemIndexSelected(index:int):Boolean
    {
        if (allowMultipleSelection && (selectedIndices != null))
            return selectedIndices.indexOf(index) != -1;
        
        return index == selectedIndex;
    }
    
    /**
     *  @private
     */
    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);
        if (instance == dataGroup)
        {
            dataGroup.addEventListener(
                RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler);
            dataGroup.addEventListener(
                RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler);
        }
        if (instance == scroller)
            scroller.hasFocusableChildren = hasFocusableChildren;
    }

    /**
     *  @private
     */
    override protected function partRemoved(partName:String, instance:Object):void
    {
        if (instance == dataGroup)
        {
            dataGroup.removeEventListener(
                RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler);
            dataGroup.removeEventListener(
                RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler);
        }
        
        super.partRemoved(partName, instance);
    }
    
    /**
     *  @private
     *  Called when an item has been added to this component.
     */
    override protected function itemAdded(index:int):void
    {
        adjustSelection(index, true); 
    }
    
    /**
     *  @private
     *  Called when an item has been removed from this component.
     */
    override protected function itemRemoved(index:int):void
    {
        adjustSelection(index, false);        
    }
    
    //--------------------------------------------------------------------------
    //
    //  Private Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Returns the index of the last selected item. In single 
     *  selection, this is just selectedIndex. In multiple 
     *  selection, this is the index of the first selected item.  
     */
    private function getLastSelectedIndex():int
    {
        if (selectedIndices && selectedIndices.length > 0)
            return selectedIndices[selectedIndices.length - 1]; 
        else 
            return 0; 
    }
    
    /**
     *  @private
     *  Given a Vector, returns the value of the first item, 
     *  or -1 if there are no items in the Vector; 
     */
    private function getFirstItemValue(v:Vector.<int>):int
    {
        if (v && v.length > 0)
            return v[0]; 
        else 
            return -1; 
    }
    
    /**
     *  @private
     *  Returns true if v is null or an empty Vector.
     */
    private function isEmpty(v:Vector.<int>):Boolean
    {
        return v == null || v.length == 0;
    }
    
    /**
     *  @private
     *  Taking into account which modifier keys were clicked, the new
     *  selectedIndices interval is calculated. 
     */
    private function calculateSelectedIndicesInterval(index:int, shiftKey:Boolean, ctrlKey:Boolean):Vector.<int>
    {
        var i:int; 
        var interval:Vector.<int> = new Vector.<int>();  
        
        if (!shiftKey)
        {
            if (ctrlKey)
            {
                if (!isEmpty(selectedIndices))
                {
                    // Quick check to see if selectedIndices had only one selected item
                    // and that item was de-selected
                    if (selectedIndices.length == 1 && (selectedIndices[0] == index))
                    {
                        // We need to respect requireSelection 
                        if (!requireSelection)
                            return interval; 
                        else 
                        {
                            interval.splice(0, 0, selectedIndices[0]); 
                            return interval; 
                        }
                    }
                    else
                    {
                        // Go through and see if the index passed in was in the 
                        // selection model. If so, leave it out when constructing
                        // the new interval so it is de-selected. 
                        var found:Boolean = false; 
                        for (i = 0; i < _selectedIndices.length; i++)
                        {
                            if (_selectedIndices[i] == index)
                                found = true; 
                            else if (_selectedIndices[i] != index)
                                interval.splice(0, 0, _selectedIndices[i]);
                        }
                        if (!found)
                        {
                            // Nothing from the selection model was de-selected. 
                            // Instead, the Ctrl key was held down and we're doing a  
                            // new add. 
                            interval.splice(0, 0, index);   
                        }
                        return interval; 
                    } 
                }
                // Ctrl+click with no previously selected items 
                else
                { 
                    interval.splice(0, 0, index); 
                    return interval; 
            }
            }
            // A single item was newly selected, add that to the selection interval.  
            else 
            { 
                interval.splice(0, 0, index); 
                return interval; 
        }
        }
        else // shiftKey
        {
            // A contiguous selection action has occurred. Figure out which new 
            // indices to add to the selection interval and return that. 
            var start:int = (!isEmpty(selectedIndices)) ? selectedIndices[selectedIndices.length - 1] : 0; 
            var end:int = index; 
            if (start < end)
            {
                for (i = start; i <= end; i++)
                {
                    interval.splice(0, 0, i); 
                }
            }
            else 
            {
                for (i = start; i >= end; i--)
                {
                    interval.splice(0, 0, i); 
                }
            }
            return interval; 
        }
    }
    
    //--------------------------------------------------------------------------
    //
    //  Drag methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  The default handler for the <code>dragStart</code> event.
     *
     *  @param event The DragEvent object.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dragStartHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;
        
        var dragSource:DragSource = new DragSource();
        addDragData(dragSource);
        DragManager.doDrag(this, 
                           dragSource, 
                           event, 
                           createDragIndicator(), 
                           0 /*xOffset*/, 
                           0 /*yOffset*/, 
                           0.8 /*imageAlpha*/, 
                           dragMoveEnabled);
    }
    
    /**
     *  @private
     *  Used to sort the selected indices during drag and drop operations.
     */
    private function compareValues(a:int, b:int):int
    {
        return a - b;
    } 
    
    /**
     *  Handles <code>DragEvent.DRAG_COMPLETE</code> events.  This method
     *  removes the items from the data provider.
     *
     *  @param event The DragEvent object.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dragCompleteHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;
        
        // Remove the dragged items only if they were drag moved to
        // a different list. If the items were drag moved to this
        // list, the reordering was already handles in the 
        // DragEvent.DRAG_DROP listener.
        if (!dragMoveEnabled ||
            event.action != DragManager.MOVE || 
            event.relatedObject == this)
            return;
        
        // Clear the selection, but remember which items were moved
        var movedIndices:Vector.<int> = selectedIndices;
        selectedIndices = new Vector.<int>();
        
        // Remove the moved items
        movedIndices.sort(compareValues);
        var count:int = movedIndices.length;
        for (var i:int = count - 1; i >= 0; i--)
        {
            dataProvider.removeItemAt(movedIndices[i]);
        }
    }
    
    /**
     *  @private
     *
     *  Gets an instance of a class that displays the visuals
     *  during a drag and drop operation.
     */
    private function createDragIndicator():IFlexDisplayObject
    {
        var dragIndicator:IFlexDisplayObject;
        var dragIndicatorClass:Class = Class(getStyle("dragIndicatorClass"));
        if (dragIndicatorClass)
        {
            dragIndicator = new dragIndicatorClass();
            if (dragIndicator is IVisualElement)
                IVisualElement(dragIndicator).owner = this;
        }
        
        return dragIndicator;
    }
    
    /**
     *  Adds the selected items to the DragSource object as part of
     *  a drag-and-drop operation.
     *  Override this method to add other data to the drag source.
     * 
     *  @param ds The DragSource object to which to add the data.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function addDragData(dragSource:DragSource):void
    {
        dragSource.addHandler(copySelectedItemsForDragDrop, "orderedItems");
        
        // Calculate the index of the focus item within the vector
        // of ordered items returned for the "sourceOrderedItems" format.
        var caretIndex:int = 0;
        var draggedIndices:Vector.<int> = selectedIndices;
        var count:int = draggedIndices.length;
        for (var i:int = 0; i < count; i++)
        {
            if (mouseDownIndex > draggedIndices[i])
                caretIndex++;
        }
        dragSource.addData(caretIndex, "orderedItemsCaretIndex");
    }

    /**
     *  @private.
     */
    private function copySelectedItemsForDragDrop():Vector.<Object>
    {
        // Copy the vector so that we don't modify the original
         // since selectedIndices returns a reference.
        var draggedIndices:Vector.<int> = selectedIndices.slice(0, selectedIndices.length);
        var result:Vector.<Object> = new Vector.<Object>(draggedIndices.length);

        // Sort in the order of the data source
        draggedIndices.sort(compareValues);
        
        // Copy the items
        var count:int = draggedIndices.length;
        for (var i:int = 0; i < count; i++)
            result[i] = dataProvider.getItemAt(draggedIndices[i]);  
        return result;
    }
    
    /**
     *  Handles <code>MouseEvent.MOUSE_DOWN</code> events from any of the 
     *  item renderers. This method handles the updating and commitment 
     *  of selection as well as remembers the mouse down point and
     *  attaches <code>MouseEvent.MOUSE_MOVE</code> and
     *  <code>MouseEvent.MOUSE_UP</code> listeners in order to handle
     *  drag gestures.<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
{ 
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;

import mx.core.DragSource;
import mx.core.EventPriority;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IUID;
import mx.core.IVisualElement;
import mx.core.mx_internal;
import mx.events.DragEvent;
import mx.events.SandboxMouseEvent;
import mx.managers.DragManager;
import mx.managers.IFocusManagerComponent;
import mx.utils.ObjectUtil;
import mx.utils.UIDUtil;

import spark.components.supportClasses.ListBase;
import spark.core.NavigationUnit;
import spark.events.IndexChangeEvent;
import spark.events.RendererExistenceEvent;
import spark.layouts.supportClasses.DropLocation;

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

/**
 *  @copy spark.components.supportClasses.GroupBase#style:alternatingItemColors
 * 
 *  @default undefined
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  The alpha of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderAlpha", type="Number", inherit="no", theme="spark")]

/**
 *  The color of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderColor", type="uint", format="Color", inherit="no", theme="spark")]

/**
 *  Controls the visibility of the border for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="borderVisible", type="Boolean", inherit="no", theme="spark")]

/**
 *  The alpha of the content background for this component.
 * 
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="contentBackgroundAlpha", type="Number", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:contentBackgroundColor
 *   
 *  @default 0xFFFFFF
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="contentBackgroundColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  The class to create instance of for the drag proxy during drag
 *  and drop operations initiated by the List.
 *
 *  Must be of type <code>IFlexDisplayObject</code>.
 *
 *  If the class implements the <code>ILayoutManagerClient</code> interface,
 *  then the instance will be validated by the DragManager.
 *
 *  If the class implements the <code>IVisualElement</code> interface,
 *  then the instance's <code>owner</code> property will be set to the List
 *  that initiates the drag.
 *
 *  The AIR DragManager takes a snapshot of the instance, while
 *  the non-AIR DragManager uses the instance directly.
 *
 *  @default spark.components.supportClasses.ListItemDragProxy
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="dragIndicatorClass", type="Class", inherit="no")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:rollOverColor
 *   
 *  @default 0xCEDBEF
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:selectionColor
 *
 *  @default 0xA8C6EE
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[Style(name="selectionColor", type="uint", format="Color", inherit="yes", theme="spark")]

/**
 *  @copy spark.components.supportClasses.GroupBase#style:symbolColor
 *   
 *  @default 0x000000
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */ 
[Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark")]

//--------------------------------------
//  Other metadata
//--------------------------------------

[IconFile("List.png")]
[DefaultTriggerEvent("change")]

/**
 *  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.
 *
 *  @mxml <p>The <code>&lt;s:List&gt;</code> tag inherits all of the tag 
 *  attributes of its superclass and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;s:List
 *    <strong>Properties</strong>
 *    allowMultipleSelection="false"
 *    selectedIndices="null"
 *    selectedItems="null"
 *    useVirtualLayout="true"
 * 
 *    <strong>Styles</strong>
 *    alternatingItemColors="undefined"
 *    contentBackgroundColor="0xFFFFFF"
 *    rollOverColor="0xCEDBEF"
 *    selectionColor="0xA8C6EE"
 *    symbolColor="0x000000"
 *  /&gt;
 *  </pre>
 *
 *  @see spark.skins.spark.ListSkin
 *
 *  @includeExample examples/ListExample.mxml
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public class List extends ListBase implements IFocusManagerComponent
{
    include "../core/Version.as";
    
    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    
    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function List()
    {
        super();
        useVirtualLayout = true;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  The point where the mouse down event was received.
     *  Used to track whether a drag operation should be initiated when the user
     *  drags further than a certain threshold. 
     */
    private var mouseDownPoint:Point;

    /**
     *  @private
     *  The index of the element the mouse down event was received for. Used to
     *  track which is the "focus item" for a drag and drop operation.
     */
    private var mouseDownIndex:int = -1;
    
    /**
     *  @private
     *  When dragging is enabled with multiple selection, the selection is not
     *  comitted immediately on mouse down, but we wait to see whether the user
     *  intended to start a drag gesture instead. In that case we postpone
     *  comitting the selection until mouse up.
     */
    private var pendingSelectionOnMouseUp:Boolean = false;
    private var pendingSelectionShiftKey:Boolean;
    private var pendingSelectionCtrlKey:Boolean;
    
    //--------------------------------------------------------------------------
    //
    //  Skin Parts
    //
    //--------------------------------------------------------------------------

    [SkinPart(required="false", type="flash.display.DisplayObject")]

    /**
     *  A skin part that defines a drop indicator. The drop indicator is resized
     *  and positioned by the layout to outline the insert location when dragging
     *  over the List.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var dropIndicator:IFactory; 

    //----------------------------------
    //  scroller
    //----------------------------------

    [SkinPart(required="false")]

    /**
     *  The optional Scroller used to scroll the List.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public var scroller:Scroller;
    
    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  allowMultipleSelection
    //----------------------------------
    
    private var _allowMultipleSelection:Boolean = false;
    
    /**
     *  If <code>true</code> multiple selections is enabled. 
     *  When switched at run time, the current selection
     *  is cleared. 
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get allowMultipleSelection():Boolean
    {
        return _allowMultipleSelection;
    }
    
    /**
     *  @private
     */
    public function set allowMultipleSelection(value:Boolean):void
    {
        if (value == _allowMultipleSelection)
            return;     
        
        _allowMultipleSelection = value; 
    }
    
    //----------------------------------
    //  dragEnabled
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the dragEnabled property.
     */
    private var _dragEnabled:Boolean = false;
    
    [Inspectable(defaultValue="false")]
    
    /**
     *  A flag that indicates whether you can drag items out of
     *  this control and drop them on other controls.
     *  If <code>true</code>, dragging is enabled for the control.
     *  If the <code>dropEnabled</code> property is also <code>true</code>,
     *  you can drag items and drop them within this control
     *  to reorder the items.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dragEnabled():Boolean
    {
        return _dragEnabled;
    }
    
    /**
     *  @private
     */
    public function set dragEnabled(value:Boolean):void
    {
        if (value == _dragEnabled)
            return;
        _dragEnabled = value;
        
        if (_dragEnabled)
        {
            addEventListener(DragEvent.DRAG_START, dragStartHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false, EventPriority.DEFAULT_HANDLER);
        }
        else
        {
            removeEventListener(DragEvent.DRAG_START, dragStartHandler, false);
            removeEventListener(DragEvent.DRAG_COMPLETE, dragCompleteHandler, false);
        }
    }
    
    //----------------------------------
    //  dragMoveEnabled
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the dragMoveEnabled property.
     */
    private var _dragMoveEnabled:Boolean = false;
    
    [Inspectable(defaultValue="false")]
    
    /**
     *  A flag that indicates whether items can be moved instead
     *  of just copied from the control as part of a drag-and-drop
     *  operation.
     *  If <code>true</code>, and the <code>dragEnabled</code> property
     *  is <code>true</code>, items can be moved.
     *  Often the data provider cannot or should not have items removed
     *  from it, so a MOVE operation should not be allowed during
     *  drag-and-drop.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dragMoveEnabled():Boolean
    {
        return _dragMoveEnabled;
    }
    
    /**
     *  @private
     */
    public function set dragMoveEnabled(value:Boolean):void
    {
        _dragMoveEnabled = value;
    }

    //----------------------------------
    //  dropEnabled
    //----------------------------------

    /**
     *  @private
     *  Storage for the <code>dropEnabled</code> property.
     */
    private var _dropEnabled:Boolean = false;

    [Inspectable(defaultValue="false")]

    /**
     *  A flag that indicates whether dragged items can be dropped onto the 
     *  control.
     *
     *  <p>If you set this property to <code>true</code>,
     *  the control accepts all data formats, and assumes that
     *  the dragged data matches the format of the data in the data provider.
     *  If you want to explicitly check the data format of the data
     *  being dragged, you must handle one or more of the drag events,
     *  such as <code>dragEnter</code> and <code>dragOver</code>, 
     *  and call the DragEvent's <code>preventDefault()</code> method 
     *  to customize the way the list class accepts dropped data.</p>
     *
     *  <p>When you set <code>dropEnabled</code> to <code>true</code>, 
     *  Flex automatically calls the <code>showDropFeedback()</code> 
     *  and <code>hideDropFeedback()</code> methods to display the drop
     *  indicator.</p>
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get dropEnabled():Boolean
    {
        return _dropEnabled;
    }

    /**
     *  @private
     */
    public function set dropEnabled(value:Boolean):void
    {
        if (value == _dropEnabled)
            return;
        _dropEnabled = value;
        
        if (_dropEnabled)
        {
            addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_OVER, dragOverHandler, false, EventPriority.DEFAULT_HANDLER);
            addEventListener(DragEvent.DRAG_DROP, dragDropHandler, false, EventPriority.DEFAULT_HANDLER);
        }
        else
        {
            removeEventListener(DragEvent.DRAG_ENTER, dragEnterHandler, false);
            removeEventListener(DragEvent.DRAG_EXIT, dragExitHandler, false);
            removeEventListener(DragEvent.DRAG_OVER, dragOverHandler, false);
            removeEventListener(DragEvent.DRAG_DROP, dragDropHandler, false);
        }
    }

    //----------------------------------
    //  selectedIndices
    //----------------------------------
    
    /**
     *  @private
     *  Internal storage for the selectedIndices property and invalidation variables.
     */
    private var _selectedIndices:Vector.<int>;
    private var _proposedSelectedIndices:Vector.<int> = new Vector.<int>(); 
    private var multipleSelectionChanged:Boolean; 
    
    [Bindable("change")]
    /**
     *  A Vector of ints representing the indices of the currently selected  
     *  item or items. 
     *  If multiple selection is disabled by setting 
     *  <code>allowMultipleSelection</code> to <code>false</code>, and this property  
     *  is set, the data item corresponding to the first index in the Vector is selected.  
     *
     *  <p>If multiple selection is enabled by setting 
     *  <code>allowMultipleSelection</code> to <code>true</code>, this property  
     *  contains a list of the selected indices in the reverse order in which they were selected. 
     *  That means the first element in the Vector corresponds to the last item selected.</p>
     *  
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedIndices():Vector.<int>
    {
        return _selectedIndices;
    }
    
    /**
     *  @private
     */
    public function set selectedIndices(value:Vector.<int>):void
    {
        if (_proposedSelectedIndices == value)
            return; 
        
        _proposedSelectedIndices = value;
        multipleSelectionChanged = true;  
        invalidateProperties();
    }
    
    //----------------------------------
    //  selectedItems
    //----------------------------------

    [Bindable("change")]
    /**
     *  An Vector of Objects representing the currently selected data items. 
     *  If multiple selection is disabled by setting <code>allowMultipleSelection</code>
     *  to <code>false</code>, and this property is set, the data item 
     *  corresponding to the first item in the Vector is selected.  
     *
     *  <p>If multiple selection is enabled by setting 
     *  <code>allowMultipleSelection</code> to <code>true</code>, this property  
     *  contains a list of the selected items in the reverse order in which they were selected. 
     *  That means the first element in the Vector corresponds to the last item selected.</p>
     * 
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get selectedItems():Vector.<Object>
    {
        var result:Vector.<Object>;
        
        if (selectedIndices)
        {
            result = new Vector.<Object>();
            
            var count:int = selectedIndices.length;
            
            for (var i:int = 0; i < count; i++)
                result[i] = dataProvider.getItemAt(selectedIndices[i]);  
        }
        
        return result;
    }
    
    /**
     *  @private
     */
    public function set selectedItems(value:Vector.<Object>):void
    {
        var indices:Vector.<int>;
        
        if (value)
        {
            indices = new Vector.<int>();
            
            var count:int = value.length;
            
            for (var i:int = 0; i < count; i++)
            {
                // FIXME (dsubrama): What exactly should we do if an 
                //invalid item is in the selectedItems vector? 
                var index:int = dataProvider.getItemIndex(value[i]);
                if (index != -1)
                { 
                    indices.splice(0, 0, index);   
                }
                if (index == -1)
                {
                    indices = new Vector.<int>();
                    break;  
                }
            }
        }
        
        _proposedSelectedIndices = indices;
        multipleSelectionChanged = true;
        invalidateProperties(); 
    }

    //----------------------------------
    //  useVirtualLayout
    //----------------------------------

    /**
     *  @inheritDoc
     *
     *  @default true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function get useVirtualLayout():Boolean
    {
        return super.useVirtualLayout;
    }

    /**
     *  Overrides the inherited default property , it is true for this class.
     * 
     *  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 true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override public function set useVirtualLayout(value:Boolean):void
    {
        super.useVirtualLayout = value;
    }
        
    //--------------------------------------------------------------------------
    //
    //  Overridden Methods
    //
    //--------------------------------------------------------------------------
    
     /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties(); 
        
        if (multipleSelectionChanged)
        {
            commitSelection(); 
            multipleSelectionChanged = false; 
        }
    }
    
    /**
     *  @private
     */
    override public function set hasFocusableChildren(value:Boolean):void
    {
        super.hasFocusableChildren = value;
        if (scroller)
            scroller.hasFocusableChildren = value;
    }
    
    /**
     *  @private
     *  Let ListBase handle single selection and afterwards come in and 
     *  handle multiple selection via the commitMultipleSelection() 
     *  helper method. 
     */
    override protected function commitSelection(dispatchChangedEvents:Boolean = true):Boolean
    {
        var oldSelectedIndex:Number = _selectedIndex;
        var oldCaretIndex:Number = _caretIndex;  
        
        // Ensure that multiple selection is allowed and that proposed 
        // selected indices honors it. For example, in the single 
        // selection case, proposedSelectedIndices should only be a 
        // vector of 1 entry. If its not, we pare it down and select the 
        // first item.  
        if (!allowMultipleSelection && !isEmpty(_proposedSelectedIndices))
        {
            var temp:Vector.<int> = new Vector.<int>(); 
            temp.push(_proposedSelectedIndices[0]); 
            _proposedSelectedIndices = temp;  
        }
        // Keep _proposedSelectedIndex in-sync with multiple selection properties. 
        if (!isEmpty(_proposedSelectedIndices))
           _proposedSelectedIndex = getFirstItemValue(_proposedSelectedIndices); 
        
        // Let ListBase handle the validating and commiting of the single-selection
        // properties.  
        var retVal:Boolean = super.commitSelection(false); 
        
        // If super.commitSelection returns a value of false, 
        // the selection was cancelled, so return false and exit. 
        if (!retVal)
            return false; 
        
        // Now keep _proposedSelectedIndices in-sync with single selection 
        // properties now that the single selection properties have been 
        // comitted.  
        if (selectedIndex > NO_SELECTION)
        {
            if (_proposedSelectedIndices && _proposedSelectedIndices.indexOf(selectedIndex) == -1)
                _proposedSelectedIndices.push(selectedIndex);
        }
        
        // Validate and commit the multiple selection related properties. 
        commitMultipleSelection(); 
        
        // Set the caretIndex based on the current selection 
        setCurrentCaretIndex(selectedIndex);
        
        // And dispatch change and caretChange events so that all of 
        // the bindings update correctly. 
        if (dispatchChangedEvents && retVal)
        {
            var e:IndexChangeEvent = new IndexChangeEvent(IndexChangeEvent.CHANGE);
            e.oldIndex = oldSelectedIndex;
            e.newIndex = _selectedIndex;
            dispatchEvent(e);
            
            e = new IndexChangeEvent(IndexChangeEvent.CARET_CHANGE); 
            e.oldIndex = oldCaretIndex; 
            e.newIndex = caretIndex;
            dispatchEvent(e);    
        }
        
        return retVal; 
    }
    
    /**
     *  @private
     *  Given a new selection interval, figure out which
     *  items are newly added/removed from the selection interval and update
     *  selection properties and view accordingly. 
     */
    protected function commitMultipleSelection():void
    {
        var removedItems:Vector.<int> = new Vector.<int>();
        var addedItems:Vector.<int> = new Vector.<int>();
        var i:int;
        var count:int;
        
        if (!isEmpty(_selectedIndices) && !isEmpty(_proposedSelectedIndices))
        {
            // Changing selection, determine which items were added to the 
            // selection interval 
            count = _proposedSelectedIndices.length;
            for (i = 0; i < count; i++)
            {
                if (_selectedIndices.indexOf(_proposedSelectedIndices[i]) < 0)
                    addedItems.push(_proposedSelectedIndices[i]);
            }
            // Then determine which items were removed from the selection 
            // interval 
            count = _selectedIndices.length; 
            for (i = 0; i < count; i++)
            {
                if (_proposedSelectedIndices.indexOf(_selectedIndices[i]) < 0)
                    removedItems.push(_selectedIndices[i]);
            }
        }
        else if (!isEmpty(_selectedIndices))
        {
            // Going to a null selection, remove all
            removedItems = _selectedIndices;
        }
        else if (!isEmpty(_proposedSelectedIndices))
        {
            // Going from a null selection, add all
            addedItems = _proposedSelectedIndices;
        }
         
        // De-select the old items that were selected 
        if (removedItems.length > 0)
        {
            count = removedItems.length;
            for (i = 0; i < count; i++)
            {
                itemSelected(removedItems[i], false);
            }
        }
        
        // Select the new items in the new selection interval 
        if (!isEmpty(_proposedSelectedIndices))
        {
            count = _proposedSelectedIndices.length;
            for (i = 0; i < count; i++)
            {
                itemSelected(_proposedSelectedIndices[i], true);
            }
        }
        
        // Commit the selected indices and put _proposedSelectedIndices
        // back to its default value.  
        _selectedIndices = _proposedSelectedIndices;
        _proposedSelectedIndices = new Vector.<int>();
    }
    
    /**
     *  @private
     */
    override protected function itemSelected(index:int, selected:Boolean):void
    {
        super.itemSelected(index, selected);
        
        var renderer:Object = dataGroup ? dataGroup.getElementAt(index) : null;
        
        if (renderer is IItemRenderer)
        {
            IItemRenderer(renderer).selected = selected;
        }
    }
    
    /**
     *  @private 
     */
    override protected function itemShowingCaret(index:int, showsCaret:Boolean):void
    {
        super.itemShowingCaret(index, showsCaret); 
        
        var renderer:Object = dataGroup ? dataGroup.getElementAt(index) : null;
        
        if (renderer is IItemRenderer)
        {
            IItemRenderer(renderer).showsCaret = showsCaret;
        }
    }
    
    /**
     *  @private
     */
    override mx_internal function isItemIndexSelected(index:int):Boolean
    {
        if (allowMultipleSelection && (selectedIndices != null))
            return selectedIndices.indexOf(index) != -1;
        
        return index == selectedIndex;
    }
    
    /**
     *  @private
     */
    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);
        if (instance == dataGroup)
        {
            dataGroup.addEventListener(
                RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler);
            dataGroup.addEventListener(
                RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler);
        }
        if (instance == scroller)
            scroller.hasFocusableChildren = hasFocusableChildren;
    }

    /**
     *  @private
     */
    override protected function partRemoved(partName:String, instance:Object):void
    {
        if (instance == dataGroup)
        {
            dataGroup.removeEventListener(
                RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler);
            dataGroup.removeEventListener(
                RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler);
        }
        
        super.partRemoved(partName, instance);
    }
    
    /**
     *  @private
     *  Called when an item has been added to this component.
     */
    override protected function itemAdded(index:int):void
    {
        adjustSelection(index, true); 
    }
    
    /**
     *  @private
     *  Called when an item has been removed from this component.
     */
    override protected function itemRemoved(index:int):void
    {
        adjustSelection(index, false);        
    }
    
    //--------------------------------------------------------------------------
    //
    //  Private Methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Returns the index of the last selected item. In single 
     *  selection, this is just selectedIndex. In multiple 
     *  selection, this is the index of the first selected item.  
     */
    private function getLastSelectedIndex():int
    {
        if (selectedIndices && selectedIndices.length > 0)
            return selectedIndices[selectedIndices.length - 1]; 
        else 
            return 0; 
    }
    
    /**
     *  @private
     *  Given a Vector, returns the value of the first item, 
     *  or -1 if there are no items in the Vector; 
     */
    private function getFirstItemValue(v:Vector.<int>):int
    {
        if (v && v.length > 0)
            return v[0]; 
        else 
            return -1; 
    }
    
    /**
     *  @private
     *  Returns true if v is null or an empty Vector.
     */
    private function isEmpty(v:Vector.<int>):Boolean
    {
        return v == null || v.length == 0;
    }
    
    /**
     *  @private
     *  Taking into account which modifier keys were clicked, the new
     *  selectedIndices interval is calculated. 
     */
    private function calculateSelectedIndicesInterval(index:int, shiftKey:Boolean, ctrlKey:Boolean):Vector.<int>
    {
        var i:int; 
        var interval:Vector.<int> = new Vector.<int>();  
        
        if (!shiftKey)
        {
            if (ctrlKey)
            {
                if (!isEmpty(selectedIndices))
                {
                    // Quick check to see if selectedIndices had only one selected item
                    // and that item was de-selected
                    if (selectedIndices.length == 1 && (selectedIndices[0] == index))
                    {
                        // We need to respect requireSelection 
                        if (!requireSelection)
                            return interval; 
                        else 
                        {
                            interval.splice(0, 0, selectedIndices[0]); 
                            return interval; 
                        }
                    }
                    else
                    {
                        // Go through and see if the index passed in was in the 
                        // selection model. If so, leave it out when constructing
                        // the new interval so it is de-selected. 
                        var found:Boolean = false; 
                        for (i = 0; i < _selectedIndices.length; i++)
                        {
                            if (_selectedIndices[i] == index)
                                found = true; 
                            else if (_selectedIndices[i] != index)
                                interval.splice(0, 0, _selectedIndices[i]);
                        }
                        if (!found)
                        {
                            // Nothing from the selection model was de-selected. 
                            // Instead, the Ctrl key was held down and we're doing a  
                            // new add. 
                            interval.splice(0, 0, index);   
                        }
                        return interval; 
                    } 
                }
                // Ctrl+click with no previously selected items 
                else
                { 
                    interval.splice(0, 0, index); 
                    return interval; 
            }
            }
            // A single item was newly selected, add that to the selection interval.  
            else 
            { 
                interval.splice(0, 0, index); 
                return interval; 
        }
        }
        else // shiftKey
        {
            // A contiguous selection action has occurred. Figure out which new 
            // indices to add to the selection interval and return that. 
            var start:int = (!isEmpty(selectedIndices)) ? selectedIndices[selectedIndices.length - 1] : 0; 
            var end:int = index; 
            if (start < end)
            {
                for (i = start; i <= end; i++)
                {
                    interval.splice(0, 0, i); 
                }
            }
            else 
            {
                for (i = start; i >= end; i--)
                {
                    interval.splice(0, 0, i); 
                }
            }
            return interval; 
        }
    }
    
    //--------------------------------------------------------------------------
    //
    //  Drag methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  The default handler for the <code>dragStart</code> event.
     *
     *  @param event The DragEvent object.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dragStartHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;
        
        var dragSource:DragSource = new DragSource();
        addDragData(dragSource);
        DragManager.doDrag(this, 
                           dragSource, 
                           event, 
                           createDragIndicator(), 
                           0 /*xOffset*/, 
                           0 /*yOffset*/, 
                           0.8 /*imageAlpha*/, 
                           dragMoveEnabled);
    }
    
    /**
     *  @private
     *  Used to sort the selected indices during drag and drop operations.
     */
    private function compareValues(a:int, b:int):int
    {
        return a - b;
    } 
    
    /**
     *  Handles <code>DragEvent.DRAG_COMPLETE</code> events.  This method
     *  removes the items from the data provider.
     *
     *  @param event The DragEvent object.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    protected function dragCompleteHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;
        
        // Remove the dragged items only if they were drag moved to
        // a different list. If the items were drag moved to this
        // list, the reordering was already handles in the 
        // DragEvent.DRAG_DROP listener.
        if (!dragMoveEnabled ||
            event.action != DragManager.MOVE || 
            event.relatedObject == this)
            return;
        
        // Clear the selection, but remember which items were moved
        var movedIndices:Vector.<int> = selectedIndices;
        selectedIndices = new Vector.<int>();
        
        // Remove the moved items
        movedIndices.sort(compareValues);
        var count:int = movedIndices.length;
        for (var i:int = count - 1; i >= 0; i--)
        {
            dataProvider.removeItemAt(movedIndices[i]);
        }
    }
    
    /**
     *  @private
     *
     *  Gets an instance of a class that displays the visuals
     *  during a drag and drop operation.
     */
    private function createDragIndicator():IFlexDisplayObject
    {
        var dragIndicator:IFlexDisplayObject;
        var dragIndicatorClass:Class = Class(getStyle("dragIndicatorClass"));
        if (dragIndicatorClass)
        {
            dragIndicator = new dragIndicatorClass();
            if (dragIndicator is IVisualElement)
                IVisualElement(dragIndicator).owner = this;
        }
        
        return dragIndicator;
    }
    
    /**
     *  Adds the selected items to the DragSource object as part of
     *  a drag-and-drop operation.
     *  Override this method to add other data to the drag source.
     * 
     *  @param ds The DragSource object to which to add the data.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function addDragData(dragSource:DragSource):void
    {
        dragSource.addHandler(copySelectedItemsForDragDrop, "orderedItems");
        
        // Calculate the index of the focus item within the vector
        // of ordered items returned for the "orderedItems" format.
        var caretIndex:int = 0;
        var draggedIndices:Vector.<int> = selectedIndices;
        var count:int = draggedIndices.length;
        for (var i:int = 0; i < count; i++)
        {
            if (mouseDownIndex > draggedIndices[i])
                caretIndex++;
        }
        dragSource.addData(caretIndex, "orderedItemsCaretIndex");
    }

    /**
     *  @private
     */
    private function copySelectedItemsForDragDrop():Vector.<Object>
    {
        // Copy the vector so that we don't modify the original
         // since selectedIndices returns a reference.
        var draggedIndices:Vector.<int> = selectedIndices.slice(0, selectedIndices.length);
        var result:Vector.<Object> = new Vector.<Object>(draggedIndices.length);

        // Sort in the order of the data source
        draggedIndices.sort(compareValues);
        
        // Copy the items
        var count:int = draggedIndices.length;
        for (var i:int = 0; i < count; i++)
            result[i] = dataProvider.getItemAt(draggedIndices[i]);  
        return result;
    }
    
    /**
     *  Handles <code>MouseEvent.MOUSE_DOWN</code> events from any of the 
     *  item renderers. This method handles the updating and commitment 
     *  of selection as well as remembers the mouse down point and
     *  attaches <code>MouseEvent.MOUSE_MOVE</code> and
     *  <code>MouseEvent.MOUSE_UP</code> listeners in order to handle
     *  drag gestures.
{'fraction_non_alphanumeric': 0.1335361470527829, 'fraction_numerical': 0.006649209283220374, 'mean_word_length': 1.6537112102735294, 'pattern_counts': {'":': 0, '<': 279, '<?xml version=': 0, '>': 267, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6341532', 'n_tokens_mistral': 20682, 'n_tokens_neox': 19976, 'n_words': 5672}