blob: 2e4bd6219d049b579dffb1e45a02f65d901573d3 [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.TextEvent;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IList;
import mx.collections.IViewCursor;
import mx.collections.ListCollectionView;
import mx.collections.XMLListCollection;
import mx.core.EdgeMetrics;
import mx.core.FlexVersion;
import mx.core.IFlexDisplayObject;
import mx.core.IIMESupport;
import mx.core.IRectangularBorder;
import mx.core.ITextInput;
import mx.core.IUITextField;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.FlexEvent;
import mx.managers.IFocusManager;
import mx.managers.IFocusManagerComponent;
import mx.styles.ISimpleStyleClient;
import mx.styles.StyleProxy;
import mx.utils.UIDUtil;
use namespace mx_internal;
/**
* The alpha of the content background for this component.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="contentBackgroundAlpha", type="Number", inherit="yes", theme="spark")]
/**
* Color of the content area of the component.
*
* @default 0xFFFFFF
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="contentBackgroundColor", 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")]
/**
* Color of any symbol of a component. Examples include the check mark of a CheckBox or
* the arrow of a ScrollBar button.
*
* @default 0x000000
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="symbolColor", type="uint", format="Color", inherit="yes", theme="spark")]
/**
* Name of the class to use as the default skin for the background and border.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @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, editableUp, editableOver, editableDown, editableDisabled")]
/**
* Name of the class to use as the skin for the background and border
* when the mouse is not over the control.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @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 mouse is over the control.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
* For the ColorPicker class, the default value is the ColorPickerSkin class.
* For the DateField class, the default value is the ScrollArrowDownSkin class.
*
* @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 user holds down the mouse button.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
* For the ColorPicker class, the default value is the ColorPickerSkin class.
* For the DateField class, the default value is the ScrollArrowDownSkin class.
*
* @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 control is disabled.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
* For the ColorPicker class, the default value is the ColorPickerSkin class.
* For the DateField class, the default value is the ScrollArrowDownSkin class.
*
* @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 background and border
* when the <code>editable</code>
* property is <code>true</code>. This skin is only used by the ComboBox class.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="editableSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the mouse is not over the control, and the <code>editable</code>
* property is <code>true</code>. This skin is only used by the ComboBox class.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="editableUpSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the mouse is over the control, and the <code>editable</code>
* property is <code>true</code>. This skin is only used by the ComboBox class.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="editableOverSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the user holds down the mouse button, and the <code>editable</code>
* property is <code>true</code>. This skin is only used by the ComboBox class.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="editableDownSkin", type="Class", inherit="no")]
/**
* Name of the class to use as the skin for the background and border
* when the control is disabled, and the <code>editable</code>
* property is <code>true</code>. This skin is only used by the ComboBox class.
* For the ComboBase class, there is no default value.
* For the ComboBox class, the default value is the ComboBoxArrowSkin class.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="editableDisabledSkin", type="Class", inherit="no")]
/**
* The class implementing ITextInput that is used by this component
* to input text.
*
* <p>It can be set to either the mx.core.TextInput class
* (to use the classic Halo TextInput control)
* or the mx.controls.MXFTETextInput class
* (to use the Spark TextInput component based on the Text Layout Framework
* to get improved text rendering, including bidirectional layout).</p>
*
* @default mx.controls.TextInput
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
[Style(name="textInputClass", type="Class", inherit="no")]
/**
* The style declaration for the internal TextInput subcomponent
* that displays the current selection.
* If no value is specified, then the TextInput subcomponent uses
* the default text styles defined by the ComboBase class.
*
* @default ""
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textInputStyleName", type="String", inherit="no")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="mx.accessibility.ComboBaseAccImpl")]
/**
* The ComboBase class is the base class for controls that display text in a
* text field and have a button that causes a drop-down list to appear where
* the user can choose which text to display.
* The ComboBase class is not used directly as an MXML tag.
*
* @mxml
*
* <p>The <code>&lt;mx:ComboBase&gt;</code> tag inherits all the tag attributes
* of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* &lt;<i>mx:tagname</i>
* <b>Properties</b>
* dataProvider="null"
* editable="false|true"
* imeMode="null"
* restrict="null"
* selectedIndex="-1"
* selectedItem="null"
* text=""
* &nbsp;
* <b>Styles</b>
* disabledSkin="<i>Depends on class</i>"
* downSkin="<i>Depends on class</i>"
* editableDisabledSkin="<i>Depends on class</i>"
* editableDownSkin="<i>Depends on class</i>"
* editableOverSkin="<i>Depends on class</i>"
* editableUpSkin="<i>Depends on class</i>"
* overSkin="<i>Depends on class</i>"
* textInputStyleName=""
* upSkin="<i>Depends on class</i>"
*
* /&gt;
* </pre>
*
* @see mx.controls.Button
* @see mx.controls.TextInput
* @see mx.collections.ICollectionView
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class ComboBase extends UIComponent implements IIMESupport, IFocusManagerComponent
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ComboBaseAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ComboBase()
{
super();
tabEnabled = true;
tabFocusEnabled = true;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* The ICollectionView of items this component displays.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var collection:ICollectionView;
/**
* The main IViewCursor used to fetch items from the
* dataProvider and pass the items to the renderers.
* At the end of any sequence of code, it must always be positioned
* at the topmost visible item on screen.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var iterator:IViewCursor;
/**
* @private
* A separate IViewCursor used to find indices of items and other things.
* The collectionIterator can be at any place within the set of items.
*/
mx_internal var collectionIterator:IViewCursor;
/**
* @private
* The internal object that draws the border.
*/
mx_internal var border:IFlexDisplayObject;
/**
* @private
* The internal Button property that causes the drop-down list to appear.
*/
mx_internal var downArrowButton:Button;
/**
* @private
*/
mx_internal var wrapDownArrowButton:Boolean = true;
/**
* @private
*/
mx_internal var useFullDropdownSkin:Boolean = false;
/**
* @private
*/
private var selectedUID:String;
/**
* @private
* A flag indicating that selection has changed
*/
mx_internal var selectionChanged:Boolean = false;
/**
* @private
* A flag indicating that selectedIndex has changed
*/
mx_internal var selectedIndexChanged:Boolean = false;
/**
* @private
* A flag indicating that selectedItem has changed
*/
mx_internal var selectedItemChanged:Boolean = false;
/**
* @private
* Stores the old value of the borderStyle style
*/
mx_internal var oldBorderStyle:String;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// baselinePosition
//----------------------------------
/**
* @private
* The baselinePosition of a ComboBase is calculated for its TextInput.
*/
override public function get baselinePosition():Number
{
if (!validateBaselinePosition())
return NaN;
return textInput.y + textInput.baselinePosition;
}
//----------------------------------
// enabled
//----------------------------------
/**
* @private
* Storage for enabled property.
*/
private var _enabled:Boolean = false;
/**
* @private
*/
private var enabledChanged:Boolean = false;
[Inspectable(category="General", enumeration="true,false", defaultValue="true")]
/**
* @private
*/
override public function set enabled(value:Boolean):void
{
super.enabled = value;
_enabled = value;
enabledChanged = true;
invalidateProperties();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// arrowButtonStyleFilters
//----------------------------------
/**
* Set of styles to pass from the ComboBase to the down arrow button
* @see mx.styles.StyleProxy
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get arrowButtonStyleFilters():Object
{
return null;
}
//----------------------------------
// borderMetrics
//----------------------------------
/**
* Returns an EdgeMetrics object that has four properties:
* <code>left</code>, <code>top</code>, <code>right</code>,
* and <code>bottom</code>.
* The value of each property is equal to the thickness of the
* corresponding side of the border, expressed in pixels.
*
* @return EdgeMetrics object with the left, right, top,
* and bottom properties.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get borderMetrics():EdgeMetrics
{
if (border && border is IRectangularBorder)
return IRectangularBorder(border).borderMetrics;
return EdgeMetrics.EMPTY;
}
//----------------------------------
// dataProvider
//----------------------------------
[Bindable("collectionChange")]
[Inspectable(category="Data")]
/**
* The set of items this component displays. This property is of type
* Object because the derived classes can handle a variety of data
* types such as Arrays, XML, ICollectionViews, and other classes. All
* are converted into an ICollectionView and that ICollectionView is
* returned if you get the value of this property; you will not get the
* value you set if it was not an ICollectionView.
*
* <p>Setting this property will adjust the <code>selectedIndex</code>
* property (and therefore the <code>selectedItem</code> property) if
* the <code>selectedIndex</code> property has not otherwise been set.
* If there is no <code>prompt</code> property, the <code>selectedIndex</code>
* property will be set to 0; otherwise it will remain at -1,
* the index used for the prompt string.
* If the <code>selectedIndex</code> property has been set and
* it is out of range of the new data provider, unexpected behavior is
* likely to occur.</p>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dataProvider():Object
{
return collection;
}
/**
* @private
*/
public function set dataProvider(value:Object):void
{
if (value is Array)
{
collection = new ArrayCollection(value as Array);
}
else if (value is ICollectionView)
{
collection = ICollectionView(value);
}
else if (value is IList)
{
collection = new ListCollectionView(IList(value));
}
else if (value is XMLList)
{
collection = new XMLListCollection(value as XMLList);
}
else
{
// convert it to an array containing this one item
var tmp:Array = [value];
collection = new ArrayCollection(tmp);
}
// get an iterator for the displaying rows. The CollectionView's
// main iterator is left unchanged so folks can use old DataSelector
// methods if they want to
iterator = collection.createCursor();
collectionIterator = collection.createCursor(); //IViewCursor(collection);
// trace("ListBase added change listener");
// weak listeners to collections and dataproviders
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true);
var event:CollectionEvent =
new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
event.kind = CollectionEventKind.RESET;
collectionChangeHandler(event);
dispatchEvent(event);
invalidateSize();
invalidateDisplayList();
}
//----------------------------------
// editable
//----------------------------------
/**
* @private
* Storage for editable property.
*/
private var _editable:Boolean = false;
/**
* @private
*/
mx_internal var editableChanged:Boolean = true;
[Bindable("editableChanged")]
[Inspectable(category="General", defaultValue="false")]
/**
* A flag that indicates whether the control is editable,
* which lets the user directly type entries that are not specified
* in the dataProvider, or not editable, which requires the user select
* from the items in the dataProvider.
*
* <p>If <code>true</code> keyboard input will be entered in the
* editable text field; otherwise it will be used as shortcuts to
* select items in the dataProvider.</p>
*
* @default false.
* This property is ignored by the DateField control.
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get editable():Boolean
{
return _editable;
}
/**
* @private
*/
public function set editable(value:Boolean):void
{
_editable = value;
editableChanged = true;
invalidateProperties();
dispatchEvent(new Event("editableChanged"));
}
//----------------------------------
// 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
{
return editable;
}
//----------------------------------
// imeMode
//----------------------------------
/**
* @private
*/
private var _imeMode:String = null;
/**
* @copy mx.controls.TextInput#imeMode
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
**/
public function get imeMode():String
{
return _imeMode;
}
/**
* @private
*/
public function set imeMode(value:String):void
{
_imeMode = value;
if (textInput)
textInput.imeMode = _imeMode;
}
//----------------------------------
// restrict
//----------------------------------
/**
* @private
* Storage for restrict property.
*/
private var _restrict:String;
[Bindable("restrictChanged")]
[Inspectable(category="Other")]
/**
* Set of characters that a user can or cannot enter into the text field.
*
* @default null
*
* @see flash.text.TextField#restrict
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get restrict():String
{
return _restrict;
}
/**
* @private
*/
public function set restrict(value:String):void
{
_restrict = value;
invalidateProperties();
dispatchEvent(new Event("restrictChanged"));
}
//----------------------------------
// selectedIndex
//----------------------------------
private var _selectedIndex:int = -1;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="-1")]
/**
* The index in the data provider of the selected item.
* If there is a <code>prompt</code> property, the <code>selectedIndex</code>
* value can be set to -1 to show the prompt.
* If there is no <code>prompt</code>, property then <code>selectedIndex</code>
* will be set to 0 once a <code>dataProvider</code> is set.
*
* <p>If the ComboBox control is editable, the <code>selectedIndex</code>
* property is -1 if the user types any text
* into the text field.</p>
*
* <p>Unlike many other Flex properties that are invalidating (setting
* them does not have an immediate effect), the <code>selectedIndex</code> and
* <code>selectedItem</code> properties are synchronous; setting one immediately
* affects the other.</p>
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedIndex():int
{
return _selectedIndex;
}
/**
* @private
*/
public function set selectedIndex(value:int):void
{
_selectedIndex = value;
if (value == -1)
{
_selectedItem = null;
selectedUID = null;
}
//2 code paths: one for before collection, one after
if (!collection || collection.length == 0)
{
selectedIndexChanged = true;
}
else
{
if (value != -1)
{
value = Math.min(value, collection.length - 1);
var bookmark:CursorBookmark = iterator.bookmark;
var len:int = value;
iterator.seek(CursorBookmark.FIRST, len);
var data:Object = iterator.current;
var uid:String = itemToUID(data);
iterator.seek(bookmark, 0);
_selectedIndex = value;
_selectedItem = data;
selectedUID = uid;
}
}
selectionChanged = true;
invalidateDisplayList();
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//----------------------------------
// selectedItem
//----------------------------------
/**
* @private
* Storage for the selectedItem property.
*/
private var _selectedItem:Object;
[Bindable("change")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="null")]
/**
* The item in the data provider at the selectedIndex.
*
* <p>If the data is an object or class instance, modifying
* properties in the object or instance modifies the
* <code>dataProvider</code> object but may not update the views
* unless the instance is Bindable or implements IPropertyChangeNotifier
* or a call to dataProvider.itemUpdated() occurs.</p>
*
* Setting the <code>selectedItem</code> property causes the
* ComboBox control to select that item (display it in the text field and
* set the <code>selectedIndex</code>) if it exists in the data provider.
* If the ComboBox control is editable, the <code>selectedItem</code>
* property is <code>null</code> if the user types any text
* into the text field.
*
* <p>Unlike many other Flex properties that are invalidating (setting
* them does not have an immediate effect), <code>selectedIndex</code> and
* <code>selectedItem</code> are synchronous; setting one immediately
* affects the other.</p>
*
* @default null;
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedItem():Object
{
return _selectedItem;
}
/**
* @private
*/
public function set selectedItem(data:Object):void
{
setSelectedItem(data);
}
/**
* @private
*/
private function setSelectedItem(data:Object, clearFirst:Boolean = true):void
{
//2 code paths: one for before collection, one after
if (!collection || collection.length == 0)
{
_selectedItem = data;
selectedItemChanged = true;
invalidateDisplayList();
return;
}
var found:Boolean = false;
var listCursor:IViewCursor = collection.createCursor();
var i:int = 0;
do
{
if (data == listCursor.current)
{
_selectedIndex = i;
_selectedItem = data;
selectedUID = itemToUID(data);
selectionChanged = true;
found = true;
break;
}
i++;
}
while (listCursor.moveNext());
if (!found)
{
selectedIndex = -1;
_selectedItem = null;
selectedUID = null;
}
invalidateDisplayList();
}
//----------------------------------
// text
//----------------------------------
/**
* @private
* Storage for the text property.
*/
private var _text:String = "";
/**
* @private
*/
mx_internal var textChanged:Boolean;
[Bindable("collectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="")]
[NonCommittingChangeEvent("change")]
/**
* Contents of the text field. If the control is non-editable
* setting this property has no effect. If the control is editable,
* setting this property sets the contents of the text field.
*
* @default ""
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get text():String
{
return _text;
}
/**
* @private
*/
public function set text(value:String):void
{
_text = value;
textChanged = true;
invalidateProperties();
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//----------------------------------
// textInput
//----------------------------------
/**
* The internal TextInput subcomponent that displays
* the current selection.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var textInput:ITextInput;
//----------------------------------
// textInputStyleFilters
//----------------------------------
/**
* The set of styles to pass from the ComboBase to the text input.
* These styles are ignored if you set
* the <code>textInputStyleName</code> style property.
* @see mx.styles.StyleProxy
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get textInputStyleFilters():Object
{
return _textInputStyleFilters;
}
private static var _textInputStyleFilters:Object =
{
"backgroundAlpha" : "backgroundAlpha",
"backgroundColor" : "backgroundColor",
"backgroundImage" : "backgroundImage",
"backgroundDisabledColor" : "backgroundDisabledColor",
"backgroundSize" : "backgroundSize",
"borderAlpha" : "borderAlpha",
"borderColor" : "borderColor",
"borderSides" : "borderSides",
"borderStyle" : "borderStyle",
"borderThickness" : "borderThickness",
"dropShadowColor" : "dropShadowColor",
"dropShadowEnabled" : "dropShadowEnabled",
"embedFonts" : "embedFonts",
"focusAlpha" : "focusAlpha",
"focusBlendMode" : "focusBlendMode",
"focusRoundedCorners" : "focusRoundedCorners",
"focusThickness" : "focusThickness",
"leading" : "leading",
"paddingLeft" : "paddingLeft",
"paddingRight" : "paddingRight",
"shadowDirection" : "shadowDirection",
"shadowDistance" : "shadowDistance",
"textDecoration" : "textDecoration"
};
//----------------------------------
// value
//----------------------------------
[Bindable("change")]
[Bindable("valueCommit")]
/**
* The value of the selected item. If the item is a Number or String,
* the value is the item. If the item is an object, the value is
* the <code>data</code> property, if it exists, or the <code>label</code>
* property, if it exists.
*
* <p><strong>Note:</strong> Using the <code>selectedItem</code> property
* is often preferable to using this property. The <code>value</code>
* property exists for backward compatibility with older applications.</p>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get value():Object
{
if (_editable)
return text;
var item:Object = selectedItem;
if (item == null || typeof(item) != "object")
return item;
// Note: the explicit comparison with null is important, because otherwise when
// the data is zero, the label will be returned. See bug 183294 for an example.
return item.data != null ? item.data : item.label;
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function initializeAccessibility():void
{
if (ComboBase.createAccessibilityImplementation != null)
ComboBase.createAccessibilityImplementation(this);
}
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
// Create the border first, in the back.
if (!border)
{
var borderClass:Class = getStyle("borderSkin");
if (borderClass)
{
border = new borderClass();
if (border is IFocusManagerComponent)
IFocusManagerComponent(border).focusEnabled = false;
if (border is ISimpleStyleClient)
ISimpleStyleClient(border).styleName = this;
addChild(DisplayObject(border));
if (FlexVersion.compatibilityVersion >= FlexVersion.VERSION_4_0)
border.visible = false;
}
}
// Next, create the downArrowButton before creating the textInput,
// because it can be as large as the entire control.
if (!downArrowButton)
{
downArrowButton = new Button();
downArrowButton.styleName = new StyleProxy(this, arrowButtonStyleFilters);
downArrowButton.focusEnabled = false;
downArrowButton.tabEnabled = false;
addChild(downArrowButton);
downArrowButton.addEventListener(FlexEvent.BUTTON_DOWN,
downArrowButton_buttonDownHandler);
}
// Create the textInput on top.
if (!textInput)
{
var textInputStyleName:Object = getStyle("textInputStyleName");
if (!textInputStyleName)
textInputStyleName = new StyleProxy(this, textInputStyleFilters);
// Mechanism to use MXFTETextInput.
var textInputClass:Class = getStyle("textInputClass");
if (!textInputClass ||
FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_0)
{
textInput = new TextInput();
}
else
{
textInput = new textInputClass();
}
textInput.editable = _editable;
editableChanged = true;
// Don't show ESC characters in the text field.
textInput.restrict = "^\u001b";
textInput.focusEnabled = false;
textInput.mouseEnabled = textInput.mouseChildren = _editable;
textInput.imeMode = _imeMode;
textInput.styleName = textInputStyleName;
textInput.addEventListener(Event.CHANGE, textInput_changeHandler);
textInput.addEventListener(FlexEvent.ENTER, textInput_enterHandler);
textInput.addEventListener(FocusEvent.FOCUS_IN, focusInHandler);
textInput.addEventListener(FocusEvent.FOCUS_OUT, focusOutHandler);
textInput.addEventListener(FlexEvent.VALUE_COMMIT,
textInput_valueCommitHandler);
addChild(DisplayObject(textInput));
textInput.move(0, 0);
textInput.parentDrawsFocus = true;
}
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
if (downArrowButton)
downArrowButton.styleChanged(styleProp);
if (textInput)
textInput.styleChanged(styleProp);
if (border && border is ISimpleStyleClient)
ISimpleStyleClient(border).styleChanged(styleProp);
super.styleChanged(styleProp);
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
// A TextField interprets restrict="" as meaning
// don't allow any characters, and restrict = null
// as meaning allow all characters.
// The former is useless (the TextField should be disabled
// instead) and the latter is useful, but MXML doesn't
// have a way to specify restrict = null. So Flex changes
// the semantics of restrict="" to mean allow all characters.
textInput.restrict = _restrict;
if (textChanged)
{
textInput.text = _text;
textChanged = false;
}
if (enabledChanged)
{
textInput.enabled = _enabled;
editableChanged = true;
downArrowButton.enabled = _enabled;
enabledChanged = false;
}
if (editableChanged)
{
editableChanged = false;
var e:Boolean = _editable;
if (wrapDownArrowButton == false)
{
if (e)
{
if (oldBorderStyle)
setStyle("borderStyle", oldBorderStyle);
}
else
{
oldBorderStyle = getStyle("borderStyle");
setStyle("borderStyle", "comboNonEdit");
}
}
// Swap the button skins if we have different skins for editable and
// non-editable states.
if (useFullDropdownSkin)
{
if (e && getStyle("editableSkin") != null)
downArrowButton.skinName = "editableSkin";
else
downArrowButton.skinName = "skin";
downArrowButton.upSkinName = e ? "editableUpSkin" : "upSkin";
downArrowButton.overSkinName = e ? "editableOverSkin" : "overSkin";
downArrowButton.downSkinName = e ? "editableDownSkin" : "downSkin";
downArrowButton.disabledSkinName = e ? "editableDisabledSkin" : "disabledSkin";
downArrowButton.changeSkins();
downArrowButton.invalidateDisplayList();
}
if (textInput)
{
textInput.editable = e;
textInput.selectable = e;
if (e)
{
textInput.removeEventListener(MouseEvent.MOUSE_DOWN,
textInput_mouseEventHandler);
textInput.removeEventListener(MouseEvent.MOUSE_UP,
textInput_mouseEventHandler);
textInput.removeEventListener(MouseEvent.ROLL_OVER,
textInput_mouseEventHandler);
textInput.removeEventListener(MouseEvent.ROLL_OUT,
textInput_mouseEventHandler);
textInput.addEventListener(KeyboardEvent.KEY_DOWN,
keyDownHandler);
}
else
{
// Trap all mouse actions on the textfield and feed them to the button
textInput.addEventListener(MouseEvent.MOUSE_DOWN,
textInput_mouseEventHandler);
textInput.addEventListener(MouseEvent.MOUSE_UP,
textInput_mouseEventHandler);
textInput.addEventListener(MouseEvent.ROLL_OVER,
textInput_mouseEventHandler);
textInput.addEventListener(MouseEvent.ROLL_OUT,
textInput_mouseEventHandler);
textInput.removeEventListener(KeyboardEvent.KEY_DOWN,
keyDownHandler);
}
}
}
}
/**
* Determines the <code>measuredWidth</code> and
* <code>measuredHeight</code> properties of the control.
* The measured width is the width of the widest text
* in the <code>dataProvider</code>
* plus the width of the drop-down button.
* The measured height is the larger of either the button or the text.
* If no data provider has been set or there are no items
* in the data provider, the <code>measuredWidth</code> property is set to
* <code>UIComponent.DEFAULT_MEASURED_WIDTH</code> and the
* <code>measuredHeight</code> property is set
* to <code>UIComponent.DEFAULT_MEASURED_HEIGHT</code>.
*
* @see mx.core.UIComponent#measure()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function measure():void
{
super.measure();
var buttonWidth:Number = getStyle("arrowButtonWidth");
var buttonHeight:Number = downArrowButton.getExplicitOrMeasuredHeight();
// Text fields have 4 pixels of white space added to each side
// by the player, so fudge this amount.
// If we don't have any data, measure a single space char for defaults
if (collection && collection.length > 0)
{
var prefSize:Object = calculatePreferredSizeFromData(collection.length);
var bm:EdgeMetrics = borderMetrics;
var textWidth:Number = prefSize.width + bm.left + bm.right + 8;
var textHeight:Number = prefSize.height + bm.top + bm.bottom
+ UITextField.TEXT_HEIGHT_PADDING;
measuredMinWidth = measuredWidth = textWidth + buttonWidth;
measuredMinHeight = measuredHeight = Math.max(textHeight, buttonHeight);
}
else
{
measuredMinWidth = DEFAULT_MEASURED_MIN_WIDTH;
measuredWidth = DEFAULT_MEASURED_WIDTH;
measuredMinHeight = DEFAULT_MEASURED_MIN_HEIGHT;
measuredHeight = DEFAULT_MEASURED_HEIGHT;
}
// Add in the paddingTop and paddingBottom values
var padding:Number = getStyle("paddingTop") + getStyle("paddingBottom");
measuredMinHeight += padding;
measuredHeight += padding;
}
/**
* Sizes and positions the internal components in the given width
* and height. The drop-down button is placed all the way to the right
* and the text field fills the remaining area.
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @see mx.core.UIComponent#updateDisplayList()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var w:Number = unscaledWidth;
var h:Number = unscaledHeight;
var arrowWidth:Number = getStyle("arrowButtonWidth");
var textInputHeight:Number = textInput.getExplicitOrMeasuredHeight();
if (isNaN(arrowWidth))
arrowWidth = 0;
if (wrapDownArrowButton)
{
var vm:EdgeMetrics = borderMetrics;
var wh:Number = h - vm.top - vm.bottom;
downArrowButton.setActualSize(wh, wh);
downArrowButton.move(w - arrowWidth - vm.right, vm.top);
if (border)
border.setActualSize(w, h);
textInput.setActualSize(w - arrowWidth, textInputHeight);
}
else
{
if (!_editable && useFullDropdownSkin)
{
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
downArrowButton.move(0, 0);
if (border)
border.setActualSize(w, h);
textInput.setActualSize(w - arrowWidth, textInputHeight);
textInput.showBorderAndBackground(false);
textInput.move(textInput.x, ((h - textInputHeight - paddingTop - paddingBottom) / 2) + paddingTop);
downArrowButton.setActualSize(unscaledWidth, unscaledHeight);
}
else
{
downArrowButton.move(w - arrowWidth, 0);
if (border)
border.setActualSize(w - arrowWidth, h);
textInput.setActualSize(w - arrowWidth, h);
downArrowButton.setActualSize(arrowWidth, unscaledHeight);
textInput.showBorderAndBackground(true);
}
}
if (selectedItemChanged)
{
selectedItem = selectedItem;
selectedItemChanged = false;
selectedIndexChanged = false;
}
if (selectedIndexChanged)
{
selectedIndex = selectedIndex;
selectedIndexChanged = false;
}
}
/**
* @private
*/
override public function setFocus():void
{
if (textInput && _editable)
textInput.setFocus();
else
super.setFocus();
}
/**
* @private
*/
override protected function isOurFocus(target:DisplayObject):Boolean
{
return target == textInput || super.isOurFocus(target);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Determines default values of the height and width to use for the
* entries in the drop-down list.
* Each subclass of ComboBase must implement this method and return
* an Object containing two properties: <code>width</code> and
* <code>height</code>.
*
* @param numItems The number of items to check to determine the size.
*
* @return An Object with <code>width</code> and <code>height</code>
* properties.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function calculatePreferredSizeFromData(numItems:int):Object
{
return null;
}
/**
* Determines the UID for a dataProvider item.
* Every dataProvider item must have or will be assigned a unique
* identifier (UID).
*
* @param data A dataProvider item.
*
* @return A unique identifier.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToUID(data:Object):String
{
if (!data)
return "null";
return UIDUtil.getUID(data);
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function focusInHandler(event:FocusEvent):void
{
super.focusInHandler(event);
}
/**
* @private
*/
override protected function focusOutHandler(event:FocusEvent):void
{
super.focusOutHandler(event);
var fm:IFocusManager = focusManager;
if (fm && event.target == this)
fm.defaultButtonEnabled = true;
if (_editable)
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* Responds to changes to the data provider. The component will adjust
* the <code>selectedIndex</code> property if items are added or removed
* before the component's selected item.
*
* @param event The CollectionEvent dispatched from the collection.
*
* @see mx.events.CollectionEvent
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function collectionChangeHandler(event:Event):void
{
if (event is CollectionEvent)
{
var requiresValueCommit:Boolean = false;
var len:Number;
var ind:Object;
var ce:CollectionEvent = CollectionEvent(event);
if (ce.kind == CollectionEventKind.ADD)
{
if (selectedIndex >= ce.location)
_selectedIndex++;
}
if (ce.kind == CollectionEventKind.REMOVE)
{
for (var i:int = 0; i < ce.items.length; i++)
{
var uid:String = itemToUID(ce.items[i]);
if (selectedUID == uid)
{
selectionChanged = true;
}
}
if (selectionChanged)
{
if (_selectedIndex >= collection.length)
_selectedIndex = collection.length - 1;
selectedIndexChanged = true;
requiresValueCommit = true;
invalidateDisplayList();
}
else if (selectedIndex >= ce.location)
{
_selectedIndex--;
selectedIndexChanged = true;
requiresValueCommit = true;
invalidateDisplayList();
}
}
if (ce.kind == CollectionEventKind.REFRESH)
{
selectedItemChanged = true;
// Sorting always changes the selection array
requiresValueCommit = true;
}
invalidateDisplayList();
if (requiresValueCommit)
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
}
/**
* @private
* Forward an event to the down arrow button.
*/
private function textInput_mouseEventHandler(event:Event):void
{
downArrowButton.dispatchEvent(event);
}
/**
* Handles changes to the TextInput that serves as the editable
* text field in the component. The method sets
* <code>selectedIndex</code> to -1 (and therefore
* <code>selectedItem</code> to <code>null</code>).
*
* @param event The event that is triggered each time the text in the control changes.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function textInput_changeHandler(event:Event):void
{
// update _text as the user types
_text = textInput.text;
if (_selectedIndex != -1)
{
_selectedIndex = -1;
_selectedItem = null;
selectedUID = null;
}
}
/**
* @private
* valueCommit handler for the textInput
*/
private function textInput_valueCommitHandler(event:FlexEvent):void
{
// update _text if textInput.text is changed programatically
_text = textInput.text;
dispatchEvent(event);
}
/**
* @private
*/
private function textInput_enterHandler(event:FlexEvent):void
{
dispatchEvent(event);
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
/**
* Performs some action when the drop-down button is pressed. This is
* an abstract base class implementation, so it has no effect and is
* overridden by the subclasses.
*
* @param event The event that is triggered when the drop-down button is pressed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function downArrowButton_buttonDownHandler(event:FlexEvent):void
{
// overridden by subclasses
}
/**
* @private
*/
mx_internal function getTextInput():ITextInput
{
return textInput;
}
/**
* @private
*/
mx_internal function get ComboDownArrowButton():Button
{
return downArrowButton;
}
}
}