blob: 5671dc03680e04b39189dd8ab0f0eef3333acd66 [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.controls
{
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.text.TextFormatAlign;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;
import flash.utils.Timer;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.EdgeMetrics;
import mx.core.FlexVersion;
import mx.core.IBorder;
import mx.core.IButton;
import mx.core.IDataRenderer;
import mx.core.IFlexAsset;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModuleFactory;
import mx.core.IFontContextComponent;
import mx.core.IInvalidating;
import mx.core.ILayoutDirectionElement;
import mx.core.IProgrammaticSkin;
import mx.core.IStateClient;
import mx.core.IUIComponent;
import mx.core.IUITextField;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import mx.events.MoveEvent;
import mx.events.SandboxMouseEvent;
import mx.managers.IFocusManagerComponent;
import mx.styles.ISimpleStyleClient;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the user presses the Button control.
* If the <code>autoRepeat</code> property is <code>true</code>,
* this event is dispatched repeatedly as long as the button stays down.
*
* @eventType mx.events.FlexEvent.BUTTON_DOWN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="buttonDown", type="mx.events.FlexEvent")]
/**
* Dispatched when the <code>selected</code> property
* changes for a toggle Button control. A toggle Button control means that the
* <code>toggle</code> property is set to <code>true</code>.
*
* For the RadioButton controls, this event is dispatched when the <code>selected</code>
* property changes.
*
* For the CheckBox controls, this event is dispatched only when the
* user interacts with the control by using the mouse.
*
* @eventType flash.events.Event.CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="change", type="flash.events.Event")]
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains the data to display.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
//--------------------------------------
// Styles
//--------------------------------------
include "../styles/metadata/FocusStyles.as"
include "../styles/metadata/LeadingStyle.as"
include "../styles/metadata/PaddingStyles.as"
include "../styles/metadata/SkinStyles.as"
include "../styles/metadata/TextStyles.as"
/**
* Color applied to the button when the emphasized flag is true.
*
* @default #0099FF
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="accentColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* Color of focus ring when the component is in focus
*
* @default 0x70B2EE
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="focusColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* Gap between the label and icon, when the <code>labelPlacement</code> property
* is set to <code>left</code> or <code>right</code>.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="horizontalGap", type="Number", format="Length", inherit="no")]
/**
* Number of pixels of vertical offset to apply to the label position.
* Positive numbers move the label down.
*
* The default value for the Halo theme is <code>0</code>.
* The default value for the Spark theme is <code>1</code>.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="labelVerticalOffset", type="Number", format="Length", inherit="no")]
/**
* Number of pixels between the component's bottom border
* and the bottom of its content area.
*
* The default value for the Halo theme is <code>2</code>.
* The default value for the Spark theme is <code>0</code>.
*
* @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 component's top border
* and the top of its content area.
*
* The default value for the Halo theme is <code>2</code>.
* The default value for the Spark theme is <code>0</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* Number of milliseconds to wait after the first <code>buttonDown</code>
* event before repeating <code>buttonDown</code> events at each
* <code>repeatInterval</code>.
*
* @default 500
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="repeatDelay", type="Number", format="Time", inherit="no")]
/**
* Number of milliseconds between <code>buttonDown</code> events
* if the user presses and holds the mouse on a button.
*
* @default 35
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="repeatInterval", type="Number", format="Time", inherit="no")]
/**
* Text color of the label as the user moves the mouse pointer over the button.
*
* 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")]
/**
* Text color of the label as the user presses it.
*
* 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")]
/**
* Gap between the button's label and icon when the <code>labelPlacement</code>
* property is set to <code>"top"</code> or <code>"bottom"</code>.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="verticalGap", type="Number", format="Length", inherit="no")]
//--------------------------------------
// Skins
//--------------------------------------
/**
* Name of the class to use as the default skin for the background and border.
*
* The default value for the Halo theme is <code>mx.skins.halo.ButtonSkin</code>.
* The default value for the Spark theme is <code>mx.skins.spark.ButtonSkin</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="skin", type="Class", inherit="no", states="up, over, down, disabled, selectedUp, selectedOver, selectedDown, selectedDisabled")]
/**
* Name of the class to use as the skin for the background and border
* when the button is not selected and the mouse is not over the control.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="upSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the button is not selected and the mouse is over the control.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="overSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the button is not selected and the mouse button is down.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="downSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the button is not selected and is disabled.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="disabledSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the button when the button
* is <code>emphasized</code> (such as when serving as the default
* button for a container).
*
* The default value for the Halo theme is <code>mx.skins.halo.ButtonSkin</code>.
* The default value for the Spark theme is <code>mx.skins.spark.DefaultButtonSkin</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="emphasizedSkin", type="Class", inherit="no", states="up, over, down, disabled, selectedUp, selectedOver, selectedDown, selectedDisabled")]
/**
* Name of the class to use as the skin for the background and border
* when a toggle button is selected and the mouse is not over the control.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedUpSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when a toggle button is selected and the mouse is over the control.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedOverSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when a toggle button is selected and the mouse button is down.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedDownSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when a toggle button is selected and disabled.
*
* <p>The default skin class is based on the theme. For example, with the Halo theme,
* the default skin class is <code>mx.skins.halo.ButtonSkin</code>. For the Spark theme, the default skin
* class is <code>mx.skins.spark.ButtonSkin</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedDisabledSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the default icon.
* Setting any other icon style overrides this setting.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="icon", type="Class", inherit="no", states="up, over, down, disabled, selectedUp, selectedOver, selectedDown, selectedDisabled")]
/**
* Name of the class to use as the icon when a toggle button is not
* selected and the mouse is not over the button.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="upIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon when the button is not
* selected and the mouse is over the control.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="overIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon when the button is not
* selected and the mouse button is down.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="downIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon
* when the button is disabled and not selected.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="disabledIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon
* when the button is selected and the mouse button is up.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedUpIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon
* when the button is selected and the mouse is over the control.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedOverIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon
* when the button is selected and the mouse button is down.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedDownIcon", type="Class", inherit="no")]
/**
* Name of the class to use as the icon
* when the button is selected and disabled.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectedDisabledIcon", type="Class", inherit="no")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="mx.accessibility.ButtonAccImpl")]
[DefaultBindingProperty(source="selected", destination="label")]
[DefaultTriggerEvent("click")]
[IconFile("Button.png")]
[Alternative(replacement="spark.components.Button", since="4.0")]
/**
* The Button control is a commonly used rectangular button.
* Button controls look like they can be pressed.
* They can have a text label, an icon, or both on their face.
*
* <p>Buttons typically use event listeners to perform an action
* when the user selects the control. When a user clicks the mouse
* on a Button control, and the Button control is enabled,
* it dispatches a <code>click</code> event and a <code>buttonDown</code> event.
* A button always dispatches events such as the <code>mouseMove</code>,
* <code>mouseOver</code>, <code>mouseOut</code>, <code>rollOver</code>,
* <code>rollOut</code>, <code>mouseDown</code>, and
* <code>mouseUp</code> events whether enabled or disabled.</p>
*
* <p>You can customize the look of a Button control
* and change its functionality from a push button to a toggle button.
* You can change the button appearance by using a skin
* for each of the button's states.</p>
*
* <p>The label of a Button control uses a bold typeface. If you embed
* a font that you want to use for the label of the Button control, you must
* embed the bold typeface; for example:</p>
*
* <pre>
* &lt;fx:style&gt;
* &#64;font-face {
* src:url("../MyFont-Bold.ttf");
* fontFamily: myFont;
* fontWeight: bold;
* }
* .myBoldStyle {
* fontFamily: myFont;
* fontWeight: bold;
* }
* &lt;/fx:style&gt;
* ...
* &lt;mx:Button ... styleName="myBoldStyle"/&gt;
* </pre>
*
* <p>The Button control has the following default characteristics:</p>
* <table class="innertable">
* <tr><th>Characteristic</th><th>Description</th></tr>
* <tr><td>Default size</td><td>A size large enough to hold the label text, and any icon</td></tr>
* <tr><td>Minimum size</td><td>0 pixels</td></tr>
* <tr><td>Maximum size</td><td>No limit</td></tr>
* </table>
*
* @mxml
*
* <p>The <code>&lt;mx:Button&gt;</code> tag inherits all the tag attributes
* of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:Button
* <b>Properties</b>
* autoRepeat="false|true"
* emphasized="false|true"
* fontContext="<i>IFontModuleFactory</i>"
* label=""
* labelPlacement="right|left|bottom|top"
* selected="false|true"
* selectedField="null"
* stickyHighlighting="false|true"
* toggle="false|true"
*
* <b>Styles</b>
* borderColor="0xAAB3B3"
* color="0x0B333C"
* cornerRadius="4"
* disabledColor="0xAAB3B3"
* disabledIcon="null"
* disabledSkin="mx.skins.halo.ButtonSkin"
* downIcon="null"
* downSkin="mx.skins.halo.ButtonSkin"
* fillAlphas="[0.6, 0.4]"
* fillColors="[0xE6EEEE, 0xFFFFFF]"
* focusAlpha="0.5"
* focusRoundedCorners"tl tr bl br"
* fontAntiAliasType="advanced"
* fontFamily="Verdana"
* fontGridFitType="pixel"
* fontSharpness="0"
* fontSize="10"
* fontStyle="normal|italic"
* fontThickness="0"
* fontWeight="bold|normal"
* highlightAlphas="[0.3, 0.0]"
* horizontalGap="2"
* icon="null"
* kerning="false|true"
* leading="2"
* letterSpacing="0"
* overIcon="null"
* overSkin="mx.skins.halo.ButtonSkin"
* paddingBottom="2"
* paddingLeft="0"
* paddingRight="0"
* paddingTop="2"
* repeatDelay="500"
* repeatInterval="35"
* selectedDisabledIcon="null"
* selectedDisabledSkin="mx.skins.halo.ButtonSkin"
* selectedDownIcon="null"
* selectedDownSkin="mx.skins.halo.ButtonSkin"
* selectedOverIcon="null"
* selectedOverSkin="mx.skins.halo.ButtonSkin"
* selectedUpIcon="null"
* selectedUpSkin="mx.skins.halo.ButtonSkin"
* skin="mx.skins.halo.ButtonSkin"
* textAlign="center|left|right"
* textDecoration="none|underline"
* textIndent="0"
* textRollOverColor="0x2B333C"
* textSelectedColor="0x000000"
* upIcon="null"
* upSkin="mx.skins.halo.ButtonSkin"
* verticalGap="2"
*
* <b>Events</b>
* buttonDown="<i>No default</i>"
* change="<i>No default</i>"
* dataChange="<i>No default</i>"
* /&gt;
* </pre>
*
* @includeExample examples/ButtonExample.mxml
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class Button extends UIComponent
implements IDataRenderer, IDropInListItemRenderer,
IFocusManagerComponent, IListItemRenderer,
IFontContextComponent, IButton
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ButtonAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function Button()
{
super();
// DisplayObjectContainer properties.
// Setting mouseChildren to false ensure that mouse events
// are dispatched from the Button itself,
// not from its skins, icons, or TextField.
// One reason for doing this is that if you press the mouse button
// while over the TextField and release the mouse button while over
// a skin or icon, we want the player to dispatch a "click" event.
// Another is that if mouseChildren were true and someone uses
// Sprites rather than Shapes for the skins or icons,
// then we we wouldn't get a click because the current skin or icon
// changes between the mouseDown and the mouseUp.
// (This doesn't happen even when mouseChildren is true if the skins
// and icons are Shapes, because Shapes never dispatch mouse events;
// they are dispatched from the Button in this case.)
mouseChildren = false;
// Register for player events.
addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
addEventListener(MouseEvent.CLICK, clickHandler);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* Skins for the various states (falseUp, trueOver, etc.)
* are created just-in-time as they are needed.
* Each skin is a child Sprite of this Button.
* Each skin has a name property indicating which skin it is;
* for example, the instance of the class specified by the falseUpSkin
* style has the name "falseUpSkin" and can be found using
* getChildByName(). Note that there is no falseUpSkin property
* of Button containing a reference to this skin instance.
* This array contains references to all skins that have been created,
* for looping over them; without this array we wouldn't know
* which of the children are the skins.
* New skins are created and added to this array in viewSkin().
*/
private var skins:Array /* of Sprite */ = [];
/**
* @private
* A reference to the current skin.
* Set by viewSkin().
*/
mx_internal var currentSkin:IFlexDisplayObject;
/**
* The icons array contains references to all icons
* that have been created. Since each icon is a child
* Sprite of this button, we need this array to keep
* track of which children are icons. Each icon has a
* name property indicating which icon it is; for example,
* the instance of the class specified by the falseUpIcon
* style has the name "falseUpIcon" and can be found using
* getChildByName(). Note that there is no falseUpIcon property
* of Button containing a reference to this icon instance.
* New icons are created and added to this array in viewIcon().
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var icons:Array /* of Sprite */ = [];
/**
* @private
* A reference to the current icon.
* Set by viewIcon().
*/
mx_internal var currentIcon:IFlexDisplayObject;
/**
* @private
* Timer for doing auto-repeat.
*/
private var autoRepeatTimer:Timer;
/**
* @private
* Number used to offset the label and/or icon
* when button is pressed.
*/
mx_internal var buttonOffset:Number = 0;
/**
* @private
* used by old measure/layout logic
*/
mx_internal var centerContent:Boolean = true;
/**
* @private
* used by old measure/layout logic
*/
mx_internal var extraSpacing:Number = 10 + 10;
/**
* @private
*/
mx_internal static var TEXT_WIDTH_PADDING:Number = UITextField.TEXT_WIDTH_PADDING + 1;
/**
* @private
*/
private var styleChangedFlag:Boolean = true;
/**
* @private
* The measured width of the first skin loaded.
*/
private var skinMeasuredWidth:Number;
/**
* @private
* The measured height of the first skin loaded.
*/
private var skinMeasuredHeight:Number;
/**
* @private
* The value of the unscaledWidth parameter during the most recent
* call to updateDisplayList
*/
private var oldUnscaledWidth:Number;
/**
* @private
* Flags that will block default data/listData behavior
*/
private var selectedSet:Boolean;
private var labelSet:Boolean;
/**
* @private
* Flags used to save information about the skin and icon styles
*/
mx_internal var checkedDefaultSkin:Boolean = false;
mx_internal var defaultSkinUsesStates:Boolean = false;
mx_internal var checkedDefaultIcon:Boolean = false;
mx_internal var defaultIconUsesStates:Boolean = false;
/**
* @private
* Skin names.
* Allows subclasses to re-define the skin property names.
*/
mx_internal var skinName:String = "skin";
mx_internal var emphasizedSkinName:String = "emphasizedSkin";
mx_internal var upSkinName:String = "upSkin";
mx_internal var overSkinName:String = "overSkin";
mx_internal var downSkinName:String = "downSkin";
mx_internal var disabledSkinName:String = "disabledSkin";
mx_internal var selectedUpSkinName:String = "selectedUpSkin";
mx_internal var selectedOverSkinName:String = "selectedOverSkin";
mx_internal var selectedDownSkinName:String = "selectedDownSkin";
mx_internal var selectedDisabledSkinName:String = "selectedDisabledSkin";
/**
* @private
* Icon names.
* Allows subclasses to re-define the icon property names.
*/
mx_internal var iconName:String = "icon";
mx_internal var upIconName:String = "upIcon";
mx_internal var overIconName:String = "overIcon";
mx_internal var downIconName:String = "downIcon";
mx_internal var disabledIconName:String = "disabledIcon";
mx_internal var selectedUpIconName:String = "selectedUpIcon";
mx_internal var selectedOverIconName:String = "selectedOverIcon";
mx_internal var selectedDownIconName:String = "selectedDownIcon";
mx_internal var selectedDisabledIconName:String = "selectedDisabledIcon";
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// baselinePosition
//----------------------------------
/**
* @private
* The baselinePosition of a Button is calculated for its label.
*/
override public function get baselinePosition():Number
{
if (!validateBaselinePosition())
return NaN;
return textField.y + textField.baselinePosition;
}
//----------------------------------
// enabled
//----------------------------------
/**
* @private
*/
private var enabledChanged:Boolean = false;
[Inspectable(category="General", enumeration="true,false", defaultValue="true")]
/**
* @private
* This is called whenever the enabled state changes.
*/
override public function set enabled(value:Boolean):void
{
if (super.enabled == value)
return;
super.enabled = value;
enabledChanged = true;
invalidateProperties();
invalidateDisplayList();
}
//----------------------------------
// textField
//----------------------------------
/**
* The internal UITextField object that renders the label of this Button.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var textField:IUITextField;
//----------------------------------
// toolTip
//----------------------------------
/**
* @private
*/
private var toolTipSet:Boolean = false;
[Inspectable(category="General", defaultValue="null")]
/**
* @private
*/
override public function set toolTip(value:String):void
{
super.toolTip = value;
if (value)
{
toolTipSet = true;
}
else
{
toolTipSet = false;
invalidateDisplayList();
}
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// autoRepeat
//----------------------------------
/**
* @private
* Storage for the autoRepeat property.
*/
private var _autoRepeat:Boolean = false;
[Inspectable(defaultValue="false")]
/**
* Specifies whether to dispatch repeated <code>buttonDown</code>
* events if the user holds down the mouse button.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get autoRepeat():Boolean
{
return _autoRepeat;
}
/**
* @private
*/
public function set autoRepeat(value:Boolean):void
{
_autoRepeat = value;
if (value)
{
// Create a Timer object for driving the autorepeat.
// The duration gets set in mouseDownHandler and reset
// in autoRepeatTimer_timerDelayHandler, because
// there is a delay before the first autorepeat
// and then a possibly different interval
// between subsequent ones.
autoRepeatTimer = new Timer(1);
}
else
{
autoRepeatTimer = null;
}
}
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property;
*/
private var _data:Object;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* The <code>data</code> property lets you pass a value
* to the component when you use it as an item renderer or item editor.
* You typically use data binding to bind a field of the <code>data</code>
* property to a property of this component.
*
* <p>When you use the control as a drop-in item renderer or drop-in
* item editor, Flex automatically writes the current value of the item
* to the <code>selected</code> property of this control.</p>
*
* <p>You do not set this property in MXML.</p>
*
* @default null
* @see mx.core.IDataRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
var newSelected:*;
var newLabel:*;
_data = value;
if (_listData && _listData is DataGridListData &&
DataGridListData(_listData).dataField !=null)
{
newSelected = _data[DataGridListData(_listData).dataField];
newLabel = "";
}
else if (_listData)
{
if (selectedField)
newSelected = _data[selectedField];
newLabel = _listData.label;
}
else
{
newSelected = _data;
}
if (newSelected !== undefined && !selectedSet)
{
selected = newSelected as Boolean;
selectedSet = false;
}
if (newLabel !== undefined && !labelSet)
{
label = newLabel;
labelSet = false;
}
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
//----------------------------------
// emphasized
//----------------------------------
/**
* @private
* Storage for the emphasized property.
*/
mx_internal var _emphasized:Boolean = false;
/**
* @private
*/
private var emphasizedChanged:Boolean = false;
[Inspectable(category="General", defaultValue="false")]
/**
* Draws a thick border around the Button control
* when the control is in its up state if <code>emphasized</code>
* is set to <code>true</code>.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get emphasized():Boolean
{
return _emphasized;
}
/**
* @private
*/
public function set emphasized(value:Boolean):void
{
_emphasized = value;
emphasizedChanged = true;
invalidateDisplayList();
}
//----------------------------------
// fontContext
//----------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get fontContext():IFlexModuleFactory
{
return moduleFactory;
}
/**
* @private
*/
public function set fontContext(moduleFactory:IFlexModuleFactory):void
{
this.moduleFactory = moduleFactory;
}
//----------------------------------
// label
//----------------------------------
/**
* @private
* Storage for label property.
*/
private var _label:String = "";
/**
* @private
*/
private var labelChanged:Boolean = false;
[Bindable("labelChanged")]
[Inspectable(category="General", defaultValue="")]
/**
* Text to appear on the Button control.
*
* <p>If the label is wider than the Button control,
* the label is truncated and terminated by an ellipsis (...).
* The full label displays as a tooltip
* when the user moves the mouse over the Button control.
* If you have also set a tooltip by using the <code>tooltip</code>
* property, the tooltip is displayed rather than the label text.</p>
*
* @default ""
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get label():String
{
return _label;
}
/**
* @private
*/
public function set label(value:String):void
{
labelSet = true;
if (_label != value)
{
_label = value;
labelChanged = true;
invalidateSize();
invalidateDisplayList();
dispatchEvent(new Event("labelChanged"));
}
}
//----------------------------------
// labelPlacement
//----------------------------------
/**
* @private
* Storage for labelPlacement property.
*/
mx_internal var _labelPlacement:String = ButtonLabelPlacement.RIGHT;
[Bindable("labelPlacementChanged")]
[Inspectable(category="General", enumeration="left,right,top,bottom", defaultValue="right")]
/**
* Orientation of the label in relation to a specified icon.
* Valid MXML values are <code>right</code>, <code>left</code>,
* <code>bottom</code>, and <code>top</code>.
*
* <p>In ActionScript, you can use the following constants
* to set this property:
* <code>ButtonLabelPlacement.RIGHT</code>,
* <code>ButtonLabelPlacement.LEFT</code>,
* <code>ButtonLabelPlacement.BOTTOM</code>, and
* <code>ButtonLabelPlacement.TOP</code>.</p>
*
* @default ButtonLabelPlacement.RIGHT
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelPlacement():String
{
return _labelPlacement;
}
/**
* @private
*/
public function set labelPlacement(value:String):void
{
_labelPlacement = value;
invalidateSize();
invalidateDisplayList();
dispatchEvent(new Event("labelPlacementChanged"));
}
//-----------------------------------
// listData
//-----------------------------------
/**
* @private
* Storage for the listData property.
*/
private var _listData:BaseListData;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* When a component is used as a drop-in item renderer or drop-in
* item editor, Flex initializes the <code>listData</code> property
* of the component with the appropriate data from the list control.
* The component can then use the <code>listData</code> property
* to initialize the <code>data</code> property
* of the drop-in item renderer or drop-in item editor.
*
* <p>You do not set this property in MXML or ActionScript;
* Flex sets it when the component is used as a drop-in item renderer
* or drop-in item editor.</p>
*
* @default null
* @see mx.controls.listClasses.IDropInListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get listData():BaseListData
{
return _listData;
}
/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = value;
}
//----------------------------------
// phase
//----------------------------------
/**
* @private
* Mouse and focus events set this to
* ButtonPhase.UP, ButtonPhase.OVER, or ButtonPhase.DOWN.
*/
private var _phase:String = ButtonPhase.UP;
/**
* @private
*/
mx_internal var phaseChanged:Boolean = false;
/**
* @private
*/
mx_internal function get phase():String
{
return _phase;
}
/**
* @private
*/
mx_internal function set phase(value:String):void
{
_phase = value;
phaseChanged = true;
invalidateSize();
invalidateProperties();
invalidateDisplayList();
}
//----------------------------------
// selected
//----------------------------------
/**
* @private
* Storage for selected property.
*/
mx_internal var _selected:Boolean = false;
[Bindable("click")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="false")]
/**
* Indicates whether a toggle button is toggled
* on (<code>true</code>) or off (<code>false</code>).
* This property can be set only if the <code>toggle</code> property
* is set to <code>true</code>.
*
* <p>For a CheckBox control, indicates whether the box
* is displaying a check mark. For a RadioButton control,
* indicates whether the control is selected.</p>
*
* <p>The user can change this property by clicking the control,
* but you can also set the property programmatically.</p>
*
* <p>In previous versions, If the <code>toggle</code> property
* was set to <code>true</code>, changing this property also dispatched
* a <code>change</code> event. Starting in version 3.0, setting this
* property programmatically only dispatches a
* <code>valueCommit</code> event.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selected():Boolean
{
return _selected;
}
/**
* @private
*/
public function set selected(value:Boolean):void
{
selectedSet = true;
setSelected(value, true);
}
mx_internal function setSelected(value:Boolean,
isProgrammatic:Boolean = false):void
{
if (_selected != value)
{
_selected = value;
invalidateDisplayList();
if (toggle && !isProgrammatic)
dispatchEvent(new Event(Event.CHANGE));
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
}
//----------------------------------
// selectedField
//----------------------------------
/**
* The name of the field in the <code>data</code> property which specifies
* the value of the Button control's <code>selected</code> property.
* You can set this property when you use the Button control in an item renderer.
* The default value is null, which means that the Button control does
* not set its selected state based on a property in the <code>data</code> property.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var selectedField:String = null;
//----------------------------------
// skinLayoutDirection
//----------------------------------
private var skinLayoutDirectionSet:Boolean = false;
private var _skinLayoutDirection:String;
/**
* @private
*/
mx_internal function set skinLayoutDirection(value:String):void
{
skinLayoutDirectionSet = true;
_skinLayoutDirection = value;
}
//----------------------------------
// stickyHighlighting
//----------------------------------
/**
* If <code>false</code>, the Button displays its down skin
* when the user presses it but changes to its over skin when
* the user drags the mouse off of it.
* If <code>true</code>, the Button displays its down skin
* when the user presses it, and continues to display this skin
* when the user drags the mouse off of it.
*
* <p>Button subclasses, such as the SliderThumb and ScrollThumb classes
* or the up and down arrows of a ScrollBar, set
* this property to <code>true</code>.</p>
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var stickyHighlighting:Boolean = false;
//----------------------------------
// toggle
//----------------------------------
/**
* @private
* Storage for toggle property.
*/
mx_internal var _toggle:Boolean = false;
/**
* @private
*/
mx_internal var toggleChanged:Boolean = false;
[Bindable("toggleChanged")]
[Inspectable(category="General", defaultValue="false")]
/**
* Controls whether a Button is in a toggle state or not.
*
* If <code>true</code>, clicking the button toggles it
* between a selected and an unselected state.
* You can get or set this state programmatically
* by using the <code>selected</code> property.
*
* If <code>false</code>, the button does not stay pressed
* after the user releases it.
* In this case, its <code>selected</code> property
* is always <code>false</code>.
* Buttons like this are used for performing actions.
*
* When <code>toggle</code> is set to <code>false</code>,
* <code>selected</code> is forced to <code>false</code>
* because only toggle buttons can be selected.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get toggle():Boolean
{
return _toggle;
}
/**
* @private
*/
public function set toggle(value:Boolean):void
{
_toggle = value;
toggleChanged = true;
invalidateProperties();
invalidateDisplayList();
dispatchEvent(new Event("toggleChanged"));
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function initializeAccessibility():void
{
if (Button.createAccessibilityImplementation != null)
Button.createAccessibilityImplementation(this);
}
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
// Create a UITextField to display the label.
if (!textField)
{
textField = IUITextField(createInFontContext(UITextField));
textField.styleName = this;
addChild(DisplayObject(textField));
}
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
// if the font changed and we already created the textfield, we will need to
// destory it so it can be re-created, possibly in a different swf context.
if (hasFontContextChanged() && textField != null)
{
removeChild(DisplayObject(textField));
textField = null;
}
// Create a UITextField to display the label.
if (!textField)
{
textField = IUITextField(createInFontContext(UITextField));
textField.styleName = this;
addChild(DisplayObject(textField));
enabledChanged = true;
toggleChanged = true;
}
if (!initialized)
{
viewSkin();
viewIcon();
}
if (enabledChanged)
{
textField.enabled = enabled;
if (currentIcon && currentIcon is IUIComponent)
IUIComponent(currentIcon).enabled = enabled;
enabledChanged = false;
}
if (toggleChanged)
{
// If the button is no longer toggleable,
// deselect it.
if (!toggle)
selected = false;
toggleChanged = false;
}
if (phaseChanged)
{
// Ensure all potential pseudo-selectors are reevaluated if
// necessary.
var prevState:String = _currentButtonState;
if (prevState != getCurrentButtonState())
stateChanged(prevState, _currentButtonState, false);
phaseChanged = false;
}
}
/**
* @private
*/
override protected function measure():void
{
super.measure();
var textWidth:Number = 0;
var textHeight:Number = 0;
if (label)
{
var lineMetrics:TextLineMetrics = measureText(label);
textWidth = lineMetrics.width + TEXT_WIDTH_PADDING;
textHeight = lineMetrics.height + UITextField.TEXT_HEIGHT_PADDING;
}
var tempCurrentIcon:IFlexDisplayObject = getCurrentIcon();
var iconWidth:Number = tempCurrentIcon ? tempCurrentIcon.width : 0;
var iconHeight:Number = tempCurrentIcon ? tempCurrentIcon.height : 0;
var w:Number = 0;
var h:Number = 0;
if (labelPlacement == ButtonLabelPlacement.LEFT ||
labelPlacement == ButtonLabelPlacement.RIGHT)
{
w = textWidth + iconWidth;
if (textWidth && iconWidth)
w += getStyle("horizontalGap");
h = Math.max(textHeight, iconHeight);
}
else
{
w = Math.max(textWidth, iconWidth);
h = textHeight + iconHeight;
if (textHeight && iconHeight)
h += getStyle("verticalGap");
}
// Add padding. !!!Need a hack here to only add padding if we don't
// have text or icon. This is required to make small buttons (like scroll
// arrows and numeric stepper buttons) look correct.
if (textWidth || iconWidth)
{
w += getStyle("paddingLeft") + getStyle("paddingRight");
h += getStyle("paddingTop") + getStyle("paddingBottom");
}
var bm:EdgeMetrics = currentSkin &&
currentSkin is IBorder && !(currentSkin is IFlexAsset) ?
IBorder(currentSkin).borderMetrics :
null;
if (bm)
{
w += bm.left + bm.right;
h += bm.top + bm.bottom
}
// Use the larger of the measured sizes and the skin's preferred sizes.
// Each skin should override measure() with their measuredWidth
// and measuredHeight.
if (currentSkin && (isNaN(skinMeasuredWidth) || isNaN(skinMeasuredHeight)))
{
skinMeasuredWidth = currentSkin.measuredWidth;
skinMeasuredHeight = currentSkin.measuredHeight;
}
if (!isNaN(skinMeasuredWidth))
w = Math.max(skinMeasuredWidth, w);
if (!isNaN(skinMeasuredHeight))
h = Math.max(skinMeasuredHeight, h);
measuredMinWidth = measuredWidth = w;
measuredMinHeight = measuredHeight = h;
// trace("measure: Button width = " + w + " height = " + h);
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (emphasizedChanged)
{
changeSkins();
emphasizedChanged = false;
}
// Set each skin's size to the layout size of this Button.
var n:int = skins.length;
for (var i:int = 0; i < n; i++)
{
var skin:IFlexDisplayObject = IFlexDisplayObject(skins[i]);
skin.setActualSize(unscaledWidth, unscaledHeight);
}
// Show the appropriate skin and icon, based on whether this
// Button is enabled or disabled, whether it is selected
// or unselected, and how it is currently interacting
// with the mouse (i.e., the up/over/down phase).
viewSkin();
viewIcon();
/* if (currentIcon && currentIcon is IUIComponent)
IUIComponent(currentIcon).enabled = enabled; */
layoutContents(unscaledWidth, unscaledHeight,
phase == ButtonPhase.DOWN);
// If our width changed, reset the label text to get it to fit.
if (oldUnscaledWidth > unscaledWidth ||
textField.text != label ||
labelChanged ||
styleChangedFlag)
{
textField.text = label;
var truncated:Boolean = textField.truncateToFit();
if (!toolTipSet)
{
if (truncated)
super.toolTip = label;
else
super.toolTip = null;
}
styleChangedFlag = false;
labelChanged = false;
}
oldUnscaledWidth = unscaledWidth;
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
styleChangedFlag = true;
super.styleChanged(styleProp);
// Check for skin/icon changes here.
// We could only throw out any skins that change,
// but since dynamic re-skinning is uncommon, we'll take
// the simpler approach of throwing out all skins.
if (!styleProp || styleProp == "styleName")
{
// All style props have changed, so dump skins and icons.
changeSkins();
changeIcons();
if (initialized)
{
viewSkin();
viewIcon();
}
}
else if (styleProp.toLowerCase().indexOf("skin") != -1)
{
changeSkins();
}
else if (styleProp.toLowerCase().indexOf("icon") != -1)
{
changeIcons();
invalidateSize();
}
}
/**
* @private
*/
override protected function adjustFocusRect(
object:DisplayObject = null):void
{
// If we don't have a skin, show focus around the icon.
super.adjustFocusRect(!currentSkin ? DisplayObject(currentIcon) : this);
}
/**
* @private
* The state to be used when matching CSS pseudo-selectors. This override
* returns the current button state.
*/
override protected function get currentCSSState():String
{
return getCurrentButtonState();
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
* Displays one of the eight possible skins,
* creating it if it doesn't already exist.
*/
mx_internal function viewSkin():void
{
// Determine which skin to display, based on whether this
// button is enabled or disabled, whether it is
// selected or unselected, and how it is currently interacting
// with the mouse (i.e., the up/over/down state).
var tempSkinName:String;
if (!enabled)
tempSkinName = selected ? selectedDisabledSkinName : disabledSkinName;
else if (phase == ButtonPhase.UP)
tempSkinName = selected ? selectedUpSkinName : upSkinName;
else if (phase == ButtonPhase.OVER)
tempSkinName = selected ? selectedOverSkinName : overSkinName;
else if (phase == ButtonPhase.DOWN)
tempSkinName = selected ? selectedDownSkinName : downSkinName;
viewSkinForPhase(tempSkinName, getCurrentButtonState());
}
/**
* @private
* Displays one of the several possible skins,
* depending on the skinName and creating
* it if it doesn't already exist.
*/
mx_internal function viewSkinForPhase(tempSkinName:String, stateName:String):void
{
var newSkinClass:Class = Class(getStyle(tempSkinName));
var newSkin:IFlexDisplayObject;
if (!newSkinClass)
{
// Try the default skin
newSkinClass = _emphasized ? Class(getStyle(emphasizedSkinName)) : Class(getStyle(skinName));
newSkinClass = !newSkinClass && _emphasized ? Class(getStyle(skinName)) : newSkinClass;
// If we are using the default skin, then
if (defaultSkinUsesStates)
tempSkinName = skinName;
if (!checkedDefaultSkin && newSkinClass)
{
newSkin = IFlexDisplayObject(new newSkinClass());
// Check if the skin class is a state client or a programmatic skin
if (!(newSkin is IProgrammaticSkin) && newSkin is IStateClient)
{
defaultSkinUsesStates = true;
tempSkinName = skinName;
}
if (newSkin)
{
checkedDefaultSkin = true;
if (newSkin is ILayoutDirectionElement && skinLayoutDirectionSet)
ILayoutDirectionElement(newSkin).layoutDirection = _skinLayoutDirection;
}
}
}
// Has this skin already been created?
newSkin = IFlexDisplayObject(getChildByName(tempSkinName));
// If not, create it.
if (!newSkin)
{
if (newSkinClass)
{
newSkin = IFlexDisplayObject(new newSkinClass());
// Set its name so that we can find it in the future
// using getChildByName().
newSkin.name = tempSkinName;
// Make the getStyle() calls in ButtonSkin find the styles
// for this Button.
var styleableSkin:ISimpleStyleClient = newSkin as ISimpleStyleClient;
if (styleableSkin)
styleableSkin.styleName = this;
if (newSkin is ILayoutDirectionElement && skinLayoutDirectionSet)
ILayoutDirectionElement(newSkin).layoutDirection = _skinLayoutDirection;
addChild(DisplayObject(newSkin));
// Make the skin the proper size for this Button.
// This will cause to skin to be drawn by drawHaloRect()
// in ButtonSkin.
newSkin.setActualSize(unscaledWidth, unscaledHeight);
// If the skin is programmatic, and we've already been
// initialized, update it now to avoid flicker.
if (newSkin is IInvalidating && initialized)
{
IInvalidating(newSkin).validateNow();
}
else if (newSkin is IProgrammaticSkin && initialized)
{
IProgrammaticSkin(newSkin).validateDisplayList()
}
// Keep track of all skin children that have been created.
skins.push(newSkin);
}
}
// Hide the old skin.
if (currentSkin)
currentSkin.visible = false;
// Keep track of which skin is current.
currentSkin = newSkin;
// Update the state of the skin if it accepts states and it implements the IStateClient interface.
if (defaultSkinUsesStates && currentSkin is IStateClient)
{
IStateClient(currentSkin).currentState = stateName;
if (currentSkin is IInvalidating)
IInvalidating(currentSkin).validateNow();
}
// Show the new skin.
if (currentSkin)
currentSkin.visible = true;
var labelColor:Number;
if (enabled)
{
if (phase == ButtonPhase.OVER)
labelColor = textField.getStyle("textRollOverColor");
else if (phase == ButtonPhase.DOWN)
labelColor = textField.getStyle("textSelectedColor");
else
labelColor = textField.getStyle("color");
textField.setColor(labelColor);
}
}
/**
* @private
* Gets the currentIconName (string) based on the Button's phase.
*/
mx_internal function getCurrentIconName():String
{
var tempIconName:String;
if (!enabled)
{
tempIconName = selected ?
selectedDisabledIconName :
disabledIconName;
}
else if (phase == ButtonPhase.UP)
{
tempIconName = selected ? selectedUpIconName : upIconName;
}
else if (phase == ButtonPhase.OVER)
{
tempIconName = selected ? selectedOverIconName : overIconName;
}
else if (phase == ButtonPhase.DOWN)
{
tempIconName = selected ? selectedDownIconName : downIconName;
}
return tempIconName;
}
/**
* @private
* gets the currentIcon based on the button.phase
*/
mx_internal function getCurrentIcon():IFlexDisplayObject
{
// Determine which icon will get displayed, based on whether this
// Button is enabled or disabled, whether it is
// selected or unselected, and how it is currently interacting
// with the mouse (i.e., the up/over/down state).
var tempIconName:String = getCurrentIconName();
if (!tempIconName)
return null;
return viewIconForPhase(tempIconName);
}
/**
* @private
* Displays one of the eight possible icons,
* creating it if it doesn't already exist.
*/
mx_internal function viewIcon():void
{
// Determine which icon to display, based on whether this
// Button is enabled or disabled, whether it is
// selected or unselected, and how it is currently interacting
// with the mouse (i.e., the up/over/down state).
var tempIconName:String = getCurrentIconName();
viewIconForPhase(tempIconName);
}
/**
* @private
* Displays one of the several possible icons,
* depending on the iconName and creating it if it
* doesn't already exist.
*/
mx_internal function viewIconForPhase(tempIconName:String):IFlexDisplayObject
{
var newIconClass:Class = Class(getStyle(tempIconName));
var newIcon:IFlexDisplayObject;
if (!newIconClass)
{
newIconClass = Class(getStyle(iconName));
// If we are using the default icon, then set use the default icon name
if (defaultIconUsesStates)
tempIconName = iconName;
if (!checkedDefaultIcon && newIconClass)
{
newIcon = IFlexDisplayObject(new newIconClass());
// Check if the icon class is a state client or a programmatic skin
if (!(newIcon is IProgrammaticSkin) && newIcon is IStateClient)
{
defaultIconUsesStates = true;
tempIconName = iconName;
}
if (newIcon)
checkedDefaultIcon = true;
}
}
// Has this icon already been created?
newIcon = IFlexDisplayObject(getChildByName(tempIconName));
// If not, create it.
if (newIcon == null)
{
if (newIconClass != null)
{
newIcon = IFlexDisplayObject(new newIconClass());
// Set its name so that we can find it in the future
// using getChildByName().
newIcon.name = tempIconName;
if (newIcon is ISimpleStyleClient)
ISimpleStyleClient(newIcon).styleName = this;
addChild(DisplayObject(newIcon));
// If the skin is programmatic, and we've already been
// initialized, update it now to avoid flicker.
var sizeIcon:Boolean = false;
if (newIcon is IInvalidating)
{
IInvalidating(newIcon).validateNow();
sizeIcon = true;
}
else if (newIcon is IProgrammaticSkin)
{
IProgrammaticSkin(newIcon).validateDisplayList();
sizeIcon = true;
}
if (newIcon && newIcon is IUIComponent)
IUIComponent(newIcon).enabled = enabled;
if (sizeIcon)
newIcon.setActualSize(newIcon.measuredWidth, newIcon.measuredHeight);
// Keep track of all icon children that have been created.
icons.push(newIcon);
}
}
// Hide the old icon.
if (currentIcon != null)
currentIcon.visible = false;
// Keep track of which icon is current.
currentIcon = newIcon;
if (defaultIconUsesStates && currentIcon is IStateClient)
{
IStateClient(currentIcon).currentState = getCurrentButtonState();
if (currentIcon is IInvalidating)
IInvalidating(currentIcon).validateNow();
}
// Show the new icon.
if (currentIcon != null)
currentIcon.visible = true;
return newIcon;
}
/**
* @private
* Storage for the most recent button state.
*/
protected var _currentButtonState:String;
/**
* @private
* Computes the current button state based on whether this button is
* enabled or disabled, whether it is selected or unselected, and how it
* is currently interacting with the mouse (i.e. the up/over/down state).
*/
mx_internal function getCurrentButtonState():String
{
_currentButtonState = "";
if (!enabled)
_currentButtonState = selected ? "selectedDisabled" : "disabled";
else if (phase == ButtonPhase.UP)
_currentButtonState = selected ? "selectedUp" : "up";
else if (phase == ButtonPhase.OVER)
_currentButtonState = selected ? "selectedOver" : "over";
else if (phase == ButtonPhase.DOWN)
_currentButtonState = selected ? "selectedDown" : "down";
return _currentButtonState;
}
/**
* @private
* Controls the layout of the icon and the label within the button.
* The text/icon are aligned based on the textAlign style setting.
*/
mx_internal function layoutContents(unscaledWidth:Number,
unscaledHeight:Number,
offset:Boolean):void
{
var labelWidth:Number = 0;
var labelHeight:Number = 0;
var labelX:Number = 0;
var labelY:Number = 0;
var iconWidth:Number = 0;
var iconHeight:Number = 0;
var iconX:Number = 0;
var iconY:Number = 0;
var horizontalGap:Number = 0;
var verticalGap:Number = 0;
var paddingLeft:Number = getStyle("paddingLeft");
var paddingRight:Number = getStyle("paddingRight");
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
var textWidth:Number = 0;
var textHeight:Number = 0;
var lineMetrics:TextLineMetrics;
if (label)
{
lineMetrics = measureText(label);
textWidth = lineMetrics.width + TEXT_WIDTH_PADDING;
textHeight = lineMetrics.height + UITextField.TEXT_HEIGHT_PADDING;
}
else
{
lineMetrics = measureText("Wj");
textHeight = lineMetrics.height + UITextField.TEXT_HEIGHT_PADDING;
}
var n:Number = offset ? buttonOffset : 0;
var textAlign:String = getStyle("textAlign");
// Map new Spark values that might be set in a selector
// affecting both Halo and Spark components.
if (textAlign == "start")
textAlign = TextFormatAlign.LEFT;
else if (textAlign == "end")
textAlign = TextFormatAlign.RIGHT;
var viewWidth:Number = unscaledWidth;
var viewHeight:Number = unscaledHeight;
var bm:EdgeMetrics = currentSkin &&
currentSkin is IBorder && !(currentSkin is IFlexAsset) ?
IBorder(currentSkin).borderMetrics :
null;
if (bm)
{
viewWidth -= bm.left + bm.right;
viewHeight -= bm.top + bm.bottom;
}
if (currentIcon)
{
iconWidth = currentIcon.width;
iconHeight = currentIcon.height;
}
if (labelPlacement == ButtonLabelPlacement.LEFT ||
labelPlacement == ButtonLabelPlacement.RIGHT)
{
horizontalGap = getStyle("horizontalGap");
if (iconWidth == 0 || textWidth == 0)
horizontalGap = 0;
if (textWidth > 0)
{
textField.width = labelWidth =
Math.max(Math.min(viewWidth - iconWidth - horizontalGap -
paddingLeft - paddingRight, textWidth), 0);
}
else
{
textField.width = labelWidth = 0;
}
textField.height = labelHeight = Math.min(viewHeight, textHeight);
if (textAlign == "left")
{
labelX += paddingLeft;
}
else if (textAlign == "right")
{
labelX += (viewWidth - labelWidth - iconWidth -
horizontalGap - paddingRight);
}
else // "center" -- default value
{
labelX += ((viewWidth - labelWidth - iconWidth -
horizontalGap - paddingLeft - paddingRight) / 2) + paddingLeft;
}
if (labelPlacement == ButtonLabelPlacement.RIGHT)
{
labelX += iconWidth + horizontalGap;
iconX = labelX - (iconWidth + horizontalGap);
}
else
{
iconX = labelX + labelWidth + horizontalGap;
}
iconY = ((viewHeight - iconHeight - paddingTop - paddingBottom) / 2) + paddingTop;
labelY = ((viewHeight - labelHeight - paddingTop - paddingBottom) / 2) + paddingTop;
}
else
{
verticalGap = getStyle("verticalGap");
if (iconHeight == 0 || label == "")
verticalGap = 0;
if (textWidth > 0)
{
textField.width = labelWidth = Math.max(viewWidth - paddingLeft - paddingRight, 0);
textField.height = labelHeight =
Math.min(viewHeight - iconHeight - paddingTop - paddingBottom - verticalGap, textHeight);
}
else
{
textField.width = labelWidth = 0;
textField.height = labelHeight = 0;
}
labelX = paddingLeft;
if (textAlign == "left")
{
iconX += paddingLeft;
}
else if (textAlign == "right")
{
iconX += Math.max(viewWidth - iconWidth - paddingRight, paddingLeft);
}
else
{
iconX += ((viewWidth - iconWidth - paddingLeft - paddingRight) / 2) + paddingLeft;
}
if (labelPlacement == ButtonLabelPlacement.TOP)
{
labelY += ((viewHeight - labelHeight - iconHeight -
paddingTop - paddingBottom - verticalGap) / 2) + paddingTop;
iconY += labelY + labelHeight + verticalGap;
}
else
{
iconY += ((viewHeight - labelHeight - iconHeight -
paddingTop - paddingBottom - verticalGap) / 2) + paddingTop;
labelY += iconY + iconHeight + verticalGap;
}
}
var buffX:Number = n;
var buffY:Number = n;
if (bm)
{
buffX += bm.left;
buffY += bm.top;
}
if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_4_0)
labelY += getStyle("labelVerticalOffset");
textField.x = Math.round(labelX + buffX);
textField.y = Math.round(labelY + buffY);
if (currentIcon)
{
iconX += buffX;
iconY += buffY;
// dispatch a move on behalf of the icon
// the focus system uses that to adjust
// focus rectangles
var moveEvent:MoveEvent = new MoveEvent(MoveEvent.MOVE);
moveEvent.oldX = currentIcon.x;
moveEvent.oldY = currentIcon.y;
currentIcon.x = Math.round(iconX);
currentIcon.y = Math.round(iconY);
currentIcon.dispatchEvent(moveEvent);
}
// The skins and icons get created on demand as the user interacts
// with the Button, and as they are created they become the
// frontmost child.
// Here we ensure that the textField is the frontmost child,
// with the current icon behind it and the current skin behind that.
// Any other skins and icons are left behind these three,
// with arbitrary layering.
if (currentSkin)
setChildIndex(DisplayObject(currentSkin), numChildren - 1);
if (currentIcon)
setChildIndex(DisplayObject(currentIcon), numChildren - 1);
if (textField)
setChildIndex(DisplayObject(textField), numChildren - 1);
}
/**
* @private
*/
mx_internal function changeSkins():void
{
var n:int = skins.length;
for (var i:int = 0; i < n; i++)
{
removeChild(skins[i]);
}
skins = [];
skinMeasuredWidth = NaN;
skinMeasuredHeight = NaN;
checkedDefaultSkin = false;
defaultSkinUsesStates = false;
if (initialized)
{
viewSkin();
invalidateSize();
}
}
/**
* @private
*/
mx_internal function changeIcons():void
{
var n:int = icons.length;
for (var i:int = 0; i < n; i++)
{
removeChild(icons[i]);
}
icons = [];
checkedDefaultIcon = false;
defaultIconUsesStates = false;
}
/**
* @private
*/
mx_internal function buttonPressed():void
{
phase = ButtonPhase.DOWN;
dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));
if (autoRepeat)
{
autoRepeatTimer.delay = getStyle("repeatDelay");
autoRepeatTimer.addEventListener(
TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
autoRepeatTimer.start();
}
}
/**
* @private
*/
mx_internal function buttonReleased():void
{
// Remove the handlers that were added in mouseDownHandler().
systemManager.getSandboxRoot().removeEventListener(
MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true);
systemManager.getSandboxRoot().removeEventListener(
SandboxMouseEvent.MOUSE_UP_SOMEWHERE, stage_mouseLeaveHandler);
if (autoRepeatTimer)
{
autoRepeatTimer.removeEventListener(
TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
autoRepeatTimer.removeEventListener(
TimerEvent.TIMER, autoRepeatTimer_timerHandler);
autoRepeatTimer.reset();
}
}
/**
* @private
* Some other components which use a Button as an internal
* subcomponent need access to its UITextField, but can't access the
* textField var because it is protected and therefore available
* only to subclasses.
*/
mx_internal function getTextField():IUITextField
{
return textField;
}
//--------------------------------------------------------------------------
//
// Overridden event handlers: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function focusOutHandler(event:FocusEvent):void
{
super.focusOutHandler(event);
// Most of the time the system sends a rollout, but there are
// situations where the mouse is over something else
// that you don't get one so we force one here.
if (phase != ButtonPhase.UP)
phase = ButtonPhase.UP;
}
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
if (!enabled)
return;
if (event.keyCode == Keyboard.SPACE)
buttonPressed();
}
/**
* @private
*/
override protected function keyUpHandler(event:KeyboardEvent):void
{
if (!enabled)
return;
if (event.keyCode == Keyboard.SPACE)
{
buttonReleased();
if (phase == ButtonPhase.DOWN)
dispatchEvent(new MouseEvent(MouseEvent.CLICK));
phase = ButtonPhase.UP;
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/*
Mouse interaction sequences that Button must handle:
All start with Button in "up" phase, mouse outside Button,
and mouse button up.
Normal click:
roll over Button -> "over" phase
mouse down on Button -> "down" phase, dispatch "buttonDown"
mouse up while over Button -> "over" phase, dispatch "click"
roll out of Button -> "up" phase
Click canceled:
roll over Button -> "over" phase
mouse down on Button -> "down" phase, dispatch "buttonDown"
roll out of Button -> "over" phase
maybe roll over and out of other objects -> dispatch events from them
maybe roll off the stage, or off and back on
mouse up while out of Button -> "up" phase
if mouseup was over another Button, it goes into "over" phase
Click resumed:
roll over Button -> "over" phase
mouse down on Button -> "down" phase, dispatch "buttonDown"
roll out of Button -> "over" phase
maybe roll over and out of other objects -> dispatch events from them
roll over Button -> "down" phase
maybe roll off the stage, or off and back on
maybe repeat last four steps
mouse up while over Button -> "over" phase, dispatch "click"
roll out of Button -> "up" phase
Drag over and out
mouse down while out of Button
roll over Button -> stay in "up" phase
roll out of Button -> stay in "up" phase
Drag over and up
mouse down while out of Button
roll over Button -> stay in "up" phase
mouse up while over Button -> "over" phase
continue with step 2 of first three sequences above
*/
/**
* The default handler for the <code>MouseEvent.ROLL_OVER</code> event.
*
* @param The event object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function rollOverHandler(event:MouseEvent):void
{
// Note that we don't prevent the propagation of rollOver
// from a disabled Button.
// Developers may want to detect this low-level event.
if (phase == ButtonPhase.UP)
{
if (event.buttonDown)
return;
phase = ButtonPhase.OVER;
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
}
else if (phase == ButtonPhase.OVER)
{
phase = ButtonPhase.DOWN;
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
// The mouse is back over the Button and the Button is down again,
// so resume auto-repeating.
if (autoRepeatTimer)
autoRepeatTimer.start();
}
}
/**
* The default handler for the <code>MouseEvent.ROLL_OUT</code> event.
*
* @param The event object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function rollOutHandler(event:MouseEvent):void
{
// Note that we don't prevent the propagation of rollOut
// from a disabled Button.
// Developers may want to detect this low-level event.
if (phase == ButtonPhase.OVER)
{
phase = ButtonPhase.UP;
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
}
else if (phase == ButtonPhase.DOWN && !stickyHighlighting)
{
phase = ButtonPhase.OVER;
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
// If the Button no longer looks "down", it shouldn't auto-repeat.
if (autoRepeatTimer)
autoRepeatTimer.stop();
}
}
/**
* The default handler for the <code>MouseEvent.MOUSE_DOWN</code> event.
*
* @param The event object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseDownHandler(event:MouseEvent):void
{
if (!enabled)
return;
// Note that we don't prevent the propagation of mouseDown
// from a disabled Button.
// Developers may want to detect this low-level event.
// In case the user drags out of the Button and then releases
// the mouse button, we need to get the mouseUp.
// To accomplish this, we temporarily place a capture-phase
// mouseUp handler on the SystemManager.
// We also place a mouseLeave handler on the stage
// in case the user drags off the stage and releases the mouse.
// These handlers are removed in buttonReleased().
systemManager.getSandboxRoot().addEventListener(
MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true);
systemManager.getSandboxRoot().addEventListener(
SandboxMouseEvent.MOUSE_UP_SOMEWHERE, stage_mouseLeaveHandler);
buttonPressed();
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
}
/**
* The default handler for the <code>MouseEvent.MOUSE_UP</code> event.
*
* @param The event object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function mouseUpHandler(event:MouseEvent):void
{
if (!enabled)
return;
// Note that we don't prevent the propagation of mouseUp
// from a disabled Button.
// Developers may want to detect this low-level event.
phase = ButtonPhase.OVER;
buttonReleased();
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
if (!toggle)
event.updateAfterEvent();
}
/**
* The default handler for the <code>MouseEvent.CLICK</code> event.
*
* @param The event object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function clickHandler(event:MouseEvent):void
{
if (!enabled)
{
// Prevent the propagation of click from a disabled Button.
// This is conceptually a higher-level event and
// developers will expect their click handlers not to fire
// if the Button is disabled.
event.stopImmediatePropagation();
return;
}
if (toggle)
{
setSelected(!selected);
event.updateAfterEvent();
}
}
/**
* @private
* This method is called when the user has pressed the Button
* and then released the mouse button anywhere.
* It's purpose is to get the mouseUp event when the user has
* dragged out of the Button before releasing.
* However, it gets an inside mouseUp as well;
* we have to check for this case becuase mouseHandler()
* already deals with it..
*/
private function systemManager_mouseUpHandler(event:MouseEvent):void
{
// If the mouse button was released over the Button,
// mouseUpHandler() will handle it, so do nothing.
if (contains(DisplayObject(event.target)))
return;
phase = ButtonPhase.UP;
buttonReleased();
// Force a "render" event, which will cause updateDisplayList()
// to show the appropriate skin for the new phase.
event.updateAfterEvent();
}
/**
* @private
* This method is called when the user has pressed the Button,
* dragged of the stage, and released the mouse button.
*/
private function stage_mouseLeaveHandler(event:Event):void
{
phase = ButtonPhase.UP;
buttonReleased();
}
/**
* @private
*/
private function autoRepeatTimer_timerDelayHandler(event:Event):void
{
if (!enabled)
return;
dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));
if (autoRepeat)
{
autoRepeatTimer.reset();
autoRepeatTimer.removeEventListener(
TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
autoRepeatTimer.delay = getStyle("repeatInterval");
autoRepeatTimer.addEventListener(
TimerEvent.TIMER, autoRepeatTimer_timerHandler);
autoRepeatTimer.start();
}
}
/**
* @private
*/
private function autoRepeatTimer_timerHandler(event:Event):void
{
if (!enabled)
return;
dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));
}
}
}