blob: ad0b20aa825b336cfc4b2429aef946f3a79e64ce [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 flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import mx.automation.IAutomationObject;
import mx.containers.accordionClasses.AccordionHeader;
import mx.controls.Button;
import mx.core.ClassFactory;
import mx.core.ComponentDescriptor;
import mx.core.Container;
import mx.core.ContainerCreationPolicy;
import mx.core.EdgeMetrics;
import mx.core.IDataRenderer;
import mx.core.IDeferredContentOwner;
import mx.core.IFactory;
import mx.core.IInvalidating;
import mx.core.INavigatorContent;
import mx.core.IUIComponent;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.Effect;
import mx.effects.Tween;
import mx.events.ChildExistenceChangedEvent;
import mx.events.FlexEvent;
import mx.events.IndexChangedEvent;
import mx.geom.RoundedRectangle;
import mx.managers.HistoryManager;
import mx.managers.IFocusManagerComponent;
import mx.managers.IHistoryManagerClient;
import mx.styles.CSSStyleDeclaration;
use namespace mx_internal;
[RequiresDataBinding(true)]
[IconFile("Accordion.png")]
/**
* Dispatched when the selected child container changes.
*
* @eventType mx.events.IndexChangedEvent.CHANGE
* @helpid 3012
* @tiptext change event
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="change", type="mx.events.IndexChangedEvent")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* Name of the CSS style declaration that specifies styles for the accordion
* headers (tabs).
*
* <p>You can use this class selector to set the values of all the style properties
* of the AccordionHeader class, including <code>fillAlphas</code>, <code>fillColors</code>,
* <code>focusAlpha</code>, <code>focusRounderCorners</code>,
* <code>focusSkin</code>, <code>focusThickness</code>, and <code>selectedFillColors</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="headerStyleName", type="String", inherit="no")]
/**
* Number of pixels between children in the horizontal direction.
* The default value is 8.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="horizontalGap", type="Number", format="Length", inherit="no")]
/**
* Height of each accordion header, in pixels.
* The default value is automatically calculated based on the font styles for
* the header.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="headerHeight", type="Number", format="Length", inherit="no")]
/**
* Duration, in milliseconds, of the animation from one child to another.
*
* The default value for the Halo theme is 250.
* The default value for the Spark theme is 0.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="openDuration", type="Number", format="Time", inherit="no")]
/**
* Tweening function used by the animation from one child to another.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="openEasingFunction", type="Function", inherit="no")]
/**
* Number of pixels between the container's bottom border and its content area.
* The default value is -1, so the bottom border of the last header
* overlaps the Accordion container's bottom border.
*
* @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 -1, so the top border of the first header
* overlaps the Accordion container's top border.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* Color of header text when rolled over.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default value for the Spark theme is <code>0x000000</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* Color of selected text.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default value for the Spark theme is <code>0x000000</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]
/**
* Number of pixels between children in the vertical direction.
* The default value is -1, so the top and bottom borders
* of adjacent headers overlap.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="verticalGap", type="Number", format="Length", inherit="no")]
//--------------------------------------
// Excluded APIs
//--------------------------------------
[Exclude(name="autoLayout", kind="property")]
[Exclude(name="clipContent", 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="scroll", kind="event")]
/*
[Exclude(name="focusBlendMode", kind="style")]
*/
[Exclude(name="horizontalScrollBarStyleName", kind="style")]
[Exclude(name="verticalScrollBarStyleName", kind="style")]
[Exclude(name="focusSkin", kind="style")]
[Exclude(name="focusThickness", kind="style")]
//--------------------------------------
// Other metadata
//--------------------------------------
[DefaultBindingProperty(source="selectedIndex", destination="selectedIndex")]
[DefaultTriggerEvent("change")]
/**
* An MX Accordion navigator container has a collection of child MX containers
* or Spark NavigatorContent containers, but only one of them at a time is visible.
* It creates and manages navigator buttons (accordion headers), which you use
* to navigate between the children.
* There is one navigator button associated with each child container,
* and each navigator button belongs to the Accordion container, not to the child.
* When the user clicks a navigator button, the associated child container
* is displayed.
* The transition to the new child uses an animation to make it clear to
* the user that one child is disappearing and a different one is appearing.
*
* <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>The Accordion container does not extend the ViewStack container,
* but it implements all the properties, methods, styles, and events
* of the ViewStack container, such as <code>selectedIndex</code>
* and <code>selectedChild</code>.</p>
*
* <p>An Accordion 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 currently active child.</td>
* </tr>
* <tr>
* <td>Container resizing rules</td>
* <td>Accordion containers are only sized once to fit the size of the first child container by default.
* They do not resize when you navigate to other child containers by default.
* To force Accordion 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. The child is clipped if it is larger than the Accordion container.
* If the child is smaller than the Accordion container, it is aligned to the upper-left corner of the
* Accordion container.</td>
* </tr>
* <tr>
* <td>Default padding</td>
* <td>-1 pixel for the top, bottom, left, and right values.</td>
* </tr>
* </table>
*
* @mxml
*
* <p>The <code>&lt;mx:Accordion&gt;</code> tag inherits all of the
* tag attributes of its superclass, with the exception of scrolling-related
* attributes, and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:Accordion
* <strong>Properties</strong>
* headerRenderer="<i>IFactory</i>"
* historyManagementEnabled="true|false"
* resizeToContent="false|true"
* selectedChild"<i>A reference to the first child</i>"
* selectedIndex="undefined"
*
* <strong>Styles</strong>
* headerHeight="depends on header font styles"
* headerStyleName="<i>No default</i>"
* horizontalGap="8"
* openDuration="250"
* openEasingFunction="undefined"
* paddingBottom="-1"
* paddingTop="-1"
* textRollOverColor="0xB333C"
* textSelectedColor="0xB333C"
* verticalGap="-1"
*
* <strong>Events</strong>
* change="<i>No default</i>"
* &gt;
* ...
* <i>child tags</i>
* ...
* &lt;/mx:Accordion&gt;
* </pre>
*
* @includeExample examples/AccordionExample.mxml
*
* @see mx.containers.accordionClasses.AccordionHeader
*
* @tiptext Accordion allows for navigation between different child views
* @helpid 3013
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class Accordion extends Container implements IHistoryManagerClient, IFocusManagerComponent
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* Base for all header names (_header0 - _headerN).
*/
private static const HEADER_NAME_BASE:String = "_header";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function Accordion()
{
super();
headerRenderer = new ClassFactory(AccordionHeader);
// Most views can't take focus, but an accordion can.
// However, it draws its own focus indicator on the
// header for the currently selected child view.
// Container() has set tabEnabled false, so we
// have to set it back to true.
tabEnabled = true;
tabFocusEnabled = true;
hasFocusableChildren = true;
// Accordion always clips content, it just handles it by itself
super.clipContent = false;
addEventListener(ChildExistenceChangedEvent.CHILD_ADD, childAddHandler);
addEventListener(ChildExistenceChangedEvent.CHILD_REMOVE, childRemoveHandler);
showInAutomationHierarchy = true;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Is the accordian currently sliding between views?
*/
private var bSliding:Boolean = false;
/**
* @private
*/
private var initialSelectedIndex:int = -1;
/**
* @private
* If true, call HistoryManager.save() when setting currentIndex.
*/
private var bSaveState:Boolean = false;
/**
* @private
*/
private var bInLoadState:Boolean = false;
/**
* @private
*/
private var firstTime:Boolean = true;
/**
* @private
*/
private var showFocusIndicator:Boolean = false;
/**
* @private
* Cached tween properties to speed up tweening calculations.
*/
private var tweenViewMetrics:EdgeMetrics;
private var tweenContentWidth:Number;
private var tweenContentHeight:Number;
private var tweenOldSelectedIndex:int;
private var tweenNewSelectedIndex:int;
private var tween:Tween;
/**
* @private
* We'll measure ourselves once and then store the results here
* for the lifetime of the ViewStack.
*/
private var accMinWidth:Number;
private var accMinHeight:Number;
private var accPreferredWidth:Number;
private var accPreferredHeight:Number;
/**
* @private
* When a child is added or removed, this flag is set true
* and it causes a re-measure.
*/
private var childAddedOrRemoved:Boolean = false;
/**
* @private
* Remember which child has an overlay mask, if any.
*/
private var overlayChild:IUIComponent;
/**
* @private
* Keep track of the overlay's targetArea
*/
private var overlayTargetArea:RoundedRectangle;
/**
* @private
*/
private var layoutStyleChanged:Boolean = false;
/**
* @private
*/
private var currentDissolveEffect:Effect;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// autoLayout
//----------------------------------
// Don't allow user to set autoLayout because
// there are problems if deferred instantiation
// runs at the same time as an effect. (Bug 79174)
[Inspectable(environment="none")]
/**
* @private
*/
override public function get autoLayout():Boolean
{
return true;
}
/**
* @private
*/
override public function set autoLayout(value:Boolean):void
{
}
//----------------------------------
// baselinePosition
//----------------------------------
/**
* @private
* The baselinePosition of an Accordion is calculated
* for the label of the first header.
* If there are no children, a child is temporarily added
* to do the computation.
*/
override public function get baselinePosition():Number
{
if (!validateBaselinePosition())
return NaN;
var isEmpty:Boolean = numChildren == 0;
if (isEmpty)
{
var child0:Container = new Container();
addChild(child0);
validateNow();
}
var header0:Button = getHeaderAt(0);
var result:Number = header0.y + header0.baselinePosition;
if (isEmpty)
{
removeChildAt(0);
validateNow();
}
return result;
}
//----------------------------------
// clipContent
//----------------------------------
// We still need to ensure the clip mask is *never* created for an
// Accordion.
[Inspectable(environment="none")]
/**
* @private
*/
override public function get clipContent():Boolean
{
return true; // Accordion does clip, it just does it itself
}
/**
* @private
*/
override public function set clipContent(value:Boolean):void
{
}
//----------------------------------
// horizontalScrollPolicy
//----------------------------------
[Inspectable(environment="none")]
/**
* @private
*/
override public function get horizontalScrollPolicy():String
{
return ScrollPolicy.OFF;
}
/**
* @private
*/
override public function set horizontalScrollPolicy(value:String):void
{
}
//----------------------------------
// verticalScrollPolicy
//----------------------------------
[Inspectable(environment="none")]
/**
* @private
*/
override public function get verticalScrollPolicy():String
{
return ScrollPolicy.OFF;
}
/**
* @private
*/
override public function set verticalScrollPolicy(value:String):void
{
}
//--------------------------------------------------------------------------
//
// Public properties
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var _focusedIndex:int = -1;
/**
* @private
*/
mx_internal function get focusedIndex():int
{
return _focusedIndex;
}
//----------------------------------
// 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 container.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get contentHeight():Number
{
// Start with the height of the entire accordion.
var contentHeight:Number = unscaledHeight;
// Subtract the heights of the top and bottom borders.
var vm:EdgeMetrics = viewMetricsAndPadding;
contentHeight -= vm.top + vm.bottom;
// Subtract the header heights.
var verticalGap:Number = getStyle("verticalGap");
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
contentHeight -= getHeaderAt(i).height;
if (i > 0)
contentHeight -= verticalGap;
}
return contentHeight;
}
//----------------------------------
// 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 container.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get contentWidth():Number
{
// Start with the width of the entire accordion.
var contentWidth:Number = unscaledWidth;
// Subtract the widths of the left and right borders.
var vm:EdgeMetrics = viewMetricsAndPadding;
contentWidth -= vm.left + vm.right;
contentWidth -= getStyle("paddingLeft") +
getStyle("paddingRight");
return contentWidth;
}
//----------------------------------
// headerRenderer
//----------------------------------
/**
* @private
* Storage for the headerRenderer property.
*/
private var _headerRenderer:IFactory;
[Bindable("headerRendererChanged")]
/**
* A factory used to create the navigation buttons for each child.
* The default value is a factory which creates a
* <code>mx.containers.accordionClasses.AccordionHeader</code>. The
* created object must be a subclass of Button and implement the
* <code>mx.core.IDataRenderer</code> interface. The <code>data</code>
* property is set to the content associated with the header.
*
* @see mx.containers.accordionClasses.AccordionHeader
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get headerRenderer():IFactory
{
return _headerRenderer;
}
/**
* @private
*/
public function set headerRenderer(value:IFactory):void
{
_headerRenderer = value;
dispatchEvent(new Event("headerRendererChanged"));
}
//----------------------------------
// historyManagementEnabled
//----------------------------------
/**
* @private
* Storage for historyManagementEnabled property.
*/
private var _historyManagementEnabled:Boolean = true;
/**
* @private
*/
private var historyManagementEnabledChanged:Boolean = false;
[Inspectable(defaultValue="true")]
/**
* If set to <code>true</code>, this property enables history management
* within this Accordion 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 true
*
* @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;
[Inspectable(defaultValue="false")]
/**
* If set to <code>true</code>, this Accordion 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")]
/**
* A reference to the currently visible child container.
* The default value is a reference to the first child.
* If there are no children, this property is <code>null</code>.
*
* <p><b>Note:</b> You can only set this property in an ActionScript statement,
* not in MXML.</p>
*
* @tiptext Specifies the child view that is currently displayed
* @helpid 3401
*
* @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 and selectedChild properties.
*/
private var _selectedIndex:int = -1;
/**
* @private
*/
private var proposedSelectedIndex:int = -1;
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]
/**
* The zero-based index of the currently visible child container.
* Child indexes are in the range 0, 1, 2, ..., n - 1, where n is the number
* of children.
* The default value is 0, corresponding to the first child.
* If there are no children, this property is <code>-1</code>.
*
* @default 0
*
* @tiptext Specifies the index of the child view that is currently displayed
* @helpid 3402
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedIndex():int
{
if (proposedSelectedIndex != -1)
return proposedSelectedIndex;
return _selectedIndex;
}
/**
* @private
*/
public function set selectedIndex(value:int):void
{
// Bail if new index isn't a number.
if (value == -1)
return;
// Bail if the index isn't changing.
if (value == _selectedIndex)
return;
// Propose the specified value as the new value for selectedIndex.
// It gets applied later when commitProperties() 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 History Manager to save state
// the next time measure() is called.
if (historyManagementEnabled && _selectedIndex != -1 && !bInLoadState)
bSaveState = true;
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @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 createComponents handle it.
if (actualCreationPolicy == ContainerCreationPolicy.ALL)
{
super.createComponentsFromDescriptors();
return;
}
// If the policy is ContainerCreationPolicy.AUTO, Accordion 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 Accordion'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;
if (childDescriptors)
{
var n:int = childDescriptors.length;
for (var i:int = 0; i < n; i++)
{
var descriptor:ComponentDescriptor =
ComponentDescriptor(childDescriptors[i]);
createComponentFromDescriptor(descriptor, false);
}
}
numChildrenCreated = numChildren - numChildrenBefore;
processedDescriptors = true;
}
/**
* @private
*/
override public function setChildIndex(child:DisplayObject,
newIndex:int):void
{
var oldIndex:int = getChildIndex(child);
// Check boundary conditions first
if (oldIndex == -1 || newIndex < 0)
return;
var nChildren:int = numChildren;
if (newIndex >= nChildren)
newIndex = nChildren - 1;
// Next, check for no move
if (newIndex == oldIndex)
return;
// De-select the old selected index header
var oldSelectedHeader:Button = getHeaderAt(selectedIndex);
if (oldSelectedHeader)
{
oldSelectedHeader.selected = false;
drawHeaderFocus(_focusedIndex, false);
}
// Adjust the depths and _childN references of the affected children.
super.setChildIndex(child, newIndex);
// Shuffle the headers
shuffleHeaders(oldIndex, newIndex);
// Select the new selected index header
var newSelectedHeader:Button = getHeaderAt(selectedIndex);
if (newSelectedHeader)
{
newSelectedHeader.selected = true;
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
// Make sure the new selected child is instantiated
instantiateChild(selectedChild);
}
/**
* @private
*/
private function shuffleHeaders(oldIndex:int, newIndex:int):void
{
var i:int;
// Adjust the _headerN references of the affected headers.
// Note: Algorithm is the same as Container.setChildIndex().
var header:Button = getHeaderAt(oldIndex);
if (newIndex < oldIndex)
{
for (i = oldIndex; i > newIndex; i--)
{
getHeaderAt(i - 1).name = HEADER_NAME_BASE + i;
}
}
else
{
for (i = oldIndex; i < newIndex; i++)
{
getHeaderAt(i + 1).name = HEADER_NAME_BASE + i;
}
}
header.name = HEADER_NAME_BASE + newIndex;
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (historyManagementEnabledChanged)
{
if (historyManagementEnabled)
HistoryManager.register(this);
else
HistoryManager.unregister(this);
historyManagementEnabledChanged = false;
}
commitSelectedIndex();
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);
}
}
/**
* @private
*/
override protected function measure():void
{
super.measure();
var minWidth:Number = 0;
var minHeight:Number = 0;
var preferredWidth:Number = 0;
var preferredHeight:Number = 0;
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var headerHeight:Number = getHeaderHeight();
// In general, we only measure once and thereafter use cached values.
// There are three exceptions: when resizeToContent is true,
// when a layout style like headerHeight changes,
// and when a child is added or removed.
//
// 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 Accordion is zoomed, code in UIComponent.measureSizes
// scales the measuredWidth/Height values every time that
// measureSizes is called. (bug 100749)
if (accPreferredWidth && !_resizeToContent &&
!layoutStyleChanged && !childAddedOrRemoved)
{
measuredMinWidth = accMinWidth;
measuredMinHeight = accMinHeight;
measuredWidth = accPreferredWidth;
measuredHeight = accPreferredHeight;
return;
}
layoutStyleChanged = false;
childAddedOrRemoved = false;
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var button:Button = getHeaderAt(i);
var child:IUIComponent = getLayoutChildAt(i);
minWidth = Math.max(minWidth, button.minWidth);
minHeight += headerHeight;
preferredWidth = Math.max(preferredWidth, minWidth);
preferredHeight += headerHeight;
// The headers preferredWidth is messing up the accordion measurement. This may not
// be needed anyway because we're still using the headers minWidth to determine our overall
// minWidth.
if (i == selectedIndex)
{
preferredWidth = Math.max(preferredWidth, child.getExplicitOrMeasuredWidth());
preferredHeight += child.getExplicitOrMeasuredHeight();
minWidth = Math.max(minWidth, child.minWidth);
minHeight += child.minHeight;
}
}
// Add space for borders and margins
var vm:EdgeMetrics = viewMetricsAndPadding;
var widthPadding:Number = vm.left + vm.right;
var heightPadding:Number = vm.top + vm.bottom;
// Need to adjust the widthPadding if paddingLeft and paddingRight are negative numbers
// (see explanation in updateDisplayList())
if (paddingLeft < 0)
widthPadding -= paddingLeft;
if (paddingRight < 0)
widthPadding -= paddingRight;
minWidth += widthPadding;
preferredWidth += widthPadding;
minHeight += heightPadding;
preferredHeight += heightPadding;
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 bugs 103665/104213.
if (selectedChild && INavigatorContent(selectedChild).deferredContentCreated == false)
return;
// Don't remember sizes if we don't have any children
if (numChildren == 0)
return;
accMinWidth = minWidth;
accMinHeight = minHeight;
accPreferredWidth = preferredWidth;
accPreferredHeight = preferredHeight;
}
/**
* @private
* Arranges the layout of the accordion contents.
*
* @tiptext Arranges the layout of the Accordion's contents
* @helpid 3017
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Don't do layout if we're tweening because the tweening
// code is handling it.
if (tween)
return;
// Measure the border.
var bm:EdgeMetrics = borderMetrics;
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var paddingTop:Number = getStyle("paddingTop");
var verticalGap:Number = getStyle("verticalGap");
// Determine the width and height of the content area.
var localContentWidth:Number = calcContentWidth();
var localContentHeight:Number = calcContentHeight();
// Arrange the headers, the content clips,
// based on selectedIndex.
var x:Number = bm.left + paddingLeft;
var y:Number = bm.top + paddingTop;
// Adjustments. These are required since the default halo
// appearance has verticalGap and all margins set to -1
// so the edges of the headers overlap each other and the
// border of the accordion. These overlaps cause problems with
// the content area clipping, so we adjust for them here.
var contentX:Number = x;
var adjContentWidth:Number = localContentWidth;
var headerHeight:Number = getHeaderHeight();
if (paddingLeft < 0)
{
contentX -= paddingLeft;
adjContentWidth += paddingLeft;
}
if (paddingRight < 0)
adjContentWidth += paddingRight;
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var header:Button = getHeaderAt(i);
var content:IUIComponent = getLayoutChildAt(i);
header.move(x, y);
header.setActualSize(localContentWidth, headerHeight);
y += headerHeight;
if (i == selectedIndex)
{
content.move(contentX, y);
content.visible = true;
var contentW:Number = adjContentWidth;
var contentH:Number = localContentHeight;
if (!isNaN(content.percentWidth))
{
if (contentW > content.maxWidth)
contentW = content.maxWidth;
}
else
{
if (contentW > content.getExplicitOrMeasuredWidth())
contentW = content.getExplicitOrMeasuredWidth();
}
if (!isNaN(content.percentHeight))
{
if (contentH > content.maxHeight)
contentH = content.maxHeight;
}
else
{
if (contentH > content.getExplicitOrMeasuredHeight())
contentH = content.getExplicitOrMeasuredHeight();
}
if (content.width != contentW ||
content.height != contentH)
{
content.setActualSize(contentW, contentH);
}
y += localContentHeight;
}
else
{
content.move(contentX, i < selectedIndex
? y : y - localContentHeight);
content.visible = false;
}
y += verticalGap;
}
// Make sure blocker is in front
if (blocker)
rawChildren.setChildIndex(blocker, numChildren - 1);
// refresh the focus rect, the dimensions might have changed.
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
/**
* @private
*/
override mx_internal function setActualCreationPolicies(policy:String):void
{
super.setActualCreationPolicies(policy);
// If the creation policy is switched to ContainerCreationPolicy.ALL and our createComponents
// function 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)
{
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
IDeferredContentOwner(getChildAt(i)).createDeferredContent();
}
}
}
/**
* @private
*/
override protected function focusInHandler(event:FocusEvent):void
{
super.focusInHandler(event);
showFocusIndicator = focusManager.showFocusIndicator;
// When the accordion has focus, the Focus Manager
// should not treat the Enter key as a click on
// the default pushbutton.
if (event.target == this)
focusManager.defaultButtonEnabled = false;
}
/**
* @private
*/
override protected function focusOutHandler(event:FocusEvent):void
{
super.focusOutHandler(event);
showFocusIndicator = false;
if (focusManager && event.target == this)
focusManager.defaultButtonEnabled = true;
}
/**
* @private
*/
override public function drawFocus(isFocused:Boolean):void
{
drawHeaderFocus(_focusedIndex, isFocused);
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
if (!styleProp ||
styleProp == "headerStyleName" ||
styleProp == "styleName")
{
var headerStyleName:Object = getStyle("headerStyleName");
var header:Button;
if (headerStyleName)
{
for (var j:int = 0; j < numChildren; j++)
{
header = getHeaderAt(j);
if (header)
{
header.styleName = headerStyleName;
}
}
}
}
else if (styleManager.isSizeInvalidatingStyle(styleProp))
{
layoutStyleChanged = 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 Accordion (e.g. the
* header buttons) 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 (overlayChild)
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)
overlayChild = selectedChild as IUIComponent;
if (!overlayChild)
return;
effectOverlayColor = color;
overlayTargetArea = 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
* 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
{
UIComponent(overlayChild).addOverlay(effectOverlayColor, overlayTargetArea);
}
/**
* @private
* Handle key down events
*/
override mx_internal function removeOverlay():void
{
if (overlayChild)
{
UIComponent(overlayChild).removeOverlay();
overlayChild = null;
}
}
// -------------------------------------------------------------------------
// StateInterface
// -------------------------------------------------------------------------
/**
* @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;
}
}
//--------------------------------------------------------------------------
//
// Public methods
//
//--------------------------------------------------------------------------
/**
* Returns a reference to the navigator button for a child container.
*
* @param index Zero-based index of the child.
*
* @return Button object representing the navigator button.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getHeaderAt(index:int):Button
{
return Button(rawChildren.getChildByName(HEADER_NAME_BASE + index));
}
/**
* @private
* Returns the height of the header control. All header controls are the same
* height.
*/
private function getHeaderHeight():Number
{
var headerHeight:Number = getStyle("headerHeight");
if (isNaN(headerHeight))
{
headerHeight = 0;
if (numChildren > 0)
headerHeight = getHeaderAt(0).measuredHeight;
}
return headerHeight;
}
//--------------------------------------------------------------------------
//
// Private methods
//
//--------------------------------------------------------------------------
/**
* @private
* Utility method to create the segment header
*/
private function createHeader(content:DisplayObject, i:int):void
{
// Before creating the header, un-select the currently selected
// header. We will be selecting the correct header below.
if (selectedIndex != -1 && getHeaderAt(selectedIndex))
getHeaderAt(selectedIndex).selected = false;
// Create the header.
// Notes:
// 1) An accordion maintains a reference to
// the header for its Nth child as _headerN. These references are
// juggled when children and their headers are re-indexed or
// removed, to ensure that _headerN is always a reference the
// header for the Nth child.
// 2) Always create the header with the index of the last item.
// If needed, the headers will be shuffled below.
var header:Button = Button(headerRenderer.newInstance());
header.name = HEADER_NAME_BASE + (numChildren - 1);
var headerStyleName:String = getStyle("headerStyleName");
if (headerStyleName)
{
header.styleName = headerStyleName;
}
header.addEventListener(MouseEvent.CLICK, headerClickHandler);
IDataRenderer(header).data = content;
if (content is INavigatorContent)
{
var contentContainer:INavigatorContent = INavigatorContent(content);
header.label = contentContainer.label;
if (contentContainer.icon)
header.setStyle("icon", contentContainer.icon);
// If the child has a toolTip, transfer it to the header.
var toolTip:String = (contentContainer as UIComponent).toolTip;
if (toolTip && toolTip != "")
{
header.toolTip = toolTip;
(contentContainer as UIComponent).toolTip = null;
}
}
rawChildren.addChild(header);
// If the newly added child isn't at the end of our child list, shuffle
// the headers accordingly.
if (i != numChildren - 1)
shuffleHeaders(numChildren - 1, i);
// Make sure the correct header is selected
if (selectedIndex != -1 && getHeaderAt(selectedIndex))
getHeaderAt(selectedIndex).selected = true;
}
/**
* @private
*/
private function calcContentWidth():Number
{
// Start with the width of the entire accordion.
var contentWidth:Number = unscaledWidth;
// Subtract the widths of the left and right borders.
var vm:EdgeMetrics = viewMetricsAndPadding;
contentWidth -= vm.left + vm.right;
return contentWidth;
}
/**
* @private
*/
private function calcContentHeight():Number
{
// Start with the height of the entire accordion.
var contentHeight:Number = unscaledHeight;
// Subtract the heights of the top and bottom borders.
var vm:EdgeMetrics = viewMetricsAndPadding;
contentHeight -= vm.top + vm.bottom;
// Subtract the header heights.
var verticalGap:Number = getStyle("verticalGap");
var headerHeight:Number = getHeaderHeight();
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
contentHeight -= headerHeight;
if (i > 0)
contentHeight -= verticalGap;
}
return contentHeight;
}
/**
* @private
*/
private function drawHeaderFocus(headerIndex:int, isFocused:Boolean):void
{
if (headerIndex != -1)
getHeaderAt(headerIndex).drawFocus(isFocused);
}
/**
* @private
*/
private function headerClickHandler(event:Event):void
{
var header:Button = Button(event.currentTarget);
var oldIndex:int = selectedIndex;
// content is placed onto the button so we have to access it via []
selectedChild = INavigatorContent(IDataRenderer(header).data);
var newIndex:int = selectedIndex;
if (oldIndex != newIndex)
dispatchChangeEvent(oldIndex, newIndex, event);
}
/**
* @private
*/
private function commitSelectedIndex():void
{
if (proposedSelectedIndex == -1)
return;
var newIndex:int = proposedSelectedIndex;
proposedSelectedIndex = -1;
// The selectedIndex must be undefined if there are no children,
// even if a selectedIndex has been proposed.
if (numChildren == 0)
{
_selectedIndex = -1;
return;
}
// Ensure that the new index is in bounds.
if (newIndex < 0)
newIndex = 0;
else if (newIndex > numChildren - 1)
newIndex = numChildren - 1;
// Remember the old index.
var oldIndex:int = _selectedIndex;
// Bail if the index isn't changing.
if (newIndex == oldIndex)
return;
// If we are currently playing a Dissolve effect, end it and restart it again
currentDissolveEffect = null;
if (isEffectStarted)
{
var dissolveInstanceClass:Class = Class(systemManager.getDefinitionByName("mx.effects.effectClasses.DissolveInstance"));
for (var i:int = 0; i < _effectsStarted.length; i++)
{
// Avoid referencing the DissolveInstance class directly, so that
// we don't create an unwanted linker dependency.
if (dissolveInstanceClass && _effectsStarted[i] is dissolveInstanceClass)
{
// If we find the dissolve, save a pointer to the parent effect and end the instance
currentDissolveEffect = _effectsStarted[i].effect;
_effectsStarted[i].end();
break;
}
}
}
// Unfocus the old header.
if (_focusedIndex != newIndex)
drawHeaderFocus(_focusedIndex, false);
// Deselect the old header.
if (oldIndex != -1)
getHeaderAt(oldIndex).selected = false;
// 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;
// Select the new header.
getHeaderAt(newIndex).selected = true;
if (_focusedIndex != newIndex)
{
// Focus the new header.
_focusedIndex = newIndex;
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
if (bSaveState)
{
HistoryManager.save();
bSaveState = false;
}
if (getStyle("openDuration") == 0 || oldIndex == -1)
{
// Need to set the new index to be visible here
// in order for effects to work.
IUIComponent(getChildAt(newIndex)).setVisible(true);
// Now that the effects have been triggered, we can hide the
// current view until it is properly sized and positioned below.
IUIComponent(getChildAt(newIndex)).setVisible(false, true);
if (oldIndex != -1)
IUIComponent(getChildAt(oldIndex)).setVisible(false);
instantiateChild(selectedChild);
}
else
{
if (tween)
tween.endTween();
startTween(oldIndex, newIndex);
}
}
/**
* @private
*/
private function instantiateChild(child:INavigatorContent):void
{
// fix for bug#137430
// when the selectedChild index is -1 (invalid value due to any reason)
// selectedContainer will not be valid. Before we proceed
// we need to make sure of its validity.
if (!child)
return;
// Performance optimization: don't call createComponents if we know
// that createComponents has already been called.
if (child && child.deferredContentCreated == false)
child.createDeferredContent();
// Do the initial measurement/layout pass for the newly-instantiated
// descendants.
invalidateSize();
invalidateDisplayList();
if (child is IInvalidating)
IInvalidating(child).invalidateSize();
}
/**
* @private
*/
private function dispatchChangeEvent(oldIndex:int,
newIndex:int,
cause:Event = null):void
{
var indexChangeEvent:IndexChangedEvent =
new IndexChangedEvent(IndexChangedEvent.CHANGE);
indexChangeEvent.oldIndex = oldIndex;
indexChangeEvent.newIndex = newIndex;
indexChangeEvent.relatedObject = getChildAt(newIndex);
indexChangeEvent.triggerEvent = cause;
dispatchEvent(indexChangeEvent);
}
/**
* @private
*/
private function startTween(oldSelectedIndex:int, newSelectedIndex:int):void
{
bSliding = true;
// To improve the animation performance, we set up some invariants
// used in onTweenUpdate. (Some of these, like contentHeight, are
// too slow to recalculate at every tween step.)
tweenViewMetrics = viewMetricsAndPadding;
tweenContentWidth = calcContentWidth();
tweenContentHeight = calcContentHeight();
tweenOldSelectedIndex = oldSelectedIndex;
tweenNewSelectedIndex = newSelectedIndex;
// A single instance of Tween drives the animation.
var openDuration:Number = getStyle("openDuration");
tween = new Tween(this, 0, tweenContentHeight, openDuration);
var easingFunction:Function = getStyle("openEasingFunction") as Function;
if (easingFunction != null)
tween.easingFunction = easingFunction;
// Ideally, all tweening should be managed by the EffectManager. Since
// this tween isn't managed by the EffectManager, we need this alternate
// mechanism to tell the EffectManager that we're tweening. Otherwise, the
// EffectManager might try to play another effect that animates the same
// properties.
if (oldSelectedIndex != -1)
IUIComponent(getChildAt(oldSelectedIndex)).tweeningProperties = ["x", "y", "width", "height"];
IUIComponent(getChildAt(newSelectedIndex)).tweeningProperties = ["x", "y", "width", "height"];
// If the content of the new child hasn't been created yet, set the new child
// to the content width/height. This way any background color will show up
// properly during the animation.
var newSelectedChild:IDeferredContentOwner = IDeferredContentOwner(getChildAt(newSelectedIndex));
if (newSelectedChild.deferredContentCreated == false)
{
var paddingLeft:Number = getStyle("paddingLeft");
var contentX:Number = borderMetrics.left + (paddingLeft > 0 ? paddingLeft : 0);
newSelectedChild.move(contentX, newSelectedChild.y);
newSelectedChild.setActualSize(tweenContentWidth, tweenContentHeight);
}
UIComponent.suspendBackgroundProcessing();
}
/**
* @private
*/
mx_internal function onTweenUpdate(value:Number):void
{
// Fetch the tween invariants we set up in startTween.
var vm:EdgeMetrics = tweenViewMetrics;
var contentWidth:Number = tweenContentWidth;
var contentHeight:Number = tweenContentHeight;
var oldSelectedIndex:int = tweenOldSelectedIndex;
var newSelectedIndex:int = tweenNewSelectedIndex;
// The tweened value is the height of the new content area, which varies
// from 0 to the contentHeight. As the new content area grows, the
// old content area shrinks.
var newContentHeight:Number = value;
var oldContentHeight:Number = contentHeight - value;
// These offsets for the Y position of the content clips make the content
// clips appear to be pushed up and pulled down.
var oldOffset:Number = oldSelectedIndex < newSelectedIndex ?
-newContentHeight :
newContentHeight;
var newOffset:Number = newSelectedIndex > oldSelectedIndex ?
oldContentHeight :
-oldContentHeight;
// Loop over all the headers to arrange them vertically.
// The loop is intentionally over ALL the headers, not just the ones that
// need to move; this makes the animation look equally smooth
// regardless of how many headers are moving.
// We also reposition the two visible content clips.
var y:Number = vm.top;
var verticalGap:Number = getStyle("verticalGap");
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var header:Button = getHeaderAt(i);
var content:UIComponent = UIComponent(getChildAt(i));
header.$y = y;
y += header.height;
if (i == oldSelectedIndex)
{
content.cacheAsBitmap = true;
content.scrollRect = new Rectangle(0, -oldOffset,
contentWidth, contentHeight);
content.visible = true;
y += oldContentHeight;
}
else if (i == newSelectedIndex)
{
content.cacheAsBitmap = true;
content.scrollRect = new Rectangle(0, -newOffset,
contentWidth, contentHeight);
content.visible = true;
y += newContentHeight;
}
y += verticalGap;
}
}
/**
* @private
*/
mx_internal function onTweenEnd(value:Number):void
{
bSliding = false;
var oldSelectedIndex:int = tweenOldSelectedIndex;
var vm:EdgeMetrics = tweenViewMetrics;
var verticalGap:Number = getStyle("verticalGap");
var headerHeight:Number = getHeaderHeight();
var localContentWidth:Number = calcContentWidth();
var localContentHeight:Number = calcContentHeight();
var y:Number = vm.top;
var content:UIComponent;
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var header:Button = getHeaderAt(i);
header.$y = y;
y += headerHeight;
if (i == selectedIndex)
{
content = UIComponent(getChildAt(i));
content.cacheAsBitmap = false;
content.scrollRect = null;
content.visible = true;
y += localContentHeight;
}
y += verticalGap;
}
if (oldSelectedIndex != -1)
{
content = UIComponent(getChildAt(oldSelectedIndex));
content.cacheAsBitmap = false;
content.scrollRect = null;
content.visible = false;
content.tweeningProperties = null;
}
// Delete the temporary tween invariants we set up in startTween.
tweenViewMetrics = null;
tweenContentWidth = NaN;
tweenContentHeight = NaN;
tweenOldSelectedIndex = 0;
tweenNewSelectedIndex = 0;
tween = null;
UIComponent.resumeBackgroundProcessing();
UIComponent(getChildAt(selectedIndex)).tweeningProperties = null;
// If we interrupted a Dissolve effect, restart it here
if (currentDissolveEffect)
{
if (currentDissolveEffect.target != null)
{
currentDissolveEffect.play();
}
else
{
currentDissolveEffect.play([this]);
}
}
// Let the screen render the last frame of the animation before
// we begin instantiating the new child.
callLater(instantiateChild, [selectedChild]);
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
/**
* @private
* Handles "keyDown" event.
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
// Only listen for events that have come from the accordion itself.
if (event.target != this)
return;
var prevValue:int = selectedIndex;
// If rtl layout, need to swap LEFT and RIGHT so correct action
// is done.
var keyCode:uint = mapKeycodeForLayoutDirection(event);
switch (keyCode)
{
case Keyboard.PAGE_DOWN:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = selectedIndex = (selectedIndex < numChildren - 1
? selectedIndex + 1
: 0);
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
dispatchChangeEvent(prevValue, selectedIndex, event);
break;
}
case Keyboard.PAGE_UP:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = selectedIndex = (selectedIndex > 0 ?
selectedIndex - 1 :
numChildren - 1);
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
dispatchChangeEvent(prevValue, selectedIndex, event);
break;
}
case Keyboard.HOME:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = selectedIndex = 0;
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
dispatchChangeEvent(prevValue, selectedIndex, event);
break;
}
case Keyboard.END:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = selectedIndex = numChildren - 1;
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
dispatchChangeEvent(prevValue, selectedIndex, event);
break;
}
case Keyboard.DOWN:
case Keyboard.RIGHT:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = (_focusedIndex < numChildren - 1
? _focusedIndex + 1
: 0);
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
break;
}
case Keyboard.UP:
case Keyboard.LEFT:
{
drawHeaderFocus(_focusedIndex, false);
_focusedIndex = (_focusedIndex > 0 ?
_focusedIndex - 1 :
numChildren - 1);
drawHeaderFocus(_focusedIndex, true);
event.stopPropagation();
break;
}
case Keyboard.SPACE:
case Keyboard.ENTER:
{
event.stopPropagation();
if (_focusedIndex != selectedIndex)
{
selectedIndex = _focusedIndex;
dispatchChangeEvent(prevValue, selectedIndex, event);
}
break;
}
}
}
//--------------------------------------------------------------------------
//
// 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
*/
private function childAddHandler(event:ChildExistenceChangedEvent):void
{
childAddedOrRemoved = true;
var child:DisplayObject = event.relatedObject;
// Accordion creates all of its children initially invisible.
// They are made as they become the selected child.
child.visible = false;
// Create the header associated with this child.
createHeader(child, getChildIndex(child));
// If the child's label or icon changes, Accordion needs to know so that
// the header label can be updated. This event is handled by
// labelChanged().
// note: weak listeners
child.addEventListener("labelChanged", labelChangedHandler, false, 0, true);
child.addEventListener("iconChanged", iconChangedHandler, false, 0, true);
// 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;
// Select the new header.
var newHeader:Button = getHeaderAt(0);
newHeader.selected = true;
// Focus the new header.
_focusedIndex = 0;
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
if (child as IAutomationObject)
IAutomationObject(child).showInAutomationHierarchy = true;
}
/**
* @private
*/
private function childRemoveHandler(event:ChildExistenceChangedEvent):void
{
childAddedOrRemoved = true;
if (numChildren == 0)
return;
var child:DisplayObject = event.relatedObject;
var oldIndex:int = selectedIndex;
var newIndex:int;
var index:int = getChildIndex(child);
// Remove Event Listeners, in case children are referenced elsewhere.
child.removeEventListener("labelChanged", labelChangedHandler);
child.removeEventListener("iconChanged", iconChangedHandler);
var nChildren:int = numChildren - 1;
// number of children remaining after child has been removed
rawChildren.removeChild(getHeaderAt(index));
// Shuffle all higher numbered headers down.
for (var i:int = index; i < nChildren; i++)
{
getHeaderAt(i + 1).name = HEADER_NAME_BASE + i;
}
// If we just deleted the only child, the accordion is now empty,
// and no child is now selected.
if (nChildren == 0)
{
// There's no need to go through all of commitSelectedIndex(),
// and it wouldn't do the right thing, anyway, because
// it bails immediately if numChildren is 0.
newIndex = _focusedIndex = -1;
}
else if (index > selectedIndex)
{
return;
}
// If we deleted a child before the selected child, the
// index of that selected child is now 1 less than it was,
// but the selected child itself hasn't changed.
else if (index < selectedIndex)
{
newIndex = oldIndex - 1;
}
// Now handle the case that we deleted the selected child
// and there is another child that we must select.
else if (index == selectedIndex)
{
// If it was the last child, select the previous one.
// Otherwise, select the next one. This next child now
// has the same index as the one we just deleted,
if (index == nChildren)
{
newIndex = oldIndex - 1;
// if something's selected, instantiate it
if (newIndex != -1)
instantiateChild(INavigatorContent(getChildAt(newIndex)));
}
else
{
newIndex = oldIndex;
// can't do selectedChild because we need to actually do
// newIndex + 1 because currently that's what the index
// of the child is (SDK-12622) since this one isn't
// actually removed from the display list yet
instantiateChild(INavigatorContent(getChildAt(newIndex+1)));
}
// Select the new selected index header.
var newHeader:Button = getHeaderAt(newIndex);
newHeader.selected = true;
}
if (_focusedIndex > index)
{
_focusedIndex--;
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
else if (_focusedIndex == index)
{
if (index == nChildren)
_focusedIndex--;
drawHeaderFocus(_focusedIndex, showFocusIndicator);
}
_selectedIndex = newIndex;
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
/**
* @private
* Handles "labelChanged" event.
*/
private function labelChangedHandler(event:Event):void
{
var child:DisplayObject = DisplayObject(event.target);
var childIndex:int = getChildIndex(child);
var header:Button = getHeaderAt(childIndex);
header.label = INavigatorContent(event.target).label;
}
/**
* @private
* Handles "iconChanged" event.
*/
private function iconChangedHandler(event:Event):void
{
var child:DisplayObject = DisplayObject(event.target);
var childIndex:int = getChildIndex(child);
var header:Button = getHeaderAt(childIndex);
header.setStyle("icon", INavigatorContent(event.target).icon);
//header.icon = Container(event.target).icon;
}
}
}