scaffold.libs_as.feathers.controls.TextArea.as Maven / Gradle / Ivy
/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.
This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls
{
import feathers.controls.text.ITextEditorViewPort;
import feathers.controls.text.TextFieldTextEditorViewPort;
import feathers.core.INativeFocusOwner;
import feathers.core.IStateContext;
import feathers.core.IStateObserver;
import feathers.core.PopUpManager;
import feathers.core.PropertyProxy;
import feathers.events.FeathersEventType;
import feathers.skins.IStyleProvider;
import flash.display.InteractiveObject;
import flash.geom.Point;
import flash.ui.Mouse;
import flash.ui.MouseCursor;
import starling.display.DisplayObject;
import starling.events.Event;
import starling.events.KeyboardEvent;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
/**
* Dispatched when the text area's text
property changes.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType starling.events.Event.CHANGE
*/
[Event(name="change",type="starling.events.Event")]
/**
* Dispatched when the display object's state changes.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.STATE_CHANGE
*
* @see #currentState
*/
[Event(name="stateChange",type="starling.events.Event")]
/**
* A text entry control that allows users to enter and edit multiple lines
* of uniformly-formatted text with the ability to scroll.
*
* The following example sets the text in a text area, selects the text,
* and listens for when the text value changes:
*
*
* var textArea:TextArea = new TextArea();
* textArea.text = "Hello\nWorld"; //it's multiline!
* textArea.selectRange( 0, textArea.text.length );
* textArea.addEventListener( Event.CHANGE, textArea_changeHandler );
* this.addChild( textArea );
*
* @see ../../../help/text-area.html How to use the Feathers TextArea component
* @see feathers.controls.TextInput
*/
public class TextArea extends Scroller implements INativeFocusOwner, IStateContext
{
/**
* @private
*/
private static const HELPER_POINT:Point = new Point();
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.AUTO
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_POLICY_AUTO:String = "auto";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.ON
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_POLICY_ON:String = "on";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.OFF
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_POLICY_OFF:String = "off";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FLOAT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_BAR_DISPLAY_MODE_FLOAT:String = "float";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FIXED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_BAR_DISPLAY_MODE_FIXED:String = "fixed";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FIXED_FLOAT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_BAR_DISPLAY_MODE_FIXED_FLOAT:String = "fixedFloat";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.NONE
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const SCROLL_BAR_DISPLAY_MODE_NONE:String = "none";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.RelativePosition.RIGHT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_SCROLL_BAR_POSITION_RIGHT:String = "right";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.RelativePosition.LEFT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_SCROLL_BAR_POSITION_LEFT:String = "left";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.TOUCH
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const INTERACTION_MODE_TOUCH:String = "touch";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.MOUSE
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const INTERACTION_MODE_MOUSE:String = "mouse";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.TOUCH_AND_SCROLL_BARS
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const INTERACTION_MODE_TOUCH_AND_SCROLL_BARS:String = "touchAndScrollBars";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.Direction.VERTICAL
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const MOUSE_WHEEL_SCROLL_DIRECTION_VERTICAL:String = "vertical";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.Direction.HORIZONTAL
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const MOUSE_WHEEL_SCROLL_DIRECTION_HORIZONTAL:String = "horizontal";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.DecelerationRate.NORMAL
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const DECELERATION_RATE_NORMAL:Number = 0.998;
/**
* @private
* DEPRECATED: Replaced by feathers.controls.DecelerationRate.FAST
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const DECELERATION_RATE_FAST:Number = 0.99;
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.ENABLED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_ENABLED:String = "enabled";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.DISABLED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_DISABLED:String = "disabled";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.FOCUSED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_FOCUSED:String = "focused";
/**
* The default value added to the styleNameList
of the text
* editor.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR:String = "feathers-text-area-text-editor";
/**
* The default value added to the styleNameList
of the
* error callout.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT:String = "feathers-text-input-error-callout";
/**
* @private
*/
protected static const INVALIDATION_FLAG_ERROR_CALLOUT_FACTORY:String = "errorCalloutFactory";
/**
* The default IStyleProvider
for all TextArea
* components.
*
* @default null
* @see feathers.core.FeathersControl#styleProvider
*/
public static var globalStyleProvider:IStyleProvider;
/**
* Constructor.
*/
public function TextArea()
{
super();
this._measureViewPort = false;
this.addEventListener(TouchEvent.TOUCH, textArea_touchHandler);
this.addEventListener(Event.REMOVED_FROM_STAGE, textArea_removedFromStageHandler);
}
/**
* @private
*/
protected var textEditorViewPort:ITextEditorViewPort;
/**
* The TextCallout
that displays the value of the
* errorString
property. The value may be null
* if there is no current error string or if the text area doesn't have
* focus.
*
* For internal use in subclasses.
*/
protected var callout:TextCallout;
/**
* The value added to the styleNameList
of the text editor.
* This variable is protected
so that sub-classes can
* customize the text editor style name in their constructors instead of
* using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var textEditorStyleName:String = DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR;
/**
* The value added to the styleNameList
of the error
* callout. This variable is protected
so that sub-classes
* can customize the prompt text renderer style name in their
* constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var errorCalloutStyleName:String = DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT;
/**
* @private
*/
protected var _textEditorHasFocus:Boolean = false;
/**
* A text editor may be an INativeFocusOwner
, so we need to
* return the value of its nativeFocus
property. If not,
* then we return null
.
*
* @see feathers.core.INativeFocusOwner
*/
public function get nativeFocus():InteractiveObject
{
if(this.textEditorViewPort is INativeFocusOwner)
{
return INativeFocusOwner(this.textEditorViewPort).nativeFocus;
}
return null;
}
/**
* @private
*/
protected var _isWaitingToSetFocus:Boolean = false;
/**
* @private
*/
protected var _pendingSelectionStartIndex:int = -1;
/**
* @private
*/
protected var _pendingSelectionEndIndex:int = -1;
/**
* @private
*/
protected var _textAreaTouchPointID:int = -1;
/**
* @private
*/
protected var _oldMouseCursor:String = null;
/**
* @private
*/
protected var _ignoreTextChanges:Boolean = false;
/**
* @private
*/
override protected function get defaultStyleProvider():IStyleProvider
{
return TextArea.globalStyleProvider;
}
/**
* @private
*/
override public function get isFocusEnabled():Boolean
{
if(this._isEditable)
{
//the behavior is different when editable.
return this._isEnabled && this._isFocusEnabled;
}
return super.isFocusEnabled;
}
/**
* When the FocusManager
isn't enabled, hasFocus
* can be used instead of FocusManager.focus == textArea
* to determine if the text area has focus.
*/
public function get hasFocus():Boolean
{
if(!this._focusManager)
{
return this._textEditorHasFocus;
}
return this._hasFocus;
}
/**
* @private
*/
override public function set isEnabled(value:Boolean):void
{
super.isEnabled = value;
this.refreshState();
}
/**
* @private
*/
protected var _currentState:String = TextInputState.ENABLED;
/**
* The current state of the text area.
*
* @see feathers.controls.TextInputState
* @see #event:stateChange feathers.events.FeathersEventType.STATE_CHANGE
*/
public function get currentState():String
{
return this._currentState;
}
/**
* @private
*/
protected var _text:String = "";
/**
* The text displayed by the text area. The text area dispatches
* Event.CHANGE
when the value of the text
* property changes for any reason.
*
* In the following example, the text area's text is updated:
*
*
* textArea.text = "Hello World";
*
* @see #event:change
*
* @default ""
*/
public function get text():String
{
return this._text;
}
/**
* @private
*/
public function set text(value:String):void
{
if(!value)
{
//don't allow null or undefined
value = "";
}
if(this._text == value)
{
return;
}
this._text = value;
this.invalidate(INVALIDATION_FLAG_DATA);
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _maxChars:int = 0;
/**
* The maximum number of characters that may be entered.
*
* In the following example, the text area's maximum characters is
* specified:
*
*
* textArea.maxChars = 10;
*
* @default 0
*/
public function get maxChars():int
{
return this._maxChars;
}
/**
* @private
*/
public function set maxChars(value:int):void
{
if(this._maxChars == value)
{
return;
}
this._maxChars = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _restrict:String;
/**
* Limits the set of characters that may be entered.
*
* In the following example, the text area's allowed characters are
* restricted:
*
*
* textArea.restrict = "0-9;
*
* @default null
*/
public function get restrict():String
{
return this._restrict;
}
/**
* @private
*/
public function set restrict(value:String):void
{
if(this._restrict == value)
{
return;
}
this._restrict = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _isEditable:Boolean = true;
/**
* Determines if the text area is editable. If the text area is not
* editable, it will still appear enabled.
*
* In the following example, the text area is not editable:
*
*
* textArea.isEditable = false;
*
* @default true
*/
public function get isEditable():Boolean
{
return this._isEditable;
}
/**
* @private
*/
public function set isEditable(value:Boolean):void
{
if(this._isEditable == value)
{
return;
}
this._isEditable = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _errorString:String = null;
/**
* Error text to display in a Callout
when the text area
* has focus. When this value is not null
the text area's
* state is changed to TextInputState.ERROR
.
*
* An empty string will change the background, but no
* Callout
will appear on focus.
*
* To clear an error, the errorString
property must be set
* to null
*
* The following example displays an error string:
*
*
* textArea.errorString = "Something is wrong";
*
* @default null
*
* @see #currentState
*/
public function get errorString():String
{
return this._errorString;
}
/**
* @private
*/
public function set errorString(value:String):void
{
if(this._errorString === value)
{
return;
}
this._errorString = value;
this.refreshState();
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _stateToSkin:Object = {};
/**
* A display object displayed behind the text area's content when it
* is disabled.
*
* In the following example, the text area's disabled background skin
* is specified:
*
*
* textArea.isEnabled = false;
* textArea.backgroundDisabledSkin = new Image( texture );
*
* @default null
*/
override public function get backgroundDisabledSkin():DisplayObject
{
return this.getSkinForState(TextInputState.DISABLED);
}
/**
* @private
*/
override public function set backgroundDisabledSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.DISABLED, value);
}
/**
* A display object displayed behind the text area's content when it
* has focus.
*
* In the following example, the text area's focused background skin is
* specified:
*
*
* textArea.backgroundFocusedSkin = new Image( texture );
*
* @default null
*/
public function get backgroundFocusedSkin():DisplayObject
{
return this.getSkinForState(TextInputState.FOCUSED);
}
/**
* @private
*/
public function set backgroundFocusedSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.FOCUSED, value);
}
/**
* The skin used for the text area's error state. If null
,
* then backgroundSkin
is used instead.
*
* The following example gives the text area a skin for the error state:
*
*
* textArea.backgroundErrorSkin = new Image( texture );
*
* @default null
*/
public function get backgroundErrorSkin():DisplayObject
{
return this.getSkinForState(TextInputState.ERROR);
}
/**
* @private
*/
public function set backgroundErrorSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.ERROR, value);
}
/**
* @private
*/
protected var _stateToSkinFunction:Function;
/**
* DEPRECATED: Create a feathers.skins.ImageSkin
instead,
* and pass to the backgroundSkin
property.
*
* DEPRECATION WARNING: This property is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public function get stateToSkinFunction():Function
{
return this._stateToSkinFunction;
}
/**
* @private
*/
public function set stateToSkinFunction(value:Function):void
{
if(this._stateToSkinFunction == value)
{
return;
}
this._stateToSkinFunction = value;
this.invalidate(INVALIDATION_FLAG_SKIN);
}
/**
* @private
*/
protected var _textEditorFactory:Function;
/**
* A function used to instantiate the text editor view port. If
* null
, a TextFieldTextEditorViewPort
will
* be instantiated. The text editor must be an instance of
* ITextEditorViewPort
. This factory can be used to change
* properties on the text editor view port when it is first created. For
* instance, if you are skinning Feathers components without a theme,
* you might use this factory to set styles on the text editor view
* port.
*
* The factory should have the following function signature:
* function():ITextEditorViewPort
*
* In the following example, a custom text editor factory is passed
* to the text area:
*
*
* textArea.textEditorFactory = function():ITextEditorViewPort
* {
* return new TextFieldTextEditorViewPort();
* };
*
* @default null
*
* @see feathers.controls.text.ITextEditorViewPort
*/
public function get textEditorFactory():Function
{
return this._textEditorFactory;
}
/**
* @private
*/
public function set textEditorFactory(value:Function):void
{
if(this._textEditorFactory == value)
{
return;
}
this._textEditorFactory = value;
this.invalidate(INVALIDATION_FLAG_TEXT_EDITOR);
}
/**
* @private
*/
protected var _customTextEditorStyleName:String;
/**
* A style name to add to the text area's text editor sub-component.
* Typically used by a theme to provide different styles to different
* text areas.
*
* In the following example, a custom text editor style name is
* passed to the text area:
*
*
* textArea.customTextEditorStyleName = "my-custom-text-area-text-editor";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( TextFieldTextEditorViewPort ).setFunctionForStyleName( "my-custom-text-area-text-editor", setCustomTextAreaTextEditorStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR
* @see feathers.core.FeathersControl#styleNameList
* @see #textEditorFactory
*/
public function get customTextEditorStyleName():String
{
return this._customTextEditorStyleName;
}
/**
* @private
*/
public function set customTextEditorStyleName(value:String):void
{
if(this._customTextEditorStyleName == value)
{
return;
}
this._customTextEditorStyleName = value;
this.invalidate(INVALIDATION_FLAG_TEXT_RENDERER);
}
/**
* @private
*/
protected var _textEditorProperties:PropertyProxy;
/**
* An object that stores properties for the text area's text editor
* sub-component, and the properties will be passed down to the
* text editor when the text area validates. The available properties
* depend on which ITextEditorViewPort
implementation is
* returned by textEditorFactory
. Refer to
* feathers.controls.text.ITextEditorViewPort
* for a list of available text editor implementations for text area.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a textEditorFactory
function
* instead of using textEditorProperties
will result in
* better performance.
*
* In the following example, the text input's text editor properties
* are specified (this example assumes that the text editor is a
* TextFieldTextEditorViewPort
):
*
*
* textArea.textEditorProperties.textFormat = new TextFormat( "Source Sans Pro", 16, 0x333333);
* textArea.textEditorProperties.embedFonts = true;
*
* @default null
*
* @see #textEditorFactory
* @see feathers.controls.text.ITextEditorViewPort
*/
public function get textEditorProperties():Object
{
if(!this._textEditorProperties)
{
this._textEditorProperties = new PropertyProxy(childProperties_onChange);
}
return this._textEditorProperties;
}
/**
* @private
*/
public function set textEditorProperties(value:Object):void
{
if(this._textEditorProperties == value)
{
return;
}
if(!value)
{
value = new PropertyProxy();
}
if(!(value is PropertyProxy))
{
var newValue:PropertyProxy = new PropertyProxy();
for(var propertyName:String in value)
{
newValue[propertyName] = value[propertyName];
}
value = newValue;
}
if(this._textEditorProperties)
{
this._textEditorProperties.removeOnChangeCallback(childProperties_onChange);
}
this._textEditorProperties = PropertyProxy(value);
if(this._textEditorProperties)
{
this._textEditorProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _customErrorCalloutStyleName:String;
/**
* A style name to add to the text area's error callout sub-component.
* Typically used by a theme to provide different styles to different
* text areas.
*
* In the following example, a custom error callout style name
* is passed to the text area:
*
*
* textArea.customErrorCalloutStyleName = "my-custom-text-area-error-callout";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( Callout ).setFunctionForStyleName( "my-custom-text-area-error-callout", setCustomTextAreaErrorCalloutStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT
* @see feathers.core.FeathersControl#styleNameList
*/
public function get customErrorCalloutStyleName():String
{
return this._customErrorCalloutStyleName;
}
/**
* @private
*/
public function set customErrorCalloutStyleName(value:String):void
{
if(this._customErrorCalloutStyleName == value)
{
return;
}
this._customErrorCalloutStyleName = value;
this.invalidate(INVALIDATION_FLAG_ERROR_CALLOUT_FACTORY);
}
/**
* @inheritDoc
*/
override public function showFocus():void
{
if(!this._focusManager || this._focusManager.focus != this)
{
return;
}
this.selectRange(0, this._text.length);
super.showFocus();
}
/**
* Focuses the text area control so that it may be edited.
*/
public function setFocus():void
{
if(this._textEditorHasFocus)
{
return;
}
if(this.textEditorViewPort)
{
this._isWaitingToSetFocus = false;
this.textEditorViewPort.setFocus();
}
else
{
this._isWaitingToSetFocus = true;
this.invalidate(INVALIDATION_FLAG_SELECTED);
}
}
/**
* Manually removes focus from the text area control.
*/
public function clearFocus():void
{
this._isWaitingToSetFocus = false;
if(!this.textEditorViewPort || !this._textEditorHasFocus)
{
return;
}
this.textEditorViewPort.clearFocus();
}
/**
* Sets the range of selected characters. If both values are the same,
* or the end index is -1
, the text insertion position is
* changed and nothing is selected.
*/
public function selectRange(startIndex:int, endIndex:int = -1):void
{
if(endIndex < 0)
{
endIndex = startIndex;
}
if(startIndex < 0)
{
throw new RangeError("Expected start index greater than or equal to 0. Received " + startIndex + ".");
}
if(endIndex > this._text.length)
{
throw new RangeError("Expected start index less than " + this._text.length + ". Received " + endIndex + ".");
}
if(this.textEditorViewPort)
{
this._pendingSelectionStartIndex = -1;
this._pendingSelectionEndIndex = -1;
this.textEditorViewPort.selectRange(startIndex, endIndex);
}
else
{
this._pendingSelectionStartIndex = startIndex;
this._pendingSelectionEndIndex = endIndex;
this.invalidate(INVALIDATION_FLAG_SELECTED);
}
}
/**
* @private
*/
override public function dispose():void
{
//we don't dispose it if the text area is the parent because it'll
//already get disposed in super.dispose()
for(var state:String in this._stateToSkin)
{
var skin:DisplayObject = this._stateToSkin[state] as DisplayObject;
if(skin !== null && skin.parent !== this)
{
skin.dispose();
}
}
super.dispose();
}
/**
* Gets the skin to be used by the text area when its
* currentState
property matches the specified state value.
*
* If a skin is not defined for a specific state, returns
* null
.
*
* @see #setSkinForState()
* @see feathers.controls.TextInputState
*/
public function getSkinForState(state:String):DisplayObject
{
return this._stateToSkin[state] as DisplayObject;
}
/**
* Sets the skin to be used by the text area when its
* currentState
property matches the specified state value.
*
* If a skin is not defined for a specific state, the value of the
* backgroundSkin
property will be used instead.
*
* @see #backgroundSkin
* @see #getSkinForState()
* @see feathers.controls.TextInputState
*/
public function setSkinForState(state:String, skin:DisplayObject):void
{
if(skin !== null)
{
this._stateToSkin[state] = skin;
}
else
{
delete this._stateToSkin[state];
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
override protected function draw():void
{
var textEditorInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_TEXT_EDITOR);
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE);
if(textEditorInvalid)
{
this.createTextEditor();
}
if(textEditorInvalid || stylesInvalid)
{
this.refreshTextEditorProperties();
}
if(textEditorInvalid || dataInvalid)
{
var oldIgnoreTextChanges:Boolean = this._ignoreTextChanges;
this._ignoreTextChanges = true;
this.textEditorViewPort.text = this._text;
this._ignoreTextChanges = oldIgnoreTextChanges;
}
if(textEditorInvalid || stateInvalid)
{
this.textEditorViewPort.isEnabled = this._isEnabled;
if(!this._isEnabled && Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
super.draw();
this.doPendingActions();
}
/**
* Creates and adds the textEditorViewPort
sub-component and
* removes the old instance, if one exists.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #textEditorViewPort
* @see #textEditorFactory
*/
protected function createTextEditor():void
{
if(this.textEditorViewPort)
{
this.textEditorViewPort.removeEventListener(Event.CHANGE, textEditor_changeHandler);
this.textEditorViewPort.removeEventListener(FeathersEventType.FOCUS_IN, textEditor_focusInHandler);
this.textEditorViewPort.removeEventListener(FeathersEventType.FOCUS_OUT, textEditor_focusOutHandler);
this.textEditorViewPort = null;
}
if(this._textEditorFactory != null)
{
this.textEditorViewPort = ITextEditorViewPort(this._textEditorFactory());
}
else
{
this.textEditorViewPort = new TextFieldTextEditorViewPort();
}
var textEditorStyleName:String = this._customTextEditorStyleName != null ? this._customTextEditorStyleName : this.textEditorStyleName;
this.textEditorViewPort.styleNameList.add(textEditorStyleName);
if(this.textEditorViewPort is IStateObserver)
{
IStateObserver(this.textEditorViewPort).stateContext = this;
}
this.textEditorViewPort.addEventListener(Event.CHANGE, textEditor_changeHandler);
this.textEditorViewPort.addEventListener(FeathersEventType.FOCUS_IN, textEditor_focusInHandler);
this.textEditorViewPort.addEventListener(FeathersEventType.FOCUS_OUT, textEditor_focusOutHandler);
var oldViewPort:ITextEditorViewPort = ITextEditorViewPort(this._viewPort);
this.viewPort = this.textEditorViewPort;
if(oldViewPort)
{
//the view port setter won't do this
oldViewPort.dispose();
}
}
/**
* @private
*/
protected function createErrorCallout():void
{
if(this.callout)
{
this.callout.removeFromParent(true);
this.callout = null;
}
if(this._errorString === null)
{
return;
}
this.callout = new TextCallout();
var errorCalloutStyleName:String = this._customErrorCalloutStyleName != null ? this._customErrorCalloutStyleName : this.errorCalloutStyleName;
this.callout.styleNameList.add(errorCalloutStyleName);
this.callout.closeOnKeys = null;
this.callout.closeOnTouchBeganOutside = false;
this.callout.closeOnTouchEndedOutside = false;
this.callout.touchable = false;
this.callout.text = this._errorString;
this.callout.origin = this;
PopUpManager.addPopUp(this.callout, false, false);
}
/**
* @private
*/
protected function changeState(state:String):void
{
if(this._currentState === state)
{
return;
}
this._currentState = state;
this.invalidate(INVALIDATION_FLAG_STATE);
}
/**
* @private
*/
protected function doPendingActions():void
{
if(this._isWaitingToSetFocus || (this._focusManager && this._focusManager.focus == this))
{
this._isWaitingToSetFocus = false;
if(!this._textEditorHasFocus)
{
this.textEditorViewPort.setFocus();
}
}
if(this._pendingSelectionStartIndex >= 0)
{
var startIndex:int = this._pendingSelectionStartIndex;
var endIndex:int = this._pendingSelectionEndIndex;
this._pendingSelectionStartIndex = -1;
this._pendingSelectionEndIndex = -1;
this.selectRange(startIndex, endIndex);
}
}
/**
* @private
*/
protected function refreshTextEditorProperties():void
{
this.textEditorViewPort.maxChars = this._maxChars;
this.textEditorViewPort.restrict = this._restrict;
this.textEditorViewPort.isEditable = this._isEditable;
for(var propertyName:String in this._textEditorProperties)
{
var propertyValue:Object = this._textEditorProperties[propertyName];
this.textEditorViewPort[propertyName] = propertyValue;
}
}
/**
* @private
*/
override protected function refreshBackgroundSkin():void
{
var oldSkin:DisplayObject = this.currentBackgroundSkin;
this.currentBackgroundSkin = this.getCurrentSkin();
if(oldSkin !== this.currentBackgroundSkin)
{
if(oldSkin)
{
this.removeChild(oldSkin, false);
}
if(this.currentBackgroundSkin)
{
this.addChildAt(this.currentBackgroundSkin, 0);
if(this.originalBackgroundWidth !== this.originalBackgroundWidth) //isNaN
{
this.originalBackgroundWidth = this.currentBackgroundSkin.width;
}
if(this.originalBackgroundHeight !== this.originalBackgroundHeight) //isNaN
{
this.originalBackgroundHeight = this.currentBackgroundSkin.height;
}
}
}
}
/**
* @private
*/
protected function getCurrentSkin():DisplayObject
{
if(this._stateToSkinFunction != null)
{
return DisplayObject(this._stateToSkinFunction(this, this._currentState, this.currentBackgroundSkin));
}
var result:DisplayObject = this._stateToSkin[this._currentState] as DisplayObject;
if(result !== null)
{
return result;
}
return this._backgroundSkin;
}
/**
* @private
*/
protected function refreshState():void
{
if(this._isEnabled)
{
if(this._textEditorHasFocus)
{
this.changeState(TextInputState.FOCUSED);
}
else if(this._errorString !== null)
{
this.changeState(TextInputState.ERROR);
}
else
{
this.changeState(TextInputState.ENABLED);
}
}
else
{
this.changeState(TextInputState.DISABLED);
}
}
/**
* @private
*/
protected function setFocusOnTextEditorWithTouch(touch:Touch):void
{
if(!this.isFocusEnabled)
{
return;
}
touch.getLocation(this.stage, HELPER_POINT);
var isInBounds:Boolean = this.contains(this.stage.hitTest(HELPER_POINT));
if(!this._textEditorHasFocus && isInBounds)
{
this.globalToLocal(HELPER_POINT, HELPER_POINT);
HELPER_POINT.x -= this._paddingLeft;
HELPER_POINT.y -= this._paddingTop;
this._isWaitingToSetFocus = false;
this.textEditorViewPort.setFocus(HELPER_POINT);
}
}
/**
* @private
*/
protected function textArea_touchHandler(event:TouchEvent):void
{
if(!this._isEnabled)
{
this._textAreaTouchPointID = -1;
return;
}
var horizontalScrollBar:DisplayObject = DisplayObject(this.horizontalScrollBar);
var verticalScrollBar:DisplayObject = DisplayObject(this.verticalScrollBar);
if(this._textAreaTouchPointID >= 0)
{
var touch:Touch = event.getTouch(this, TouchPhase.ENDED, this._textAreaTouchPointID);
if(!touch || touch.isTouching(verticalScrollBar) || touch.isTouching(horizontalScrollBar))
{
return;
}
this.removeEventListener(Event.SCROLL, textArea_scrollHandler);
this._textAreaTouchPointID = -1;
if(this.textEditorViewPort.setTouchFocusOnEndedPhase)
{
this.setFocusOnTextEditorWithTouch(touch);
}
}
else
{
touch = event.getTouch(this, TouchPhase.BEGAN);
if(touch)
{
if(touch.isTouching(verticalScrollBar) || touch.isTouching(horizontalScrollBar))
{
return;
}
this._textAreaTouchPointID = touch.id;
if(!this.textEditorViewPort.setTouchFocusOnEndedPhase)
{
this.setFocusOnTextEditorWithTouch(touch);
}
this.addEventListener(Event.SCROLL, textArea_scrollHandler);
return;
}
touch = event.getTouch(this, TouchPhase.HOVER);
if(touch)
{
if(touch.isTouching(verticalScrollBar) || touch.isTouching(horizontalScrollBar))
{
return;
}
if(Mouse.supportsNativeCursor && !this._oldMouseCursor)
{
this._oldMouseCursor = Mouse.cursor;
Mouse.cursor = MouseCursor.IBEAM;
}
return;
}
//end hover
if(Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
}
/**
* @private
*/
protected function textArea_scrollHandler(event:Event):void
{
this.removeEventListener(Event.SCROLL, textArea_scrollHandler);
this._textAreaTouchPointID = -1;
}
/**
* @private
*/
protected function textArea_removedFromStageHandler(event:Event):void
{
if(!this._focusManager && this._textEditorHasFocus)
{
this.clearFocus();
}
this._isWaitingToSetFocus = false;
this._textEditorHasFocus = false;
this._textAreaTouchPointID = -1;
this.removeEventListener(Event.SCROLL, textArea_scrollHandler);
if(Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
/**
* @private
*/
override protected function focusInHandler(event:Event):void
{
if(!this._focusManager)
{
return;
}
super.focusInHandler(event);
this.setFocus();
}
/**
* @private
*/
override protected function focusOutHandler(event:Event):void
{
if(!this._focusManager)
{
return;
}
super.focusOutHandler(event);
this.textEditorViewPort.clearFocus();
this.invalidate(INVALIDATION_FLAG_STATE);
}
/**
* @private
*/
override protected function stage_keyDownHandler(event:KeyboardEvent):void
{
if(this._isEditable)
{
return;
}
super.stage_keyDownHandler(event);
}
/**
* @private
*/
protected function textEditor_changeHandler(event:Event):void
{
if(this._ignoreTextChanges)
{
return;
}
this.text = this.textEditorViewPort.text;
}
/**
* @private
*/
protected function textEditor_focusInHandler(event:Event):void
{
this._textEditorHasFocus = true;
this.refreshState();
if(this._errorString !== null && this._errorString.length > 0)
{
this.createErrorCallout();
}
this._touchPointID = -1;
this.invalidate(INVALIDATION_FLAG_STATE);
if(this._focusManager && this.isFocusEnabled && this._focusManager.focus !== this)
{
//if setFocus() was called manually, we need to notify the focus
//manager (unless isFocusEnabled is false).
//if the focus manager already knows that we have focus, it will
//simply return without doing anything.
this._focusManager.focus = this;
}
else if(!this._focusManager)
{
this.dispatchEventWith(FeathersEventType.FOCUS_IN);
}
}
/**
* @private
*/
protected function textEditor_focusOutHandler(event:Event):void
{
this._textEditorHasFocus = false;
this.refreshState();
if(this.callout)
{
this.callout.removeFromParent(true);
this.callout = null;
}
this.invalidate(INVALIDATION_FLAG_STATE);
if(this._focusManager && this._focusManager.focus === this)
{
//if clearFocus() was called manually, we need to notify the
//focus manager if it still thinks we have focus.
this._focusManager.focus = null;
}
else if(!this._focusManager)
{
this.dispatchEventWith(FeathersEventType.FOCUS_OUT);
}
}
}
}