////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2003-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package flexlib.baseClasses
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
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 flash.utils.getTimer;
import mx.automation.IAutomationObject;
import flexlib.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.FlexVersion;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.managers.IFocusManagerComponent;
import mx.managers.IHistoryManagerClient;
import mx.core.IInvalidating;
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.graphics.RoundedRectangle;
import mx.managers.HistoryManager;
import mx.styles.StyleManager;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleProxy;
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
*/
[Event(name="change", type="mx.events.IndexChangedEvent")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* Specifies the alpha transparency values used for the background fill of components.
* You should set this to an Array of either two or four numbers.
* Elements 0 and 1 specify the start and end values for
* an alpha gradient.
* If elements 2 and 3 exist, they are used instead of elements 0 and 1
* when the component is in a mouse-over state.
* The global default value is [ 0.60, 0.40, 0.75, 0.65 ].
* Some components, such as the ApplicationControlBar container,
* have a different default value. For the ApplicationControlBar container,
* the default value is [ 0.0, 0.0 ].
*/
[Style(name="fillAlphas", type="Array", arrayType="Number", inherit="no", deprecatedReplacement="headerStyleName", deprecatedSince="3.0")]
/**
* Specifies the colors used to tint the background fill of the component.
* You should set this to an Array of either two or four uint values
* that specify RGB colors.
* Elements 0 and 1 specify the start and end values for
* a color gradient.
* If elements 2 and 3 exist, they are used instead of elements 0 and 1
* when the component is in a mouse-over state.
* For a flat-looking control, set the same color for elements 0 and 1
* and for elements 2 and 3,
* The default value is
* [ 0xFFFFFF, 0xCCCCCC, 0xFFFFFF, 0xEEEEEE ].
*
Some components, such as the ApplicationControlBar container,
* have a different default value. For the ApplicationControlBar container,
* the default value is [ 0xFFFFFF, 0xFFFFFF ].
"tl", "tr", "bl"
* and "br".
* For example, to specify that the right side corners should be rounded,
* but the left side corners should be square, use "tr br".
* The cornerRadius style property specifies
* the radius of the rounded corners.
* The default value depends on the component class; if not overridden for
* the class, default value is "tl tr bl br".
*
*/
[Style(name="focusRoundedCorners", type="String", inherit="no", deprecatedReplacement="headerStyleName", deprecatedSince="3.0")]
/**
* Skin used to draw the focus rectangle.
*
* @default mx.skins.halo.HaloFocusRect
*/
[Style(name="focusSkin", type="Class", inherit="no", deprecatedReplacement="headerStyleName", deprecatedSince="3.0")]
/**
* Thickness, in pixels, of the focus rectangle outline.
*
* @default 2
*/
[Style(name="focusThickness", type="Number", format="Length", inherit="no", deprecatedReplacement="headerStyleName", deprecatedSince="3.0")]
/**
* Name of the CSS style declaration that specifies styles for the accordion
* headers (tabs).
*
* You can use this class selector to set the values of all the style properties
* of the AccordionHeader class, including fillAlphas, fillColors,
* focusAlpha, focusRounderCorners,
* focusSkin, focusThickness, and selectedFillColors.
undefined, which means the colors
* are derived from themeColor.
*/
[Style(name="selectedFillColors", type="Array", arrayType="uint", format="Color", inherit="no", deprecatedReplacement="headerStyleName", deprecatedSince="3.0")]
/**
* Color of header text when rolled over.
* The default value is 0x2B333C.
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* Color of selected text.
* The default value is 0x2B333C.
*/
[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.
*/
[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="focusSkin", kind="style")]
[Exclude(name="focusThickness", kind="style")]
*/
[Exclude(name="horizontalScrollBarStyleName", kind="style")]
[Exclude(name="verticalScrollBarStyleName", kind="style")]
//--------------------------------------
// Other metadata
//--------------------------------------
[DefaultBindingProperty(source="selectedIndex", destination="selectedIndex")]
[DefaultTriggerEvent("change")]
/**
* AccordionBase is a copy/paste version of the original Accordion class in the Flex framework.
*
* The only modifications made to this class were to change some properties and * methods from private to protected so we can override them in a subclass.
* *An Accordion navigator container has a collection of child 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.
* *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 selectedIndex
* and selectedChild.
An Accordion container has the following default sizing characteristics:
*| Characteristic | *Description | *
|---|---|
| Default size | *The width and height of the currently active child. | *
| Container resizing rules | *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. | *
| Child sizing rules | *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. | *
| Default padding | *-1 pixel for the top, bottom, left, and right values. | *
The <mx:Accordion> tag inherits all of the
* tag attributes of its superclass, with the exception of scrolling-related
* attributes, and adds the following tag attributes:
* <mx:Accordion * Properties * headerRenderer="IFactory" * historyManagementEnabled="true|false" * resizeToContent="false|true" * selectedIndex="undefined" * * Styles * headerHeight="depends on header font styles" * headerStyleName="No default" * horizontalGap="8" * openDuration="250" * openEasingFunction="undefined" * paddingBottom="-1" * paddingTop="-1" * textRollOverColor="0xB333C" * textSelectedColor="0xB333C" * verticalGap="-1" * * Events * change="No default" * > * ... * child tags * ... * </mx:Accordion> ** * * * @see mx.containers.accordionClasses.AccordionHeader * * @tiptext Accordion allows for navigation between different child views * @helpid 3013 */ public class AccordionBase 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. */ public function AccordionBase() { 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; // 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? */ protected 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 */ protected var showFocusIndicator:Boolean = false; /** * @private * Cached tween properties to speed up tweening calculations. */ protected var tweenViewMetrics:EdgeMetrics; protected var tweenContentWidth:Number; protected var tweenContentHeight:Number; protected var tweenOldSelectedIndex:int; protected var tweenNewSelectedIndex:int; protected var tween:Tween; /** * @private * We'll measure ourselves once and then store the results here * for the lifetime of the ViewStack. */ protected var accMinWidth:Number; protected var accMinHeight:Number; protected var accPreferredWidth:Number; protected 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 */ protected var layoutStyleChanged:Boolean = false; /** * @private */ protected 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 (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0) return super.baselinePosition; 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. */ 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. */ 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 *
mx.containers.accordionClasses.AccordionHeader. The
* created object must be a subclass of Button and implement the
* mx.core.IDataRenderer interface. The data
* property is set to the content associated with the header.
*
* @see mx.containers.accordionClasses.AccordionHeader
*/
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 true, 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
*/
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.
*/
protected var _resizeToContent:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* If set to true, this Accordion automatically resizes to
* the size of its current child.
*
* @default false
*/
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 null.
*
* Note: You can only set this property in an ActionScript statement, * not in MXML.
* * @tiptext Specifies the child view that is currently displayed * @helpid 3401 */ public function get selectedChild():Container { if (selectedIndex == -1) return null; return Container(getChildAt(selectedIndex)); } /** * @private */ public function set selectedChild(value:Container):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-1.
*
* @default 0
*
* @tiptext Specifies the index of the child view that is currently displayed
* @helpid 3402
*/
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();
/* measure() gets implemented by a subclass, either HAccordion or VAccordion */
/**
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 = IUIComponent(getChildAt(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 && Container(selectedChild).numChildrenCreated == -1)
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);
/* updateDisplayList() gets implemented by a subclass, either HAccordion or VAccordion */
/**
// 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 = IUIComponent(getChildAt(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++)
{
Container(getChildAt(i)).createComponentsFromDescriptors();
}
}
}
/**
* @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)
{
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
var headerStyleDecl:CSSStyleDeclaration =
StyleManager.getStyleDeclaration("." + headerStyleName);
if (headerStyleDecl)
{
// Need to reset the header style declaration and
// regenerate their style cache
for (var i:int = 0; i < numChildren; i++)
{
header = getHeaderAt(i);
if (header)
{
header.styleDeclaration = headerStyleDecl;
header.regenerateStyleCache(true);
header.styleChanged(null);
}
}
}
}
else
{
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;
overlayColor = color;
overlayTargetArea = targetArea;
if (selectedChild && selectedChild.numChildrenCreated == -1) // 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(overlayColor, 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()
*/
public function saveState():Object
{
var index:int = _selectedIndex == -1 ? 0 : _selectedIndex;
return { selectedIndex: index };
}
/**
* @copy mx.managers.IHistoryManagerClient#loadState()
*/
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.
*/
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.
*/
protected function getHeaderHeight():Number
{
var headerHeight:Number = getStyle("headerHeight");
if (isNaN(headerHeight))
{
headerHeight = 0;
if (numChildren > 0)
headerHeight = getHeaderAt(0).measuredHeight;
}
return headerHeight;
}
/**
* @private
* Returns the width of the header control. All header controls are the same
* width.
*/
protected function getHeaderWidth():Number
{
var headerWidth:Number = getStyle("headerWidth");
if (isNaN(headerWidth))
{
headerWidth = 0;
if (numChildren > 0)
headerWidth = getHeaderAt(0).measuredHeight;
}
return headerWidth;
}
//--------------------------------------------------------------------------
//
// 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);
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
header.styleName = this;
}
var headerStyleName:String = getStyle("headerStyleName");
if (headerStyleName)
{
if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_3_0)
{
var headerStyleDecl:CSSStyleDeclaration = StyleManager.
getStyleDeclaration("." + headerStyleName);
if (headerStyleDecl)
header.styleDeclaration = headerStyleDecl;
}
else
{
header.styleName = headerStyleName;
}
}
header.addEventListener(MouseEvent.CLICK, headerClickHandler);
IDataRenderer(header).data = content;
if (content is Container)
{
var contentContainer:Container = Container(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.toolTip;
if (toolTip && toolTip != "")
{
header.toolTip = toolTip;
contentContainer.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
*/
protected 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
*/
protected 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
*/
protected 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 = Container(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.
Container(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.
Container(getChildAt(newIndex)).setVisible(false, true);
if (oldIndex != -1)
Container(getChildAt(oldIndex)).setVisible(false);
instantiateChild(selectedChild);
}
else
{
if (tween)
tween.endTween();
startTween(oldIndex, newIndex);
}
}
/**
* @private
*/
protected function instantiateChild(child:Container):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.numChildrenCreated == -1)
child.createComponentsFromDescriptors();
// 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
*/
protected 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)
Container(getChildAt(oldSelectedIndex)).tweeningProperties = ["x", "y", "width", "height"];
Container(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:Container = Container(getChildAt(newSelectedIndex));
if (newSelectedChild.numChildren == 0)
{
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:Container = Container(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:Container;
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 = Container(getChildAt(i));
content.cacheAsBitmap = false;
content.scrollRect = null;
content.visible = true;
y += localContentHeight;
}
y += verticalGap;
}
if (oldSelectedIndex != -1)
{
content = Container(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();
Container(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;
switch (event.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(Container(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(Container(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 = Container(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", Container(event.target).icon);
//header.icon = Container(event.target).icon;
}
}
}