////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2003-2007 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 mx.containers
{

import flash.display.DisplayObject;
import flash.events.Event;

import mx.automation.IAutomationObject;
import mx.core.Container;
import mx.core.ContainerCreationPolicy;
import mx.core.EdgeMetrics;
import mx.core.IInvalidating;
import mx.core.INavigatorContent;
import mx.core.ISelectableList;
import mx.core.IUIComponent;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.Effect;
import mx.effects.EffectManager;
import mx.events.ChildExistenceChangedEvent;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.EffectEvent;
import mx.events.FlexEvent;
import mx.events.IndexChangedEvent;
import mx.events.PropertyChangeEvent;
import mx.geom.RoundedRectangle;
import mx.managers.HistoryManager;
import mx.managers.IHistoryManagerClient;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when the selected child container changes.
 *
 *  @eventType mx.events.IndexChangedEvent.CHANGE
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Event(name="change", type="mx.events.IndexChangedEvent")]

//--------------------------------------
//  Styles
//--------------------------------------

include "../styles/metadata/GapStyles.as"

/**
 *  Number of pixels between the container's bottom border and its content area.
 *  The default value is 0.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]

/**
 *  Number of pixels between the container's top border and its content area.
 *  The default value is 0.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]

//--------------------------------------
//  Excluded APIs
//--------------------------------------

[Exclude(name="autoLayout", kind="property")]
[Exclude(name="defaultButton", kind="property")]
[Exclude(name="horizontalLineScrollSize", kind="property")]
[Exclude(name="horizontalPageScrollSize", kind="property")]
[Exclude(name="horizontalScrollBar", kind="property")]
[Exclude(name="horizontalScrollPolicy", kind="property")]
[Exclude(name="horizontalScrollPosition", kind="property")]
[Exclude(name="maxHorizontalScrollPosition", kind="property")]
[Exclude(name="maxVerticalScrollPosition", kind="property")]
[Exclude(name="verticalLineScrollSize", kind="property")]
[Exclude(name="verticalPageScrollSize", kind="property")]
[Exclude(name="verticalScrollBar", kind="property")]
[Exclude(name="verticalScrollPolicy", kind="property")]
[Exclude(name="verticalScrollPosition", kind="property")]

[Exclude(name="focusIn", kind="event")]
[Exclude(name="focusOut", kind="event")]
[Exclude(name="scroll", kind="event")]

[Exclude(name="focusBlendMode", kind="style")]
[Exclude(name="focusSkin", kind="style")]
[Exclude(name="focusThickness", kind="style")]
[Exclude(name="horizontalScrollBarStyleName", kind="style")]
[Exclude(name="verticalScrollBarStyleName", kind="style")]

[Exclude(name="focusInEffect", kind="effect")]
[Exclude(name="focusOutEffect", kind="effect")]

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

[IconFile("ViewStack.png")]

/**
 *  An MX ViewStack navigator container consists of a collection of child
 *  containers stacked on top of each other, where only one child
 *  at a time is visible.
 *  When a different child container is selected, it seems to replace
 *  the old one because it appears in the same location.
 *  However, the old child container still exists; it is just invisible.
 * 
 *  <p><b>Note:</b> The direct children of an MX navigator container must be 
 *  MX containers, either MX layout or MX navigator containers, 
 *  or the Spark NavigatorContent container. 
 *  You cannot directly nest a control or a Spark container 
 *  other than the Spark NavigatorContent container within a navigator; 
 *  they must be children of an child MX container.</p>
 *
 *  <p>A ViewStack container does not provide a user interface
 *  for selecting which child container is currently visible.
 *  Typically, you set its <code>selectedIndex</code> or
 *  <code>selectedChild</code> property in ActionScript in response to
 *  some user action. 
 *  Alternately, you can associate an MX LinkBar, TabBar, ButtonBar, or ToggleButtonBar
 *  control or a Spark ButtonBar control with a ViewStack container to provide a navigation interface.
 *  To do so, specify the ViewStack container as the value of the
 *  <code>dataProvider</code> property of the LinkBar, TabBar or
 *  ToggleButtonBar container.</p>
 *
 *  <p>You might decide to use a more complex navigator container than the
 *  ViewStack container, such as a TabNavigator container or Accordion
 *  container. In addition to having a collection of child containers,
 *  these containers provide their own user interface controls
 *  for navigating between their children.</p>
 *
 *  <p>When you change the currently visible child container, 
 *  you can use the <code>hideEffect</code> property of the container being
 *  hidden and the <code>showEffect</code> property of the newly visible child
 *  container to apply an effect to the child containers.
 *  The ViewStack container waits for the <code>hideEffect</code> of the child
 *  container being hidden  to complete before it reveals the new child
 *  container. 
 *  You can interrupt a currently playing effect if you change the 
 *  <code>selectedIndex</code> property of the ViewStack container 
 *  while an effect is playing.</p>
 *
 *  <p>The ViewStack container has the following default sizing characteristics:</p>
 *     <table class="innertable">
 *        <tr>
 *           <th>Characteristic</th>
 *           <th>Description</th>
 *        </tr>
 *        <tr>
 *           <td>Default size</td>
 *           <td>The width and height of the initial active child.</td>
 *        </tr>
 *        <tr>
 *           <td>Container resizing rules</td>
 *           <td>By default, ViewStack containers are sized only once to fit the size of the 
 *               first child container. They do not resize when you navigate to other child 
 *               containers. To force ViewStack containers to resize when you navigate 
 *               to a different child container, set the resizeToContent property to true.</td>
 *        </tr>
 *        <tr>
 *           <td>Child sizing rules</td>
 *           <td>Children are sized to their default size. If the child is larger than the ViewStack 
 *               container, it is clipped. If the child is smaller than the ViewStack container, 
 *               it is aligned to the upper-left corner of the ViewStack container.</td>
 *        </tr>
 *        <tr>
 *           <td>Default padding</td>
 *           <td>0 pixels for top, bottom, left, and right values.</td>
 *        </tr>
 *     </table>
 *
 *  @mxml
 *
 *  <p>The <code>&lt;mx:ViewStack&gt;</code> tag inherits the
 *  tag attributes of its superclass, with the exception of scrolling-related
 *  attributes, and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;mx:ViewStack
 *    <b>Properties</b>
 *    historyManagementEnabled="false|true"
 *    resizeToContent="false|true"
 *    selectedIndex="0"
 *    
 *    <b>Styles</b>
 *    horizontalGap="8"
 *    paddingBottom="0"
 *    paddingTop="0"
 *    verticalGap="6"
 *    
 *    <b>Events</b>
 *    change="<i>No default</i>"
 *    &gt;
 *      ...
 *      <i>child tags</i>
 *      ...
 *  &lt;/mx:ViewStack&gt;
 *  </pre>
 *
 *  @includeExample examples/ViewStackExample.mxml
 *
 *  @see mx.controls.LinkBar
 *  @see mx.controls.ButtonBar
 *  @see mx.controls.TabBar
 *  @see mx.controls.ToggleButtonBar
 *  @see spark.components.ButtonBar
 *  @see mx.managers.HistoryManager
 *  @see mx.managers.LayoutManager
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class ViewStack extends Container implements IHistoryManagerClient, ISelectableList
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function ViewStack()
    {
        super();
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private var needToInstantiateSelectedChild:Boolean = false;

    /**
     *  @private
     *  This flag gets set when selectedIndex is set.
     *  Later, when measure()
     *  is called, it causes the HistoryManager to save the state.
     */
    private var bSaveState:Boolean = false;

    /**
     *  @private
     *  This flag gets set by loadState().
     *  It prevents the newly restored state from being saved.
     */
    private var bInLoadState:Boolean = false;
    
    /**
     *  @private
     *  True until commitProperties has been called at least once.
     */
    private var firstTime:Boolean = true;

    /**
     *  @private
     *  We'll measure ourselves once and then store the results here
     *  for the lifetime of the ViewStack
     */
    mx_internal var vsMinWidth:Number;
    mx_internal var vsMinHeight:Number;
    mx_internal var vsPreferredWidth:Number;
    mx_internal var vsPreferredHeight:Number;

    /**
     *  @private
     *  Remember which child has an overlay mask, if any.
     *  Used for the dissolve effect.
     */
    private var effectOverlayChild:UIComponent;

    /**
     *  @private
     *  Keep track of the overlay's targetArea
     *  Used for the dissolve effect.
     */
    private var effectOverlayTargetArea:RoundedRectangle;

    /**
     *  @private
     *  Store the last selectedIndex
     */
    private var lastIndex:int = -1;

    /**
     *  @private
     *  Whether a change event has to be dispatched in commitProperties()
     */
    private var dispatchChangeEventPending:Boolean = false;

    /**
     *  @private
     *  If we're in the middle of adding a child
     */
    private var addingChildren:Boolean = false;
    
    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  autoLayout
    //----------------------------------

    /**
     *  @private
     *  autoLayout is always true for ViewStack.
     */
    override public function get autoLayout():Boolean
    {
        return true;
    }

    /**
     *  @private
     *  autoLayout is always true for ViewStack
     *  and can't be changed by this setter.
     *
     *  We can probably find a way to make autoLayout work with Accordion
     *  and ViewStack, but right now there are problems if deferred
     *  instantiation runs at the same time as an effect. (Bug 79174)
     */
    override public function set autoLayout(value:Boolean):void
    {
    }

    //----------------------------------
    //  horizontalScrollPolicy
    //----------------------------------

    [Inspectable(environment="none")]

    /**
     *  @private
     *  horizontalScrollPolicy is always OFF for ViewStack.
     */
    override public function get horizontalScrollPolicy():String
    {
        return ScrollPolicy.OFF;
    }

    /**
     *  @private
     *  horizontalScrollPolicy is always OFF for ViewStack
     *  and can't be changed by this setter.
     */
    override public function set horizontalScrollPolicy(value:String):void
    {
    }

    //----------------------------------
    //  verticalScrollPolicy
    //----------------------------------

    [Inspectable(environment="none")]

    /**
     *  @private
     *  verticalScrollPolicy is always OFF for ViewStack.
     */
    override public function get verticalScrollPolicy():String
    {
        return ScrollPolicy.OFF;
    }

    /**
     *  @private
     *  verticalScrollPolicy is always OFF for ViewStack
     *  and can't be changed by this setter.
     */
    override public function set verticalScrollPolicy(value:String):void
    {
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  contentHeight
    //----------------------------------

    /**
     *  The height of the area, in pixels, in which content is displayed.
     *  You can override this getter if your content
     *  does not occupy the entire area of the ViewStack container.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get contentHeight():Number
    {
        var vm:EdgeMetrics = viewMetricsAndPadding;
        return unscaledHeight - vm.top - vm.bottom;
    }

    //----------------------------------
    //  contentWidth
    //----------------------------------

    /**
     *  The width of the area, in pixels, in which content is displayed.
     *  You can override this getter if your content
     *  does not occupy the entire area of the ViewStack container.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get contentWidth():Number
    {
        var vm:EdgeMetrics = viewMetricsAndPadding;
        return unscaledWidth - vm.left - vm.right;
    }

    //----------------------------------
    //  contentX
    //----------------------------------

    /**
     *  The x coordinate of the area of the ViewStack container
     *  in which content is displayed, in pixels.
     *  The default value is equal to the value of the
     *  <code>paddingLeft</code> style property,
     *  which has a default value of 0.
     *
     *  Override the <code>get()</code> method if you do not want
     *  your content to start layout at x = 0.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get contentX():Number
    {
        return getStyle("paddingLeft");
    }

    //----------------------------------
    //  contentY
    //----------------------------------

    /**
     *  The y coordinate of the area of the ViewStack container
     *  in which content is displayed, in pixels.
     *  The default value is equal to the value of the
     *  <code>paddingTop</code> style property,
     *  which has a default value of 0.
     *
     *  Override the <code>get()</code> method if you do not want
     *  your content to start layout at y = 0.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function get contentY():Number
    {
        return getStyle("paddingTop");
    }

    //----------------------------------
    //  historyManagementEnabled
    //----------------------------------

    /**
     *  @private
     *  Storage for the historyManagementEnabled property.
     */
    mx_internal var _historyManagementEnabled:Boolean = false;

    /**
     *  @private
     */
    private var historyManagementEnabledChanged:Boolean = false;

    [Inspectable(defaultValue="true")]

    /**
     *  If <code>true</code>, enables history management
     *  within this ViewStack container.
     *  As the user navigates from one child to another,
     *  the browser remembers which children were visited.
     *  The user can then click the browser's Back and Forward buttons
     *  to move through this navigation history.
     *
     *  @default false
     *
     *  @see mx.managers.HistoryManager
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get historyManagementEnabled():Boolean
    {
        return _historyManagementEnabled;
    }

    /**
     *  @private
     */
    public function set historyManagementEnabled(value:Boolean):void
    {
        if (value != _historyManagementEnabled)
        {
            _historyManagementEnabled = value;
            historyManagementEnabledChanged = true;

            invalidateProperties();
        }
    }

    //----------------------------------
    //  resizeToContent
    //----------------------------------

    /**
     *  @private
     *  Storage for the resizeToContent property.
     */
    private var _resizeToContent:Boolean = false;

    /**
     *  If <code>true</code>, the ViewStack container automatically
     *  resizes to the size of its current child.
     *
     *  @default false
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get resizeToContent():Boolean
    {
        return _resizeToContent;
    }

    /**
     *  @private
     */
    public function set resizeToContent(value:Boolean):void
    {
        if (value != _resizeToContent)
        {
            _resizeToContent = value;

            if (value)
                invalidateSize();
        }
    }

    //----------------------------------
    //  selectedChild
    //----------------------------------

    [Bindable("valueCommit")]
    [Bindable("creationComplete")]

    /**
     *  A reference to the currently visible child container.
     *  The default is a reference to the first child.
     *  If there are no children, this property is <code>null</code>.
     * 
     *  <p><strong>Note:</strong> You can only set this property in an
     *  ActionScript statement, not in MXML.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get selectedChild():INavigatorContent
    {
        if (selectedIndex == -1)
            return null;

        return INavigatorContent(getChildAt(selectedIndex));
    }

    /**
     *  @private
     */
    public function set selectedChild(
                            value:INavigatorContent):void
    {
        var newIndex:int = getChildIndex(DisplayObject(value));

        if (newIndex >= 0 && newIndex < numChildren)
            selectedIndex = newIndex;
    }

    //----------------------------------
    //  selectedIndex
    //----------------------------------

    /**
     *  @private
     *  Storage for the selectedIndex property.
     */
    private var _selectedIndex:int = -1;

    /**
     *  @private
     */
    private var proposedSelectedIndex:int = -1;

    /**
     *  @private
     */
    private var initialSelectedIndex:int = -1;

    [Bindable("change")]
    [Bindable("valueCommit")]
    [Bindable("creationComplete")]
    [Inspectable(category="General")]

    /**
     *  The zero-based index of the currently visible child container.
     *  Child indexes are in the range 0, 1, 2, ..., n - 1,
     *  where <i>n</i> is the number of children.
     *  The default value is 0, corresponding to the first child.
     *  If there are no children, the value of this property is <code>-1</code>.
     * 
     *  <p><strong>Note:</strong> When you add a new child to a ViewStack 
     *  container, the <code>selectedIndex</code> property is automatically 
     *  adjusted, if necessary, so that the selected child remains selected.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get selectedIndex():int
    {
        return proposedSelectedIndex == -1 ?
               _selectedIndex :
               proposedSelectedIndex;
    }

    /**
     *  @private
     */
    public function set selectedIndex(value:int):void
    {
        // Bail if the index isn't changing.
        if (value == selectedIndex)
            return;
        
        // ignore, probably coming from tabbar
        if (addingChildren)
            return;

        // Propose the specified value as the new value for selectedIndex.
        // It gets applied later when measure() calls commitSelectedIndex().
        // The proposed value can be "out of range", because the children
        // may not have been created yet, so the range check is handled
        // in commitSelectedIndex(), not here. Other calls to this setter
        // can change the proposed index before it is committed. Also,
        // childAddHandler() proposes a value of 0 when it creates the first
        // child, if no value has yet been proposed.
        proposedSelectedIndex = value;
        invalidateProperties();

        // Set a flag which will cause the HistoryManager to save state
        // the next time measure() is called.
        if (historyManagementEnabled && _selectedIndex != -1 && !bInLoadState)
            bSaveState = true;

        dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: UIComponent
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();

        if (historyManagementEnabledChanged)
        {
            if (historyManagementEnabled)
                HistoryManager.register(this);
            else
                HistoryManager.unregister(this);

            historyManagementEnabledChanged = false;
        }

        if (proposedSelectedIndex != -1)
        {
            commitSelectedIndex(proposedSelectedIndex);
            proposedSelectedIndex = -1;
        }

        if (needToInstantiateSelectedChild)
        {
            instantiateSelectedChild();
            needToInstantiateSelectedChild = false;
        }

        // Dispatch the change event only after the child has been
        // instantiated.
        if (dispatchChangeEventPending)
        {
            dispatchChangeEvent(lastIndex, selectedIndex);
            dispatchChangeEventPending = false;
        }
        
        if (firstTime)
        {
            firstTime = false;
            
            // Add "addedToStage" and "removedFromStage" listeners so we can 
            // register/un-register from the history manager when this component
            // is added or removed from the display list.
            addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler, false, 0, true);
            addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler, false, 0, true);
        }

    }

    /**
     *  Calculates the default sizes and minimum and maximum values of the
     *  ViewStack container.
     *  For more information about the <code>measure()</code> method,
     *  see the <code>UIComponent.measure()</code> method.
     *
     *  <p>The default size of a ViewStack container is the default size
     *  of its currently selected child, plus the padding and borders.
     *  If the ViewStack container has no children, its default size
     *  is just large enough for its padding and borders.</p>
     *
     *  <p>The minimum size of a ViewStack container is the minimum size
     *  of its currently selected child, plus the padding and borders.
     *  If the ViewStack container has no children, its minimum size
     *  is just large enough for its padding and borders.</p>
     *
     *  <p>This method does not change the maximum size of a ViewStack
     *  container - it remains unbounded.</p>
     * 
     *  @see mx.core.UIComponent#measure()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override protected function measure():void
    {
        super.measure();

        // A ViewStack measures itself based only on its selectedChild.
        // The minimum, maximum, and preferred sizes are those of the
        // selected child, plus the borders and margins.
        var minWidth:Number = 0;
        var minHeight:Number = 0;
        var preferredWidth:Number = 0;
        var preferredHeight:Number = 0;

        // Only measure once.  Thereafter, we'll just use cached values.
        //
        // We need to copy the cached values into the measured fields
        // again to handle the case where scaleX or scaleY is not 1.0.
        // When the ViewStack is zoomed, code in UIComponent.measureSizes
        // scales the measuredWidth/Height values every time that
        // measureSizes is called.  (bug 100749)
        if (vsPreferredWidth && !_resizeToContent)
        {
            measuredMinWidth = vsMinWidth;
            measuredMinHeight = vsMinHeight;
            measuredWidth = vsPreferredWidth;
            measuredHeight = vsPreferredHeight;
            return;
        }

        if (numChildren > 0 && selectedIndex != -1)
        {
            var child:UIComponent =
                UIComponent(getChildAt(selectedIndex));

            minWidth = child.minWidth;
            preferredWidth = child.getExplicitOrMeasuredWidth();

            minHeight = child.minHeight;
            preferredHeight = child.getExplicitOrMeasuredHeight();
        }

        var vm:EdgeMetrics = viewMetricsAndPadding;

        var wPadding:Number = vm.left + vm.right;
        minWidth += wPadding;
        preferredWidth += wPadding;

        var hPadding:Number = vm.top + vm.bottom;
        minHeight += hPadding;
        preferredHeight += hPadding;

        measuredMinWidth = minWidth;
        measuredMinHeight = minHeight;
        measuredWidth = preferredWidth;
        measuredHeight = preferredHeight;

        // If we're called before instantiateSelectedChild, then bail.
        // We'll be called again later (instantiateSelectedChild calls
        // invalidateSize), and we don't want to load values into the
        // cache until we're fully initialized.  (bug 102639)
        // This check was moved from the beginning of this function to
        // here to fix bug 103665.
        if (selectedChild && INavigatorContent(selectedChild).deferredContentCreated == false)
            return;

        // Don't remember sizes if we don't have any children
        if (numChildren == 0)
            return;

        vsMinWidth = minWidth;
        vsMinHeight = minHeight;
        vsPreferredWidth = preferredWidth;
        vsPreferredHeight = preferredHeight;
    }

    /**
     *  Responds to size changes by setting the positions and sizes
     *  of this container's children.
     *  For more information about the <code>updateDisplayList()</code> method,
     *  see the <code>UIComponent.updateDisplayList()</code> method.
     *
     *  <p>Only one of its children is visible at a time, therefore,
     *  a ViewStack container positions and sizes only that child.</p>
     *
     *  <p>The selected child is positioned in the ViewStack container's
     *  upper-left corner, and allows for the ViewStack container's
     *  padding and borders. </p>
     *
     *  <p>If the selected child has a percentage <code>width</code> or
     *  <code>height</code> value, it is resized in that direction
     *  to fill the specified percentage of the ViewStack container's
     *  content area (i.e., the region inside its padding).</p>
     *
     *  @param unscaledWidth Specifies the width of the component, in pixels,
     *  in the component's coordinates, regardless of the value of the
     *  <code>scaleX</code> property of the component.
     *
     *  @param unscaledHeight Specifies the height of the component, in pixels,
     *  in the component's coordinates, regardless of the value of the
     *  <code>scaleY</code> property of the component.
     * 
     *  @see mx.core.UIComponent#updateDisplayList()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);

        var nChildren:int = numChildren;
        var w:Number = contentWidth;
        var h:Number = contentHeight;
        var left:Number = contentX;
        var top:Number = contentY;

        // Stretch the selectedIndex to fill our size
        if (selectedIndex != -1)
        {
            var child:UIComponent =
                UIComponent(getChildAt(selectedIndex));

            var newWidth:Number = w;
            var newHeight:Number = h;

            if (!isNaN(child.percentWidth))
            {
                if (newWidth > child.maxWidth)
                    newWidth = child.maxWidth;
            }
            else
            {
                if (newWidth > child.explicitWidth)
                    newWidth = child.explicitWidth;
            }

            if (!isNaN(child.percentHeight))
            {
                if (newHeight > child.maxHeight)
                    newHeight = child.maxHeight;
            }
            else
            {
                if (newHeight > child.explicitHeight)
                    newHeight = child.explicitHeight;
            }

            // Don't send events for the size/move. The set visible below
            if (child.width != newWidth || child.height != newHeight)
                child.setActualSize(newWidth, newHeight);
            if (child.x != left || child.y != top)
                child.move(left, top);

            // Now that the child is properly sized and positioned it
            // can be shown.
            child.visible = true;
        }
    }

    /**
     *  @private
     *  When asked to create an overlay mask, create it on the selected child
     *  instead. That way, the chrome around the edge of the ViewStack
     *  (e.g. the tabs in a TabNavigator) is not occluded by the overlay mask
     *  (Bug 99029)
     */
    override mx_internal function addOverlay(color:uint,
                                        targetArea:RoundedRectangle = null):void
    {
        // As we're switching the currently-selected child, don't
        // allow two children to both have an overlay at the same time.
        // This is done because it makes accounting a headache.  If there's
        // a legitimate reason why two children both need overlays, this
        // restriction could be relaxed.
        if (effectOverlayChild)
            removeOverlay();

        // Remember which child has an overlay, so that we don't inadvertently
        // create an overlay on one child and later try to remove the overlay
        // of another child. (bug 100731)
        effectOverlayChild = (selectedChild as UIComponent);
        if (!effectOverlayChild)
            return;

        effectOverlayColor = color;
        effectOverlayTargetArea = targetArea;

        if (selectedChild &&
            selectedChild.deferredContentCreated == false)
            // No children have been created
        {
            // Wait for the childrenCreated event before creating the overlay
            selectedChild.addEventListener(FlexEvent.INITIALIZE,
                                           initializeHandler);
        }
        else // Children already exist
        {
            initializeHandler(null);
        }
    }

    /**
     *  @private
     */
    override mx_internal function removeOverlay():void
    {
        if (effectOverlayChild)
        {
            UIComponent(effectOverlayChild).removeOverlay();
            effectOverlayChild = null;
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods: Container
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override mx_internal function setActualCreationPolicies(policy:String):void
    {
        super.setActualCreationPolicies(policy);

        // If the creation policy is switched to ContainerCreationPolicy.ALL
        // and our createComponentsFromDescriptors() method has already been
        // called (we've created our children but not all our grandchildren),
        // then create all our grandchildren now. (Bug 99160)
        if (policy == ContainerCreationPolicy.ALL && numChildren > 0)
        {
            for (var i:int = 0; i < numChildren; i++)
            {
                var containerChild:INavigatorContent =
                    getChildAt(i) as INavigatorContent;

                if (containerChild && containerChild.deferredContentCreated == false)
                    containerChild.createDeferredContent();
            }
        }
    }

    /**
     *  @private
     */
    override public function createComponentsFromDescriptors(
                                    recurse:Boolean = true):void
    {
        // The easiest way to handle the ContainerCreationPolicy.ALL policy
        // is to let Container's implementation of
        // createComponentsFromDescriptors() handle it.
        if (actualCreationPolicy == ContainerCreationPolicy.ALL)
        {
            super.createComponentsFromDescriptors();
            return;
        }

        // If the policy is ContainerCreationPolicy.AUTO, ViewStack
        // instantiates its children immediately, but not any grandchildren.
        // The children of the selected child will get created in
        // instantiateSelectedChild().
        // Why not create the grandchildren of the selected child by calling
        //    createComponentFromDescriptor(childDescriptors[i],
        //                                  i == selectedIndex);
        // in the loop below? Because one of this ViewStacks's childDescriptors
        // may be for a Repeater, in which case the following loop over the
        // childDescriptors is not the same as a loop over the children.
        // In particular, selectedIndex is supposed to specify the nth
        // child, not the nth childDescriptor, and the 2nd parameter of
        // createComponentFromDescriptor() should make the recursion happen
        // on the nth child, not the nth childDescriptor.
        var numChildrenBefore:int = numChildren;
        var n:int = childDescriptors ? childDescriptors.length : 0;
        for (var i:int = 0; i < n; i++)
        {
            createComponentFromDescriptor(childDescriptors[i], false);
        }

        numChildrenCreated = numChildren - numChildrenBefore;

        processedDescriptors = true;
        dispatchEvent(new FlexEvent(FlexEvent.CONTENT_CREATION_COMPLETE));
    }

    //--------------------------------------------------------------------------
    //
    //  Methods: IHistoryManagerClient
    //
    //--------------------------------------------------------------------------

    /**
     *  @copy mx.managers.IHistoryManagerClient#saveState()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function saveState():Object
    {
        var index:int = _selectedIndex == -1 ? 0 : _selectedIndex;
        return { selectedIndex: index };
    }

    /**
     *  @copy mx.managers.IHistoryManagerClient#loadState()
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function loadState(state:Object):void
    {
        var newIndex:int = state ? int(state.selectedIndex) : 0;

        if (newIndex == -1)
            newIndex = initialSelectedIndex;

        if (newIndex == -1)
            newIndex = 0;

        if (newIndex != _selectedIndex)
        {
            // When loading a new state, we don't want to
            // save our current state in the history stack.
            bInLoadState = true;
            selectedIndex = newIndex;
            bInLoadState = false;
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Commits the selected index. This function is called during the commit 
     *  properties phase when the <code>selectedIndex</code> or 
     *  <code>selectedItem</code> property changes.
     *
     *  @param newIndex The selected index.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    protected function commitSelectedIndex(newIndex:int):void
    {
        // The selectedIndex must be -1 if there are no children,
        // even if a selectedIndex has been proposed.
        if (numChildren == 0)
        {
            _selectedIndex = -1;
            return;
        }

        // If there are children, ensure that the new index is in bounds.
        if (newIndex < 0)
            newIndex = 0;
        else if (newIndex > numChildren - 1)
            newIndex = numChildren - 1;

        // Stop all currently playing effects
        if (lastIndex != -1 && lastIndex < numChildren)
            UIComponent(getChildAt(lastIndex)).endEffectsStarted();
        
        if (_selectedIndex != -1)
            (selectedChild as UIComponent).endEffectsStarted();

        // Remember the old index.
        lastIndex = _selectedIndex;

        // Bail if the index isn't changing.
        if (newIndex == lastIndex)
            return;

        // Commit the new index.
        _selectedIndex = newIndex;

        // Remember our initial selected index so we can
        // restore to our default state when the history
        // manager requests it.
        if (initialSelectedIndex == -1)
            initialSelectedIndex = _selectedIndex;

        // Only dispatch a change event if we're going to and from
        // a valid index
        if (lastIndex != -1 && newIndex != -1)
            dispatchChangeEventPending = true;

        var listenForEffectEnd:Boolean = false;

        if (lastIndex != -1 && lastIndex < numChildren)
        {
            var currentChild:UIComponent = UIComponent(getChildAt(lastIndex));

            currentChild.setVisible(false); // Hide the current child

            if (currentChild.getStyle("hideEffect"))
            {
                var hideEffect:Effect = EffectManager.lastEffectCreated; // This should be the hideEffect

                if (hideEffect)
                {
                    hideEffect.addEventListener(EffectEvent.EFFECT_END, hideEffectEndHandler);
                    listenForEffectEnd = true;
                }
            }
        }

        // If we don't have to wait for a hide effect to finish
        if (!listenForEffectEnd)
            hideEffectEndHandler(null);
    }

    private function hideEffectEndHandler(event:EffectEvent):void
    {
        if (event)
            event.currentTarget.removeEventListener(EffectEvent.EFFECT_END, hideEffectEndHandler);

        // Give any change handlers a chance to act before we
        // instantiate our pane (which eats up all the processing cycles)
        needToInstantiateSelectedChild = true;
        invalidateProperties();

        if (bSaveState)
        {
            HistoryManager.save();
            bSaveState = false;
        }
    }

    /**
     *  @private
     */
    private function instantiateSelectedChild():void
    {
        if (!selectedChild)
            return;

        // Performance optimization: don't call createComponents if we know
        // that createComponents has already been called.
        if (selectedChild && selectedChild.deferredContentCreated == false)
        {
            if (initialized)  // Only listen if the ViewStack has already been initialized.
                selectedChild.addEventListener(FlexEvent.CREATION_COMPLETE,childCreationCompleteHandler);
            selectedChild.createDeferredContent();
        }

        // Do the initial measurement/layout pass for the
        // newly-instantiated descendants.

        if (selectedChild is IInvalidating)
            IInvalidating(selectedChild).invalidateSize();

        invalidateSize();
        invalidateDisplayList();
    }

    /**
     *  @private
     */
    private function dispatchChangeEvent(oldIndex:int, newIndex:int):void
    {
        var event:IndexChangedEvent =
            new IndexChangedEvent(IndexChangedEvent.CHANGE);
        event.oldIndex = oldIndex;
        event.newIndex = newIndex;
        event.relatedObject = getChildAt(newIndex);
        dispatchEvent(event);
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     *  Handles "addedToStage" event
     */
    private function addedToStageHandler(event:Event):void
    {
        if (historyManagementEnabled)
                HistoryManager.register(this);
    }
    
    /**
     *  @private
     *  Handles "removedFromStage" event
     */
    private function removedFromStageHandler(event:Event):void
    {
        HistoryManager.unregister(this);
    }

    /**
     *  @private
     *  Called when we are running a Dissolve effect
     *  and the initialize event has been dispatched
     *  or the children already exist
     */
    private function initializeHandler(event:FlexEvent):void
    {
        effectOverlayChild.removeEventListener(FlexEvent.INITIALIZE,
                                         initializeHandler);

        UIComponent(effectOverlayChild).addOverlay(effectOverlayColor, effectOverlayTargetArea);
    }

    /**
     *  @private
     *  Handles when the new selectedChild has finished being created.
     */
    private function childCreationCompleteHandler(event:FlexEvent):void
    {
        event.target.removeEventListener(FlexEvent.CREATION_COMPLETE,childCreationCompleteHandler);
        event.target.dispatchEvent(new FlexEvent(FlexEvent.SHOW));

    }

    /**
     *  @private
     */
    private function childAddHandler(child:DisplayObject):void
    {
        var index:int = getChildIndex(child);

        if (child is IUIComponent)
        {
            var uiChild:IUIComponent = IUIComponent(child);
            // ViewStack creates all of its children initially invisible.
            // They are made as they become the selected child.
            uiChild.visible = false;
        }
        if (child is INavigatorContent)
        {
            child.addEventListener("labelChanged", navigatorChildChangedHandler);
            child.addEventListener("iconChanged", navigatorChildChangedHandler);
        }

        // If we just created the first child and no selected index has
        // been proposed, then propose this child to be selected.
        if (numChildren == 1 && proposedSelectedIndex == -1)
        {
            _selectedIndex = 0;
            dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
            needToInstantiateSelectedChild = true;
            invalidateProperties();
        } 
        else if (index <= selectedIndex && numChildren > 1 && proposedSelectedIndex == -1)         
        {
            _selectedIndex++;
            dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
        }

        if (child is IAutomationObject)
            IAutomationObject(child).showInAutomationHierarchy = true;
        
    }

    /**
     *  @private
     *  When a child is removed, adjust the selectedIndex such that the current
     *  child remains selected; or if the current child was removed, then the
     *  next (or previous) child gets automatically selected; when the last
     *  remaining child is removed, the selectedIndex is set to -1.
     */
    private function childRemoveHandler(child:DisplayObject, index:int):void
    {
        if (child is INavigatorContent)
        {
            child.removeEventListener("labelChanged", navigatorChildChangedHandler);
            child.removeEventListener("iconChanged", navigatorChildChangedHandler);
        }

        // Handle the simple case.
        if (index > selectedIndex)
            return;

        var currentSelectedIndex:int = selectedIndex;

        // This matches one of the two conditions:
        // 1. a view before the current was deleted, or
        // 2. the current view was deleted and it was also
        //    at the end of the stack.
        // In both cases, we need to decrement selectedIndex.
        if (index < currentSelectedIndex ||
            currentSelectedIndex == numChildren)
        {
            // If the selectedIndex was already 0, it should go to -1.
            // -1 is a special value; in order to avoid runtime errors
            // in various methods, we need to skip the range checking in
            // commitSelectedIndex() and set _selectedIndex explicitly here.
            if (currentSelectedIndex == 0)
            {
                _selectedIndex = -1;
                dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
            }
            else
            {
                _selectedIndex--;
                dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
            }
        }
        else if (index == currentSelectedIndex)
        {
            // If we're deleting the currentSelectedIndex and there is another
            // child after it, it will become the new selected child so we
            // need to make sure it is instantiated.
            needToInstantiateSelectedChild = true;
            invalidateProperties();
        }
    }
  

    /**
     *  @private
     */
    override public function addChildAt(item:DisplayObject, index:int):DisplayObject
    {
        addingChildren = true;
        var obj:DisplayObject = super.addChildAt(item, index);
        internalDispatchEvent(CollectionEventKind.ADD, obj, index);
        childAddHandler(item);
        addingChildren = false;
        return obj;
    }

    /**
     *  @private
     */
    override public function removeChild(item:DisplayObject):DisplayObject
    {
        var index:int = getChildIndex(item);
        var obj:DisplayObject = super.removeChild(item);
        internalDispatchEvent(CollectionEventKind.REMOVE, obj, index);
        childRemoveHandler(item, index);
        return obj;
    }

    /**
     *  @private
     */
    override public function removeAllChildren():void
    {
        super.removeAllChildren();
        internalDispatchEvent(CollectionEventKind.RESET);
    }

    //--------------------------------------------------------------------------
    //
    //  IList Implementation
    //  Viewstack implements IList so it can be plugged into a Spark ButtonBar
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  The IList implementation dispatches change events when 
     *  label or icon properties change.
     */
    private function navigatorChildChangedHandler(event:Event):void
    {
        var pe:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
        pe.source = event.target;
        pe.property = (event.type == "labelChanged") ? "label" : "icon";

        internalDispatchEvent(CollectionEventKind.UPDATE, pe, getChildIndex(event.target as DisplayObject));
    }

    /**
     *  Dispatches a collection event with the specified information.
     *
     *  @param kind String indicates what the kind property of the event should be
     *  @param item Object reference to the item that was added or removed
     *  @param location int indicating where in the source the item was added.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    private function internalDispatchEvent(kind:String, item:Object = null, location:int = -1):void
    {
        if (hasEventListener(CollectionEvent.COLLECTION_CHANGE))
        {
            var event:CollectionEvent =
                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
            event.kind = kind;
            event.items.push(item);
            event.location = location;
            dispatchEvent(event);
        }

        // now dispatch a complementary PropertyChangeEvent
        if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE) && 
           (kind == CollectionEventKind.ADD || kind == CollectionEventKind.REMOVE))
        {
            var objEvent:PropertyChangeEvent =
                new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
            objEvent.property = location;
            if (kind == CollectionEventKind.ADD)
                objEvent.newValue = item;
            else
                objEvent.oldValue = item;
            dispatchEvent(objEvent);
        }
    }

    /**
     *  @private
     *  IList implementation of length returns numChildren
     */
    public function get length():int
    {
        return numChildren;
    }

    /**
     *  @private
     *  IList implementation of addItem calls addChild
     */
    public function addItem(item:Object):void
    {
        addChild(item as DisplayObject);
    }

    /**
     *  @private
     *  IList implementation of addItemAt calls addChildAt
     */
    public function addItemAt(item:Object, index:int):void
    {
        addChildAt(item as DisplayObject, index);
    }

    /**
     *  @private
     *  IList implementation of getItemAt calls getChildAt
     */
    public function getItemAt(index:int, prefetch:int = 0):Object
    {
        return getChildAt(index);
    }

    /**
     *  @private
     *  IList implementation of getItemIndex calls getChildIndex
     */
    public function getItemIndex(item:Object):int
    {
        return getChildIndex(item as DisplayObject);
    }

    /**
     *  @private
     *  IList implementation of itemUpdated doesn't do anything
     */
    public function itemUpdated(item:Object, property:Object = null, 
                         oldValue:Object = null, 
                         newValue:Object = null):void
    {
        
    }                         

    /**
     *  @private
     *  IList implementation of removeAll calls removeAllChildren
     */
    public function removeAll():void
    {
        removeAllChildren();
    }

    /**
     *  @private
     *  IList implementation of removeItemAt calls removeChildAt
     */
    public function removeItemAt(index:int):Object
    {
        return removeChildAt(index);
    }

    /**
     *  @private
     *  IList implementation of setItemAt removes the old
     *  child and adds the new
     */
    public function setItemAt(item:Object, index:int):Object
    {
        var result:Object = removeChildAt(index);
        addChildAt(item as DisplayObject,index);
        return result;
    }

    /**
     *  @private
     *  IList implementation of toArray returns array of children
     */
    public function toArray():Array 
    {
        var result:Array = [];
        for (var i:int =0;i<numChildren;i++)
        {
            result.push(getChildAt(i));
        }
        return result;
    }
    
    
}

}

