blob: 2fc507e96fd1ca29518fbdf7b278b291fc4f7d4a [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.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 generateMXMLInstances(document:Object, data:Array, recursive:Boolean = true):void
{
// in theory, creationpolicy gets applied later
super.generateMXMLInstances(document, data, false);
}
/**
* @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
{
if (isValidChild(item as DisplayObject))
return getChildIndex(item as DisplayObject);
else
return -1;
}
private function isValidChild(child:DisplayObject):Boolean {
for (var i:int = 0; i < numChildren; i++)
{
if (getChildAt(i) == child)
return true;
}
return false;
}
/**
* @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 removeItem calls removeChild
*/
public function removeItem(item:Object):Boolean
{
var displayObject:DisplayObject = removeChild(item as DisplayObject);
return displayObject != null;
}
/**
* @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;
}
}
}