//////////////////////////////////////////////////////////////////////////////// // // 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 spark.components { import flash.display.DisplayObject; import flash.display.InteractiveObject; import flash.events.Event; import flash.events.FocusEvent; import flash.events.KeyboardEvent; import flash.globalization.LocaleID; import flash.globalization.NumberFormatter; import mx.core.IIMESupport; import mx.core.mx_internal; import mx.events.FlexEvent; import mx.managers.IFocusManagerComponent; use namespace mx_internal; //-------------------------------------- // Styles //-------------------------------------- include "../styles/metadata/BasicInheritingTextStyles.as" include "../styles/metadata/AdvancedInheritingTextStyles.as" include "../styles/metadata/SelectionFormatTextStyles.as" /** * The alpha of the border for this component. * * @default 0.5 * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="borderAlpha", type="Number", inherit="no", theme="spark")] /** * The color of the border for this component. * * @default 0x000000 * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="borderColor", type="uint", format="Color", inherit="no", theme="spark")] /** * Controls the visibility of the border for this component. * * @default true * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="borderVisible", type="Boolean", inherit="no", theme="spark")] /** * The alpha of the content background for this component. * * @default 1 * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="contentBackgroundAlpha", type="Number", inherit="yes", theme="spark")] /** * @copy spark.components.supportClasses.GroupBase#style:contentBackgroundColor * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ [Style(name="contentBackgroundColor", type="uint", format="Color", inherit="yes", theme="spark")] //-------------------------------------- // Other metadata //-------------------------------------- [AccessibilityClass(implementation="spark.accessibility.NumericStepperAccImpl")] [DefaultTriggerEvent("change")] [IconFile("NumericStepper.png")] /** * Because this component does not define a skin for the mobile theme, Adobe * recommends that you not use it in a mobile application. Alternatively, you * can define your own mobile skin for the component. For more information, * see Basics of mobile skinning. */ [DiscouragedForProfile("mobileDevice")] /** * The NumericStepper control lets you select * a number from an ordered set. * The NumericStepper provides the same functionality as * the Spinner component, but adds a TextInput control * so that you can directly edit the value of the component, * rather than modifying it by using the control's arrow buttons. * *

The NumericStepper control consists of a single-line * input text field and a pair of arrow buttons * for stepping through the possible values. * The Up Arrow and Down Arrow keys and the mouse wheel also cycle through * the values. * An input value is committed when * the user presses the Enter key, removes focus from the * component, or steps the NumericStepper by pressing an arrow button * or by calling the changeValueByStep() method.

* *

To use this component in a list-based component, such as a List or DataGrid, * create an item renderer. * For information about creating an item renderer, see * * Custom Spark item renderers.

* *

The NumericStepper control has the following default characteristics:

* * * * * * * * * * * * * * * * * * * * * *
CharacteristicDescription
Default size53 pixels wide by 23 pixels high
Minimum size40 pixels wide and 40 pixels high
Maximum size10000 pixels wide and 10000 pixels high
Default skin classesspark.skins.spark.NumericStepperSkin *

spark.skins.spark.NumericStepperTextInputSkin

* * @mxml * *

The <s:NumericStepper> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:

* *
 *  <s:NumericStepper
 *
 *    Properties
 *    imeMode="null"
 *    maxChars="0"
 *    maximum="10"
 *    valueFormatFunction=""
 *    valueParseFunction=""
 *
 *    Styles
 *
*    alignmentBaseline="USE_DOMINANT_BASELINE"
*    baselineShift="0.0"
*    blockProgression="TB"
*    borderAlpha="0.5"
*    borderColor="0x000000"
*    borderVisible="true"
*    breakOpportunity="AUTO"
*    cffHinting="HORIZONTAL_STEM"
*    color="0"
*    contentBackgroundAlpha="1.0"
*    contentBackgroundColor="0xFFFFFF"
*    clearFloats="none"
*    digitCase="DEFAULT"
*    digitWidth="DEFAULT"
*    direction="LTR"
*    dominantBaseline="AUTO"
*    firstBaselineOffset="AUTO"
*    focusedTextSelectionColor=""
*    fontFamily="Arial"
*    fontLookup="DEVICE"
*    fontSize="12"
*    fontStyle="NORMAL"
*    fontWeight="NORMAL"
*    inactiveTextSelection=""
*    justificationRule="AUTO"
*    justificationStyle="AUTO"
*    kerning="AUTO"
*    leadingModel="AUTO"
*    ligatureLevel="COMMON"
*    lineHeight="120%"
*    lineThrough="false"
*    listAutoPadding="40"
*    listStylePosition="outside"
*    listStyleType="disc"
*    locale="en"
*    paragraphEndIndent="0"
*    paragraphSpaceAfter="0"
*    paragraphSpaceBefore="0"
*    paragraphStartIndent="0"
*    renderingMode="CFF"
*    tabStops="null"
*    textAlign="START"
*    textAlignLast="START"
*    textAlpha="1"
*    textDecoration="NONE"
*    textIndent="0"
*    textJustify="INTER_WORD"
*    textRotation="AUTO"
*    trackingLeft="0"
*    trackingRight="0"
*    typographicCase="DEFAULT"
*    unfocusedTextSelectionColor=""
*    whiteSpaceCollapse="COLLAPSE"
*    wordSpacing="100%,50%,150%"
*  />
*  
* * @see spark.components.Spinner * @see spark.skins.spark.NumericStepperSkin * @see spark.skins.spark.NumericStepperTextInputSkin * * @includeExample examples/NumericStepperExample.mxml * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public class NumericStepper extends Spinner implements IFocusManagerComponent, IIMESupport { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class mixins // //-------------------------------------------------------------------------- /** * @private * Placeholder for mixin by SpinnerAccImpl. */ mx_internal static var createAccessibilityImplementation:Function; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function NumericStepper() { super(); maximum = 10; } //-------------------------------------------------------------------------- // // Skin parts // //-------------------------------------------------------------------------- [SkinPart(required="true")] /** * A skin part that defines a TextInput control * which allows a user to edit the value of * the NumericStepper component. * The value is rounded and committed * when the user presses enter, focuses out of * the NumericStepper, or steps the NumericStepper. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var textDisplay:TextInput; //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var dataFormatter:NumberFormatter; //-------------------------------------------------------------------------- // // Overridden properties: UIComponent // //-------------------------------------------------------------------------- //---------------------------------- // baselinePosition //---------------------------------- /** * @private */ override public function get baselinePosition():Number { return getBaselinePositionForPart(textDisplay); } //-------------------------------------------------------------------------- // // Overridden Properties: Range // //-------------------------------------------------------------------------- //--------------------------------- // maximum //--------------------------------- /** * @private */ private var maxChanged:Boolean = false; [Inspectable(category="General", defaultValue="10.0")] /** * Number which represents the maximum value possible for * value. If the values for either * minimum or value are greater * than maximum, they will be changed to * reflect the new maximum * * @default 10 * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override public function set maximum(value:Number):void { maxChanged = true; super.maximum = value; } //--------------------------------- // stepSize //--------------------------------- /** * @private */ private var stepSizeChanged:Boolean = false; [Inspectable(category="General", defaultValue="1.0", minValue="0.0")] /** * @private */ override public function set stepSize(value:Number):void { stepSizeChanged = true; super.stepSize = value; } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // enableIME //---------------------------------- /** * A flag that indicates whether the IME should * be enabled when the component receives focus. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get enableIME():Boolean { if (textDisplay && textDisplay.textDisplay) return textDisplay.textDisplay.editable; // most numeric steppers will be editable return true; } //---------------------------------- // maxChars //---------------------------------- /** * @private * Storage for the maxChars property. */ private var _maxChars:int = 0; /** * @private */ private var maxCharsChanged:Boolean = false; [Inspectable(category="General", defaultValue="0")] /** * The maximum number of characters that can be entered in the field. * A value of 0 means that any number of characters can be entered. * * @default 0 * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get maxChars():int { return _maxChars; } /** * @private */ public function set maxChars(value:int):void { if (value == _maxChars) return; _maxChars = value; maxCharsChanged = true; invalidateProperties(); } //--------------------------------- // valueFormatFunction //--------------------------------- /** * @private */ private var _valueFormatFunction:Function; /** * @private */ private var valueFormatFunctionChanged:Boolean; /** * Callback function that formats the value displayed * in the skin's textDisplay property. * The function takes a single Number as an argument * and returns a formatted String. * *

The function has the following signature:

*
     *  funcName(value:Number):String
     *  
* @default undefined * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get valueFormatFunction():Function { return _valueFormatFunction; } /** * @private */ public function set valueFormatFunction(value:Function):void { _valueFormatFunction = value; valueFormatFunctionChanged = true; invalidateProperties(); } //--------------------------------- // valueParseFunction //--------------------------------- /** * @private */ private var _valueParseFunction:Function; /** * @private */ private var valueParseFunctionChanged:Boolean; /** * Callback function that extracts the numeric * value from the displayed value in the * skin's textDisplay field. * * The function takes a single String as an argument * and returns a Number. * *

The function has the following signature:

*
     *  funcName(value:String):Number
     *  
* @default undefined * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get valueParseFunction():Function { return _valueParseFunction; } /** * @private */ public function set valueParseFunction(value:Function):void { _valueParseFunction = value; valueParseFunctionChanged = true; invalidateProperties(); } //---------------------------------- // imeMode //---------------------------------- /** * @private */ private var _imeMode:String = null; /** * @private */ private var imeModeChanged:Boolean = false; /** * Specifies the IME (Input Method Editor) mode. * The IME enables users to enter text in Chinese, Japanese, and Korean. * Flex sets the specified IME mode when the control gets the focus * and sets it back to previous value when the control loses the focus. * *

The flash.system.IMEConversionMode class defines constants for the * valid values for this property. * You can also specify null to specify no IME.

* * @see flash.system.IMEConversionMode * * @default null * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get imeMode():String { return _imeMode; } /** * @private */ public function set imeMode(value:String):void { _imeMode = value; imeModeChanged = true; invalidateProperties(); } //-------------------------------------------------------------------------- // // Overridden methods // //-------------------------------------------------------------------------- /** * @private */ override protected function initializeAccessibility():void { if (NumericStepper.createAccessibilityImplementation != null) NumericStepper.createAccessibilityImplementation(this); } /** * @private */ override protected function commitProperties():void { super.commitProperties(); if (maxChanged || stepSizeChanged || valueFormatFunctionChanged) { textDisplay.widthInChars = calculateWidestValue(); maxChanged = false; stepSizeChanged = false; if (valueFormatFunctionChanged) { applyDisplayFormatFunction(); valueFormatFunctionChanged = false; } } if (valueParseFunctionChanged) { commitTextInput(false); valueParseFunctionChanged = false; } if (maxCharsChanged) { textDisplay.maxChars = _maxChars; maxCharsChanged = false; } if (imeModeChanged) { textDisplay.imeMode = _imeMode; imeModeChanged = false; } } /** * @private */ override protected function partAdded(partName:String, instance:Object):void { super.partAdded(partName, instance); if (instance == textDisplay) { textDisplay.addEventListener(FlexEvent.ENTER, textDisplay_enterHandler); textDisplay.addEventListener(FocusEvent.FOCUS_OUT, textDisplay_focusOutHandler); textDisplay.focusEnabled = false; textDisplay.maxChars = _maxChars; // Restrict to digits, minus sign, decimal point, and comma textDisplay.restrict = "0-9\\-\\.\\,"; textDisplay.text = value.toString(); // Set the the textDisplay to be wide enough to display // widest possible value. textDisplay.widthInChars = calculateWidestValue(); } } /** * @private */ override protected function partRemoved(partName:String, instance:Object):void { super.partRemoved(partName, instance); if (instance == textDisplay) { textDisplay.removeEventListener(FlexEvent.ENTER, textDisplay_enterHandler); } } /** * @private */ override public function setFocus():void { if (stage) { stage.focus = textDisplay.textDisplay as InteractiveObject; // Since the API ignores the visual editable and selectable // properties make sure the selection should be set first. if (textDisplay.textDisplay && (textDisplay.textDisplay.editable || textDisplay.textDisplay.selectable)) { textDisplay.textDisplay.selectAll(); } } } /** * @private */ override protected function isOurFocus(target:DisplayObject):Boolean { return target == textDisplay.textDisplay; } /** * @private */ override protected function setValue(newValue:Number):void { super.setValue(newValue); applyDisplayFormatFunction(); } /** * @private * Calls commitTextInput() before stepping. */ override public function changeValueByStep(increase:Boolean = true):void { commitTextInput(); super.changeValueByStep(increase); } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private * Commits the current text of textDisplay * to the value property. * This method uses the nearestValidValue() method * to round the input value to the closest valid value. * Valid values are defined by the sum of the minimum * with integer multiples of the snapInterval. It is also * constrained by and includes the maximum property. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ private function commitTextInput(dispatchChange:Boolean = false):void { var inputValue:Number; var prevValue:Number = value; if (valueParseFunction != null) { inputValue = valueParseFunction(textDisplay.text); } else { if (dataFormatter == null) dataFormatter = new NumberFormatter(LocaleID.DEFAULT); inputValue = dataFormatter.parseNumber(textDisplay.text); } if ((textDisplay.text && textDisplay.text.length != value.toString().length) || textDisplay.text == "" || (inputValue != value && (Math.abs(inputValue - value) >= 0.000001 || isNaN(inputValue)))) { setValue(nearestValidValue(inputValue, snapInterval)); // Dispatch valueCommit if the display needs to change. if (value == prevValue && inputValue != prevValue) dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); } if (dispatchChange) { if (value != prevValue) dispatchEvent(new Event(Event.CHANGE)); } } /** * @private * Helper method that returns a number corresponding * to the length of the maximum value displayable in * the textDisplay. */ private function calculateWidestValue():Number { var widestNumber:Number = minimum.toString().length > maximum.toString().length ? minimum : maximum; widestNumber += stepSize; if (valueFormatFunction != null) return valueFormatFunction(widestNumber).length; else return widestNumber.toString().length; } /** * @private * Helper method that applies the valueFormatFunction */ private function applyDisplayFormatFunction():void { if (valueFormatFunction != null) textDisplay.text = valueFormatFunction(value); else textDisplay.text = value.toString(); } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private */ override protected function focusInHandler(event:FocusEvent):void { super.focusInHandler(event); addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler, true); } /** * @private */ override protected function focusOutHandler(event:FocusEvent):void { super.focusOutHandler(event); removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler, true); } /** * @private * When the enter key is pressed, NumericStepper commits the * text currently displayed. */ private function textDisplay_enterHandler(event:Event):void { commitTextInput(true); } /** * @private * When the enter key is pressed, NumericStepper commits the * text currently displayed. */ private function textDisplay_focusOutHandler(event:Event):void { commitTextInput(true); } } }