blob: 582e0628c6617ca97ada3d34bf6812f1b6659ab9 [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.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;
import mx.collections.ArrayCollection;
import mx.collections.CursorBookmark;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBase;
import mx.controls.listClasses.ListData;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.LayoutDirection;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.effects.Tween;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DropdownEvent;
import mx.events.FlexEvent;
import mx.events.FlexMouseEvent;
import mx.events.InterManagerRequest;
import mx.events.ListEvent;
import mx.events.SandboxMouseEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.managers.ISystemManager;
import mx.managers.PopUpManager;
import mx.styles.CSSStyleDeclaration;
import mx.utils.MatrixUtil;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the ComboBox contents changes as a result of user
* interaction, when the <code>selectedIndex</code> or
* <code>selectedItem</code> property changes, and, if the ComboBox control
* is editable, each time a keystroke is entered in the box.
*
* @eventType mx.events.ListEvent.CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="change", type="mx.events.ListEvent")]
/**
* Dispatched when the drop-down list is dismissed for any reason such when
* the user:
* <ul>
* <li>selects an item in the drop-down list</li>
* <li>clicks outside of the drop-down list</li>
* <li>clicks the drop-down button while the drop-down list is
* displayed</li>
* <li>presses the ESC key while the drop-down list is displayed</li>
* </ul>
*
* @eventType mx.events.DropdownEvent.CLOSE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="close", type="mx.events.DropdownEvent")]
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains an item from the
* dataProvider.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
/**
* Dispatched if the <code>editable</code> property
* is set to <code>true</code> and the user presses the Enter key
* while typing in the editable text field.
*
* @eventType mx.events.FlexEvent.ENTER
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="enter", type="mx.events.FlexEvent")]
/**
* Dispatched when user rolls the mouse out of a drop-down list item.
* The event object's <code>target</code> property contains a reference
* to the ComboBox and not the drop-down list.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OUT
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemRollOut", type="mx.events.ListEvent")]
/**
* Dispatched when the user rolls the mouse over a drop-down list item.
* The event object's <code>target</code> property contains a reference
* to the ComboBox and not the drop-down list.
*
* @eventType mx.events.ListEvent.ITEM_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="itemRollOver", type="mx.events.ListEvent")]
/**
* Dispatched when the user clicks the drop-down button
* to display the drop-down list. It is also dispatched if the user
* uses the keyboard and types Ctrl-Down to open the drop-down.
*
* @eventType mx.events.DropdownEvent.OPEN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="open", type="mx.events.DropdownEvent")]
/**
* Dispatched when the user scrolls the ComboBox control's drop-down list.
*
* @eventType mx.events.ScrollEvent.SCROLL
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="scroll", type="mx.events.ScrollEvent")]
//--------------------------------------
// Styles
//--------------------------------------
include "../styles/metadata/FocusStyles.as"
include "../styles/metadata/IconColorStyles.as"
include "../styles/metadata/LeadingStyle.as"
include "../styles/metadata/PaddingStyles.as"
include "../styles/metadata/SkinStyles.as"
include "../styles/metadata/TextStyles.as"
/**
* The set of BackgroundColors for drop-down list rows in an alternating
* pattern.
* Value can be an Array of two of more colors.
* If <code>undefined</code> then the rows will use the drop-down list's
* backgroundColor style.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")]
/**
* Width of the arrow button in pixels.
*
* The default value for the Halo theme is 22.
* The default value for the Spark theme is 18.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="arrowButtonWidth", type="Number", format="Length", inherit="no")]
/**
* The thickness of the border of the drop-down list, in pixels.
* This value is overridden if you define
* <code>borderThickness</code> when setting the
* <code>dropdownStyleName</code> CSSStyleDeclaration.
*
* @default 1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="borderThickness", type="Number", format="Length", inherit="no")]
/**
* The length of the transition when the drop-down list closes, in milliseconds.
* The default transition has the drop-down slide up into the ComboBox.
*
* The default value for the Halo theme is 250.
* The default value for the Spark theme is 50.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="closeDuration", type="Number", format="Time", inherit="no")]
/**
* An easing function to control the close transition. Easing functions can
* be used to control the acceleration and deceleration of the transition.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="closeEasingFunction", type="Function", inherit="no")]
/**
* The color of the border of the ComboBox. If <code>undefined</code>
* the drop-down list will use its normal borderColor style. This style
* is used by the validators to show the ComboBox in an error state.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="dropdownBorderColor", type="uint", format="Color", inherit="yes", theme="halo")]
/**
* The name of a CSSStyleDeclaration to be used by the drop-down list. This
* allows you to control the appearance of the drop-down list or its item
* renderers.
*
* [deprecated]
*
* @default "comboDropDown"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="dropDownStyleName", type="String", inherit="no", deprecatedReplacement="dropdownStyleName")]
/**
* The name of a CSSStyleDeclaration to be used by the drop-down list. This
* allows you to control the appearance of the drop-down list or its item
* renderers.
*
* @default "comboDropdown"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="dropdownStyleName", type="String", inherit="no")]
/**
* Length of the transition when the drop-down list opens, in milliseconds.
* The default transition has the drop-down slide down from the ComboBox.
*
* The default value for the Halo theme is 250.
* The default value for the Spark theme is 0.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="openDuration", type="Number", format="Time", inherit="no")]
/**
* An easing function to control the open transition. Easing functions can
* be used to control the acceleration and deceleration of the transition.
*
* @default undefined
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="openEasingFunction", type="Function", inherit="no")]
/**
* Number of pixels between the control's bottom border
* and the bottom of its content area.
* When the <code>editable</code> property is <code>true</code>,
* <code>paddingTop</code> and <code>paddingBottom</code> affect the size
* of the ComboBox control, but do not affect the position of the editable text field.
*
* The default value for the Halo theme is 0.
* The default value for the Spark theme is -2.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
/**
* Number of pixels between the control's top border
* and the top of its content area.
* When the <code>editable</code> property is <code>true</code>,
* <code>paddingTop</code> and <code>paddingBottom</code> affect the size
* of the ComboBox control, but do not affect the position of the editable text field.
*
* The default value for the Halo theme is 0.
* The default value for the Spark theme is -1.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* The rollOverColor of the drop-down list.
*
* The default value for the Halo theme is <code>0xB2E1FF</code>.
* The default value for the Spark theme is <code>0xCEDBEF</code>.
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The selectionColor of the drop-down list.
*
* The default value for the Halo theme is <code>0x7FCEFF</code>.
* The default value for the Spark theme is <code>0xA8C6EE</code>.
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]
/**
* The selectionDuration of the drop-down list.
*
* @default 250
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionDuration", type="uint", format="Time", inherit="no")]
/**
* The selectionEasingFunction of the drop-down list.
*
* @default undefined
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionEasingFunction", type="Function", inherit="no")]
/**
* The textRollOverColor of the drop-down list.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default value for the Spark theme is <code>0x000000</code>.
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The textSelectedColor of the drop-down list.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default value for the Spark theme is <code>0x000000</code>.
*
* @see mx.controls.List
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="mx.accessibility.ComboBoxAccImpl")]
[DataBindingInfo("acceptedTypes", "{ dataProvider: { label: &quot;String&quot; } }")]
[DefaultBindingProperty(source="selectedItem", destination="dataProvider")]
[DefaultProperty("dataProvider")]
[DefaultTriggerEvent("change")]
[IconFile("ComboBox.png")]
[Alternative(replacement="spark.components.DropDownList", since="4.0")]
[Alternative(replacement="spark.components.ComboBox", since="4.0")]
/**
* The ComboBox control contains a drop-down list
* from which the user can select a single value.
* Its functionality is very similar to that of the
* SELECT form element in HTML.
* The ComboBox can be editable, in which case
* the user can type entries into the TextInput portion
* of the ComboBox that are not in the list.
*
* <p>The ComboBox control has the following default sizing
* characteristics:</p>
* <table class="innertable">
* <tr>
* <th>Characteristic</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>Default size</td>
* <td>Wide enough to accommodate the longest entry in the
* drop-down list in the display area of the main
* control, plus the drop-down button. When the
* drop-down list is not visible, the default height
* is based on the label text size.
*
* <p>The default drop-down list height is five rows, or
* the number of entries in the drop-down list, whichever
* is smaller. The default height of each entry in the
* drop-down list is 22 pixels.</p></td>
* </tr>
* <tr>
* <td>Minimum size</td>
* <td>0 pixels.</td>
* </tr>
* <tr>
* <td>Maximum size</td>
* <td>5000 by 5000.</td>
* </tr>
* <tr>
* <td>dropdownWidth</td>
* <td>The width of the ComboBox control.</td>
* </tr>
* <tr>
* <td>rowCount</td>
* <td>5 rows.</td>
* </tr>
* </table>
*
* @mxml
*
* <p>The <code>&lt;mx:ComboBox&gt;</code> tag inherits all the tag attributes
* of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:ComboBox
* <b>Properties</b>
* dataProvider="null"
* dropdownFactory="<i>ClassFactory that creates an mx.controls.List</i>"
* dropdownWidth="<i>100 or width of the longest text in the dataProvider</i>"
* itemRenderer="null"
* labelField="label"
* labelFunction="null"
* prompt="null"
* rowCount="5"
* selectedIndex="-1"
* selectedItem="null"
*
* <b>Styles</b>
* alternatingItemColors="undefined"
* arrowButtonWidth="22"
* borderColor="0xB7BABC"
* borderThickness="1"
* closeDuration="250"
* closeEasingFunction="undefined"
* color="0x0B333C"
* cornerRadius="0"
* disabledColor="0xAAB3B3"
* disabledIconColor="0x919999"
* dropdownBorderColor="undefined"
* dropdownStyleName="comboDropdown"
* fillAlphas="[0.6,0.4]"
* fillColors="[0xFFFFFF, 0xCCCCCC]"
* focusAlpha="0.4"
* focusRoundedCorners="tl tr bl br"
* fontAntiAliasType="advanced|normal"
* fontFamily="Verdana"
* fontGridFitType="pixel|none|subpixel"
* fontSharpness="0"
* fontSize="10"
* fontStyle="normal|italic"
* fontThickness="0"
* fontWeight="normal|bold"
* highlightAlphas="[0.3,0.0]"
* iconColor="0x111111"
* leading="0"
* openDuration="250"
* openEasingFunction="undefined"
* paddingTop="0"
* paddingBottom="0"
* paddingLeft="5"
* paddingRight="5"
* rollOverColor="<i>Depends on theme color"</i>
* selectionColor="<i>Depends on theme color"</i>
* selectionDuration="250"
* selectionEasingFunction="undefined"
* textAlign="left|center|right"
* textDecoration="none|underline"
* textIndent="0"
* textRollOverColor="0x2B333C"
* textSelectedColor="0x2B333C"
*
* <b>Events</b>
* change="<i>No default</i>"
* close="<i>No default</i>"
* dataChange="<i>No default</i>"
* enter="<i>No default</i>"
* itemRollOut="<i>No default</i>"
* itemRollOver="<i>No default</i>"
* open="<i>No default</i>"
* scroll="<i>No default</i>"
* /&gt;
* </pre>
*
* @includeExample examples/SimpleComboBox.mxml
*
* @see mx.controls.List
* @see mx.effects.Tween
* @see mx.managers.PopUpManager
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class ComboBox extends ComboBase
implements IDataRenderer, IDropInListItemRenderer,
IListItemRenderer
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* @private
* Placeholder for mixin by ComboBoxAccImpl.
*/
mx_internal static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ComboBox()
{
super();
// It it better to start out with an empty data provider rather than
// an undefined one. Otherwise, code in getDropdown() sets it to []
// later, but via setDataProvider(). This API has side effects like
// setting selectionChanged, which causes the text in an editable
// ComboBox to be lost.
dataProvider = new ArrayCollection();
useFullDropdownSkin = true;
wrapDownArrowButton = false;
addEventListener("unload", unloadHandler);
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
* A reference to the internal List that pops up to display a row
* for each dataProvider item.
*/
private var _dropdown:ListBase;
/**
* @private
* A int to track the oldIndex, used when the dropdown is dismissed using the ESC key.
*/
private var _oldIndex:int;
/**
* @private
* The tween used for showing and hiding the drop-down list.
*/
private var tween:Tween = null;
/**
* @private
* A flag to track whether the dropDown tweened up or down.
*/
private var tweenUp:Boolean = false;
/**
* @private
*/
private var preferredDropdownWidth:Number;
/**
* @private
*/
private var dropdownBorderStyle:String = "solid";
/**
* @private
* Is the dropdown list currently shown?
*/
private var _showingDropdown:Boolean = false;
/**
* @private
*/
private var _selectedIndexOnDropdown:int = -1;
/**
* @private
*/
private var bRemoveDropdown:Boolean = true;
/**
* @private
*/
private var inTween:Boolean = false;
/**
* @private
*/
private var bInKeyDown:Boolean = false;
/**
* @private
* Flag that will block default data/listData behavior
*/
private var selectedItemSet:Boolean;
/**
* @private
* Event that is causing the dropDown to open or close.
*/
private var triggerEvent:Event;
/**
* @private
* Whether the text property was explicitly set or not
*/
private var explicitText:Boolean;
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:Object;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* The <code>data</code> property lets you pass a value
* to the component when you use it in an item renderer or item editor.
* You typically use data binding to bind a field of the <code>data</code>
* property to a property of this component.
*
* <p>The ComboBox control uses the <code>listData</code> property and the
* <code>data</code> property as follows. If the ComboBox is in a
* DataGrid control, it expects the <code>dataField</code> property of the
* column to map to a property in the data and sets
* <code>selectedItem</code> to that property. If the ComboBox control is
* in a List control, it expects the <code>labelField</code> of the list
* to map to a property in the data and sets <code>selectedItem</code> to
* that property.
* Otherwise, it sets <code>selectedItem</code> to the data itself.</p>
*
* <p>You do not set this property in MXML.</p>
*
* @see mx.core.IDataRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
var newSelectedItem:*;
_data = value;
if (_listData && _listData is DataGridListData)
newSelectedItem = _data[DataGridListData(_listData).dataField];
else if (_listData is ListData && ListData(_listData).labelField in _data)
newSelectedItem = _data[ListData(_listData).labelField];
else
newSelectedItem = _data;
if (newSelectedItem !== undefined && !selectedItemSet)
{
selectedItem = newSelectedItem;
selectedItemSet = false;
}
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
//----------------------------------
// listData
//----------------------------------
/**
* @private
* Storage for the listData property.
*/
private var _listData:BaseListData;
[Bindable("dataChange")]
[Inspectable(environment="none")]
/**
* When a component is used as a drop-in item renderer or drop-in item
* editor, Flex initializes the <code>listData</code> property of the
* component with the appropriate data from the List control. The
* component can then use the <code>listData</code> property and the
* <code>data</code> property to display the appropriate information
* as a drop-in item renderer or drop-in item editor.
*
* <p>You do not set this property in MXML or ActionScript; Flex sets it
* when the component
* is used as a drop-in item renderer or drop-in item editor.</p>
*
* @see mx.controls.listClasses.IDropInListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get listData():BaseListData
{
return _listData;
}
/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = value;
}
//----------------------------------
// dataProvider
//----------------------------------
/**
* @private
*/
private var collectionChanged:Boolean = false;
[Bindable("collectionChange")]
[Inspectable(category="Data", arrayType="Object")]
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function set dataProvider(value:Object):void
{
selectionChanged = true;
super.dataProvider = value;
destroyDropdown();
invalidateProperties();
invalidateSize();
}
//----------------------------------
// itemRenderer
//----------------------------------
/**
* @private
* Storage for itemRenderer property.
*/
private var _itemRenderer:IFactory;
[Inspectable(category="Data")]
/**
* IFactory that generates the instances that displays the data for the
* drop-down list of the control. You can use this property to specify
* a custom item renderer for the drop-down list.
*
* <p>The control uses a List control internally to create the drop-down
* list.
* The default item renderer for the List control is the ListItemRenderer
* class, which draws the text associated with each item in the list,
* and an optional icon. </p>
*
* @see mx.controls.List
* @see mx.controls.listClasses.ListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get itemRenderer():IFactory
{
return _itemRenderer;
}
/**
* @private
*/
public function set itemRenderer(value:IFactory):void
{
_itemRenderer = value;
if (_dropdown)
_dropdown.itemRenderer = value;
invalidateSize();
invalidateDisplayList();
dispatchEvent(new Event("itemRendererChanged"));
}
//----------------------------------
// selectedIndex
//----------------------------------
[Bindable("change")]
[Bindable("collectionChange")]
[Bindable("valueCommit")]
[Inspectable(category="General", defaultValue="0")]
/**
* Index of the selected item in the drop-down list.
* Setting this property sets the current index and displays
* the associated label in the TextInput portion.
* <p>The default value is -1, but it set to 0
* when a <code>dataProvider</code> is assigned, unless there is a prompt.
* If the control is editable, and the user types in the TextInput portion,
* the value of the <code>selectedIndex</code> property becomes
* -1. If the value of the <code>selectedIndex</code>
* property is out of range, the <code>selectedIndex</code> property is set to the last
* item in the <code>dataProvider</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function set selectedIndex(value:int):void
{
super.selectedIndex = value;
if (value >= 0)
selectionChanged = true;
implicitSelectedIndex = false;
invalidateDisplayList();
// value committed event needs the text to be set
if (textInput && !textChanged && value >= 0)
textInput.text = selectedLabel;
else if (textInput && prompt)
textInput.text = prompt;
// [Matt] setting the text of the textInput should take care of this now
// Send a valueCommit event, which is used by the data model
//dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//----------------------------------
// selectedItem
//----------------------------------
[Bindable("change")]
[Bindable("collectionChange")]
[Bindable("valueCommit")]
/**
* Contains a reference to the selected item in the
* <code>dataProvider</code>.
* If the data is an object or class instance, modifying
* properties in the object or instance modifies the <code>dataProvider</code>
* and thus its views. Setting the selectedItem itself causes the
* ComboBox to select that item (display it in the TextInput portion and set
* the selectedIndex) if it exists in the dataProvider.
* <p>If the ComboBox control is editable, the <code>selectedItem</code>
* property is <code>null</code> if the user types any text
* into the TextInput.
* It has a value only if the user selects an item from the drop-down
* list, or if it is set programmatically.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function set selectedItem(value:Object):void
{
selectedItemSet = true;
// We do not want to apply an implicit default index in this case.
implicitSelectedIndex = false;
super.selectedItem = value;
}
//----------------------------------
// showInAutomationHierarchy
//----------------------------------
/**
* @private
*/
override public function set showInAutomationHierarchy(value:Boolean):void
{
//do not allow value changes
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// dropdown
//----------------------------------
/**
* A reference to the List control that acts as the drop-down in the ComboBox.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dropdown():ListBase
{
return getDropdown();
}
//----------------------------------
// dropdownFactory
//----------------------------------
/**
* @private
* Storage for the dropdownFactory property.
*/
private var _dropdownFactory:IFactory = new ClassFactory(List);
[Bindable("dropdownFactoryChanged")]
/**
* The IFactory that creates a ListBase-derived instance to use
* as the drop-down.
* The default value is an IFactory for List
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dropdownFactory():IFactory
{
return _dropdownFactory;
}
/**
* @private
*/
public function set dropdownFactory(value:IFactory):void
{
_dropdownFactory = value;
dispatchEvent(new Event("dropdownFactoryChanged"));
}
//----------------------------------
// dropDownStyleFilters
//----------------------------------
/**
* The set of styles to pass from the ComboBox to the dropDown.
* Styles in the dropDownStyleName style will override these styles.
* @see mx.styles.StyleProxy
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get dropDownStyleFilters():Object
{
return null;
}
//----------------------------------
// dropdownWidth
//----------------------------------
/**
* @private
* Storage for the dropdownWidth property.
*/
private var _dropdownWidth:Number = 100;
[Bindable("dropdownWidthChanged")]
[Inspectable(category="Size", defaultValue="100")]
/**
* Width of the drop-down list, in pixels.
* <p>The default value is 100 or the width of the longest text
* in the <code>dataProvider</code>, whichever is greater.</p>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get dropdownWidth():Number
{
return _dropdownWidth;
}
/**
* @private
*/
public function set dropdownWidth(value:Number):void
{
_dropdownWidth = value;
preferredDropdownWidth = value;
if (_dropdown)
_dropdown.setActualSize(value, _dropdown.height);
dispatchEvent(new Event("dropdownWidthChanged"));
}
//----------------------------------
// labelField
//----------------------------------
/**
* @private
* Storage for the labelField property.
*/
private var _labelField:String = "label";
/**
* @private
*/
private var labelFieldChanged:Boolean;
[Bindable("labelFieldChanged")]
[Inspectable(category="Data", defaultValue="label")]
/**
* Name of the field in the items in the <code>dataProvider</code>
* Array to display as the label in the TextInput portion and drop-down list.
* By default, the control uses a property named <code>label</code>
* on each Array object and displays it.
* <p>However, if the <code>dataProvider</code> items do not contain
* a <code>label</code> property, you can set the <code>labelField</code>
* property to use a different property.</p>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelField():String
{
return _labelField;
}
/**
* @private
*/
public function set labelField(value:String):void
{
_labelField = value;
labelFieldChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFieldChanged"));
}
//----------------------------------
// labelFunction
//----------------------------------
/**
* @private
* Storage for the labelFunction property.
*/
private var _labelFunction:Function;
/**
* @private
*/
private var labelFunctionChanged:Boolean;
[Bindable("labelFunctionChanged")]
[Inspectable(category="Data")]
/**
* User-supplied function to run on each item to determine its label.
* By default the control uses a property named <code>label</code>
* on each <code>dataProvider</code> item to determine its label.
* However, some data sets do not have a <code>label</code> property,
* or do not have another property that can be used for displaying
* as a label.
* <p>An example is a data set that has <code>lastName</code> and
* <code>firstName</code> fields but you want to display full names.
* You use <code>labelFunction</code> to specify a callback function
* that uses the appropriate fields and return a displayable String.</p>
*
* <p>The labelFunction takes a single argument which is the item
* in the dataProvider and returns a String:</p>
* <pre>
* myLabelFunction(item:Object):String
* </pre>
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelFunction():Function
{
return _labelFunction;
}
/**
* @private
*/
public function set labelFunction(value:Function):void
{
_labelFunction = value;
labelFunctionChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("labelFunctionChanged"));
}
//----------------------------------
// prompt
//----------------------------------
private var promptChanged:Boolean = false;
/**
* @private
* Storage for the prompt property.
*/
private var _prompt:String;
[Inspectable(category="General")]
/**
* The prompt for the ComboBox control. A prompt is
* a String that is displayed in the TextInput portion of the
* ComboBox when <code>selectedIndex</code> = -1. It is usually
* a String like "Select one...". If there is no
* prompt, the ComboBox control sets <code>selectedIndex</code> to 0
* and displays the first item in the <code>dataProvider</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get prompt():String
{
return _prompt;
}
/**
* @private
*/
public function set prompt(value:String):void
{
_prompt = value;
promptChanged = true;
invalidateProperties();
}
//----------------------------------
// rowCount
//----------------------------------
/**
* @private
* Storage for the rowCount property.
*/
private var _rowCount:int = 5;
[Bindable("resize")]
[Inspectable(category="General", defaultValue="5")]
/**
* Maximum number of rows visible in the ComboBox control list.
* If there are fewer items in the
* dataProvider, the ComboBox shows only as many items as
* there are in the dataProvider.
*
* @default 5
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get rowCount():int
{
return Math.max(1, Math.min(collection.length, _rowCount));
}
/**
* @private
*/
public function set rowCount(value:int):void
{
_rowCount = value;
if (_dropdown)
_dropdown.rowCount = value;
}
//----------------------------------
// selectedLabel
//----------------------------------
/**
* The String displayed in the TextInput portion of the ComboBox. It
* is calculated from the data by using the <code>labelField</code>
* or <code>labelFunction</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get selectedLabel():String
{
return itemToLabel(selectedItem);
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function initializeAccessibility():void
{
if (ComboBox.createAccessibilityImplementation != null)
ComboBox.createAccessibilityImplementation(this);
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
destroyDropdown();
super.styleChanged(styleProp);
}
/**
* Makes sure the control is at least 40 pixels wide,
* and tall enough to fit one line of text
* in the TextInput portion of the control but at least
* 22 pixels high.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function measure():void
{
super.measure();
// Make sure we're not too small
measuredMinWidth = Math.max(measuredWidth, DEFAULT_MEASURED_MIN_WIDTH);
// Make sure we're tall enough to hold our text.
// Text field height is text height + 4 pixels top/bottom
var textHeight:Number = measureText("M").height + 6;
var bm:EdgeMetrics = borderMetrics;
measuredMinHeight = measuredHeight =
Math.max(textHeight + bm.top + bm.bottom, DEFAULT_MEASURED_MIN_HEIGHT);
measuredMinHeight = measuredHeight += getStyle("paddingTop") + getStyle("paddingBottom");
}
/**
* @private
* Make sure the drop-down width is the same as the rest of the ComboBox
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// we toss the dropdown when resized
// except if we're opening the dropdown
// then we assume the updateDisplayList() call is spurious
// and will not affect the dropdown size
if (_dropdown && !inTween)
{
// We will not destroy our dropdown if user is actively
// interacting with it, this has been legacy behavior since
// Flex 3.0.
if (!_showingDropdown)
destroyDropdown();
}
else if (_showingDropdown)
{
bRemoveDropdown = false;
}
var ddw:Number = preferredDropdownWidth;
if (isNaN(ddw))
ddw = _dropdownWidth = unscaledWidth;
if (labelFieldChanged)
{
if (_dropdown)
_dropdown.labelField = _labelField;
selectionChanged = true;
if (!explicitText)
textInput.text = selectedLabel;
labelFieldChanged = false;
}
if (labelFunctionChanged)
{
selectionChanged = true;
if (!explicitText)
textInput.text = selectedLabel;
labelFunctionChanged = false;
}
if (selectionChanged)
{
if (!textChanged)
{
if (selectedIndex == -1 && prompt)
textInput.text = prompt;
else if (!explicitText)
textInput.text = selectedLabel;
}
textInput.invalidateDisplayList();
textInput.validateNow();
if (editable)
{
textInput.selectRange(0, textInput.text.length);
textInput.horizontalScrollPosition = 0;
}
if (_dropdown)
_dropdown.selectedIndex = selectedIndex;
selectionChanged = false;
}
// We might need to decrease the number of rows.
if (_dropdown && _dropdown.rowCount != rowCount)
_dropdown.rowCount = rowCount;
}
/**
* @private
*/
override protected function commitProperties():void
{
explicitText = textChanged;
super.commitProperties();
if (collectionChanged)
{
if (selectedIndex == -1 && implicitSelectedIndex && _prompt == null)
selectedIndex = 0;
selectedIndexChanged = true;
collectionChanged = false;
}
if (promptChanged && prompt != null && selectedIndex == -1)
{
promptChanged = false;
textInput.text = prompt;
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Returns a string representing the <code>item</code> parameter.
*
* <p>This method checks in the following order to find a value to return:</p>
*
* <ol>
* <li>If you have specified a <code>labelFunction</code> property,
* returns the result of passing the item to the function.</li>
* <li>If the item is a String, Number, Boolean, or Function, returns
* the item.</li>
* <li>If the item has a property with the name specified by the control's
* <code>labelField</code> property, returns the contents of the property.</li>
* <li>If the item has a label property, returns its value.</li>
* </ol>
*
* @param item The object that contains the value to convert to a label.
* If the item is null, this method returns the empty string.
*
* @return A string representing the <code>item</code> parameter.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function itemToLabel(item:Object, ...rest):String
{
// rest args are needed in case dropdown is some other thing like DataGrid
// that has multiple arguments to labelFunction
// we need to check for null explicitly otherwise
// a numeric zero will not get properly converted to a string.
// (do not use !item)
if (item == null)
return "";
if (labelFunction != null)
return labelFunction(item);
if (typeof(item) == "object")
{
try
{
if (item[labelField] != null)
item = item[labelField];
}
catch(e:Error)
{
}
}
else if (typeof(item) == "xml")
{
try
{
if (item[labelField].length() != 0)
item = item[labelField];
}
catch(e:Error)
{
}
}
if (typeof(item) == "string")
return String(item);
try
{
return item.toString();
}
catch(e:Error)
{
}
return " ";
}
/**
* Displays the drop-down list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function open():void
{
displayDropdown(true);
}
/**
* Hides the drop-down list.
*
* @param trigger The event to dispatch when the
* drop-down list closes.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function close(trigger:Event = null):void
{
if (_showingDropdown)
{
if (_dropdown && selectedIndex != _dropdown.selectedIndex)
selectedIndex = _dropdown.selectedIndex;
displayDropdown(false, trigger);
dispatchChangeEvent(new Event("dummy"),
_selectedIndexOnDropdown,
selectedIndex);
}
}
/**
* @private
*/
mx_internal function hasDropdown():Boolean
{
return _dropdown != null;
}
/**
* @private
*/
private function getDropdown():ListBase
{
if (!initialized)
return null;
if (!hasDropdown())
{
var dropDownStyleName:String = getStyle("dropDownStyleName");
if (dropDownStyleName == null )
dropDownStyleName = getStyle("dropdownStyleName");
_dropdown = dropdownFactory.newInstance();
_dropdown.visible = false;
_dropdown.focusEnabled = false;
_dropdown.owner = this;
if (itemRenderer)
_dropdown.itemRenderer = itemRenderer;
if (dropDownStyleName)
_dropdown.styleName = dropDownStyleName;
PopUpManager.addPopUp(_dropdown, this);
// Don't display a tween when the selection changes.
// The dropdown menu is about to appear anyway,
// and other processing can make the tween look choppy.
_dropdown.setStyle("selectionDuration", 0);
// Set up a data provider in case one doesn't yet exist,
// so we can share it with the dropdown listbox.
if (!dataProvider)
dataProvider = new ArrayCollection();
_dropdown.dataProvider = dataProvider;
_dropdown.rowCount = rowCount;
_dropdown.width = _dropdownWidth;
_dropdown.selectedIndex = selectedIndex;
_oldIndex = selectedIndex;
_dropdown.verticalScrollPolicy = ScrollPolicy.AUTO;
_dropdown.labelField = _labelField;
_dropdown.labelFunction = itemToLabel;
_dropdown.allowDragSelection = true;
_dropdown.addEventListener("change", dropdown_changeHandler);
_dropdown.addEventListener(ScrollEvent.SCROLL, dropdown_scrollHandler);
_dropdown.addEventListener(ListEvent.ITEM_ROLL_OVER, dropdown_itemRollOverHandler);
_dropdown.addEventListener(ListEvent.ITEM_ROLL_OUT, dropdown_itemRollOutHandler);
// the drop down should close if the user clicks on any item.
// add a handler to detect a click in the list
_dropdown.addEventListener(ListEvent.ITEM_CLICK, dropdown_itemClickHandler);
UIComponentGlobals.layoutManager.validateClient(_dropdown, true);
_dropdown.setActualSize(_dropdownWidth, _dropdown.getExplicitOrMeasuredHeight());
_dropdown.validateDisplayList();
_dropdown.cacheAsBitmap = true;
// weak reference to stage
systemManager.addEventListener(Event.RESIZE, stage_resizeHandler, false, 0, true);
}
var m:Matrix = MatrixUtil.getConcatenatedMatrix(this, systemManager.getSandboxRoot());
_dropdown.scaleX = m.a; //scale x
_dropdown.scaleY = m.d; //scale y
return _dropdown;
}
/**
* @private
*/
private function displayDropdown(show:Boolean, trigger:Event = null, playEffect:Boolean = true):void
{
if (!initialized || show == _showingDropdown)
return;
if (inTween && tween)
tween.endTween();
// Subclasses may extend to do pre-processing
// before the dropdown is displayed
// or override to implement special display behavior
// Show or hide the dropdown
var initY:Number;
var endY:Number;
var duration:Number;
var easingFunction:Function;
var point:Point = new Point(0, unscaledHeight);
point = localToGlobal(point);
var sm:ISystemManager = systemManager.topLevelSystemManager;
var screen:Rectangle = sm.getVisibleApplicationRect(null, true);
//opening the dropdown
if (show)
{
// Store the selectedIndex temporarily so we can tell
// if the value changed when the dropdown is closed
_selectedIndexOnDropdown = selectedIndex;
getDropdown();
_dropdown.addEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(FlexMouseEvent.MOUSE_WHEEL_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, dropdown_mouseOutsideHandler);
_dropdown.addEventListener(SandboxMouseEvent.MOUSE_WHEEL_SOMEWHERE, dropdown_mouseOutsideHandler);
if (_dropdown.parent == null) // was popped up then closed
PopUpManager.addPopUp(_dropdown, this);
else
PopUpManager.bringToFront(_dropdown);
// if we donot have enough space in the bottom display the dropdown
// at the top. But if the space there is also less than required
// display it below.
if (point.y + _dropdown.height > screen.bottom &&
point.y > screen.top + _dropdown.height)
{
// Dropdown will go below the bottom of the stage
// and be clipped. Instead, have it grow up.
point.y -= (unscaledHeight + _dropdown.height);
initY = -_dropdown.height;
tweenUp = true;
}
else
{
initY = _dropdown.height;
tweenUp = false;
}
point = _dropdown.parent.globalToLocal(point);
// If the combobox's layout is mirrored then the the dropdown's
// will be too. That also means that (stage coordinate) point.x is
// currently the right edge of the dropdown. Fix that:
if (layoutDirection == LayoutDirection.RTL)
point.x -= _dropdown.width;
var sel:int = _dropdown.selectedIndex;
if (sel == -1)
sel = 0;
var pos:Number = _dropdown.verticalScrollPosition;
// try to set the verticalScrollPosition one above the selected index so
// it looks better when the dropdown is displayed
pos = sel - 1;
pos = Math.min(Math.max(pos, 0), _dropdown.maxVerticalScrollPosition);
_dropdown.verticalScrollPosition = pos;
if (_dropdown.x != point.x || _dropdown.y != point.y)
_dropdown.move(point.x, point.y);
_dropdown.scrollRect = new Rectangle(0, initY,
_dropdown.width, _dropdown.height);
if (!_dropdown.visible)
_dropdown.visible = true;
// Make sure we don't remove the dropdown at the end of the tween
bRemoveDropdown = false;
// Set up the tween and relevant variables.
_showingDropdown = show;
duration = getStyle("openDuration");
endY = 0;
easingFunction = getStyle("openEasingFunction") as Function;
}
// closing the dropdown
else if (_dropdown)
{
// Set up the tween and relevant variables.
endY = (point.y + _dropdown.height > screen.bottom || tweenUp
? -_dropdown.height
: _dropdown.height);
_showingDropdown = show;
initY = 0;
duration = getStyle("closeDuration");
easingFunction = getStyle("closeEasingFunction") as Function;
_dropdown.resetDragScrolling();
}
inTween = true;
// only force validation if we're about to show
// or closing with effect. Otherwise, if the iterator
// is out of sync in the dropdown, we'll get an RTE
// when closing
if (playEffect || show)
UIComponentGlobals.layoutManager.validateNow();
// Block all layout, responses from web service, and other background
// processing until the tween finishes executing.
UIComponent.suspendBackgroundProcessing();
// Disable the dropdown during the tween.
if (_dropdown)
_dropdown.enabled = false;
duration = Math.max(1, duration);
if (!playEffect)
duration = 1;
tween = new Tween(this, initY, endY, duration);
if (easingFunction != null && tween)
tween.easingFunction = easingFunction;
triggerEvent = trigger;
}
/**
* @private
*/
private function dispatchChangeEvent(oldEvent:Event, prevValue:int,
newValue:int):void
{
if (prevValue != newValue)
{
var newEvent:Event = oldEvent is ListEvent ?
oldEvent :
new ListEvent("change");
dispatchEvent(newEvent);
}
}
/**
* @private
*/
private function destroyDropdown():void
{
if (inTween && tween)
tween.endTween();
displayDropdown(false, null, false);
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
private var implicitSelectedIndex:Boolean = false;
/**
* @private
*/
override protected function collectionChangeHandler(event:Event):void
{
// Save a copy of the selectedIndex
var curSelectedIndex:int = selectedIndex;
super.collectionChangeHandler(event);
if (event is CollectionEvent)
{
// trace("ListBase collectionEvent");
var ce:CollectionEvent = CollectionEvent(event);
if (collection.length == 0)
{
// Special case: Empty dataProvider.
if (!selectedIndexChanged && !selectedItemChanged)
{
if (super.selectedIndex != -1)
super.selectedIndex = -1;
implicitSelectedIndex = true;
invalidateDisplayList();
}
// if the combobox is non-editable remove the text
// we don't want to remove the text if it is editable as user might
// have typed something.
if (textInput && !editable)
textInput.text = "";
}
else if (ce.kind == CollectionEventKind.ADD)
{
if (collection.length == ce.items.length)
{
// Special case: Adding the first item(s). Select item 0
// if there is no prompt
if (selectedIndex == -1 && _prompt == null)
selectedIndex = 0;
}
else
{
// we dont want to destroy the dropdown just
// because data got added. Especially true
// for paged data.
return;
}
}
else if (ce.kind == CollectionEventKind.UPDATE)
{
if (ce.location == selectedIndex ||
ce.items[0].source == selectedItem)
// unsorted lists don't have a valid location
// Force an update of the text input
selectionChanged = true;
}
else if (ce.kind == CollectionEventKind.REPLACE)
{
// bail on a replace, no need to change anything,
// especially for paged data
return;
}
else if (ce.kind == CollectionEventKind.RESET)
{
collectionChanged = true;
if (!selectedIndexChanged && !selectedItemChanged)
selectedIndex = prompt ? -1 : 0;
invalidateProperties();
}
invalidateDisplayList();
destroyDropdown();
}
}
/**
* @private
*/
override protected function textInput_changeHandler(event:Event):void
{
super.textInput_changeHandler(event);
// Force a change event to be dispatched
dispatchChangeEvent(event, -1, -2);
}
/**
* @private
*/
override protected function downArrowButton_buttonDownHandler(
event:FlexEvent):void
{
// The down arrow should always toggle the visibility of the dropdown.
if (_showingDropdown)
{
close(event);
}
else
{
displayDropdown(true, event);
}
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function dropdown_mouseOutsideHandler(event:Event):void
{
// trace("dropdown_mouseOutsideHandler: " + event);
if (event is MouseEvent)
{
var mouseEvent:MouseEvent = MouseEvent(event);
if (mouseEvent.target != _dropdown)
// the dropdown's items can dispatch a mouseDownOutside
// event which then bubbles up to us
return;
if (!hitTestPoint(mouseEvent.stageX, mouseEvent.stageY, true))
{
close(event);
}
}
else if (event is SandboxMouseEvent)
{
close(event);
}
}
/**
* @private
*/
private function dropdown_itemClickHandler(event:ListEvent):void
{
if (_showingDropdown)
{
close();
}
}
/**
* @private
*/
override protected function focusOutHandler(event:FocusEvent):void
{
// Note: event.relatedObject is the object getting focus.
// It can be null in some cases, such as when you open
// the dropdown and then click outside the application.
// If the dropdown is open...
if (_showingDropdown && _dropdown &&
this.contains(DisplayObject(event.target)))
{
// If focus is moving outside the dropdown...
if (!event.relatedObject ||
!_dropdown.contains(event.relatedObject))
{
// Close the dropdown.
close();
}
}
super.focusOutHandler(event);
}
private function stage_resizeHandler(event:Event):void
{
destroyDropdown();
}
/**
* @private
*/
private function dropdown_scrollHandler(event:Event):void
{
// TextField.scroll bubbles so you might see it here
if (event is ScrollEvent)
{
var se:ScrollEvent = ScrollEvent(event);
if (se.detail == ScrollEventDetail.THUMB_TRACK ||
se.detail == ScrollEventDetail.THUMB_POSITION ||
se.detail == ScrollEventDetail.LINE_UP ||
se.detail == ScrollEventDetail.LINE_DOWN)
dispatchEvent(se);
}
}
/**
* @private
*/
private function dropdown_itemRollOverHandler(event:Event):void
{
dispatchEvent(event);
}
/**
* @private
*/
private function dropdown_itemRollOutHandler(event:Event):void
{
dispatchEvent(event);
}
/**
* @private
*/
private function dropdown_changeHandler(event:Event):void
{
var prevValue:int = selectedIndex;
// This assignment will also assign the label to the text field.
// See setSelectedIndex().
if (_dropdown)
selectedIndex = _dropdown.selectedIndex;
// If this was generated by the dropdown as a result of a keystroke, it is
// likely a Page-Up or Page-Down, or Arrow-Up or Arrow-Down.
// If the selection changes due to a keystroke,
// we leave the dropdown displayed.
// If it changes as a result of a mouse selection,
// we close the dropdown.
if (!_showingDropdown)
dispatchChangeEvent(event, prevValue, selectedIndex);
else if (!bInKeyDown)
{
// this will also send a change event if needed
close();
}
}
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
// If the combo box is disabled, don't do anything
if (!enabled)
return;
// If a the editable field currently has focus, it is handling
// all arrow keys. We shouldn't also scroll this selection.
if (event.target == textInput)
return;
if (event.ctrlKey && event.keyCode == Keyboard.DOWN)
{
displayDropdown(true, event);
event.stopPropagation();
}
else if (event.ctrlKey && event.keyCode == Keyboard.UP)
{
close(event);
event.stopPropagation();
}
else if (event.keyCode == Keyboard.ESCAPE)
{
if (_showingDropdown)
{
if (_oldIndex != _dropdown.selectedIndex)
selectedIndex = _oldIndex;
displayDropdown(false);
event.stopPropagation();
}
}
else if (event.keyCode == Keyboard.ENTER)
{
if (_showingDropdown)
{
close();
event.stopPropagation();
}
}
else
{
if (!editable ||
event.keyCode == Keyboard.UP ||
event.keyCode == Keyboard.DOWN ||
event.keyCode == Keyboard.PAGE_UP ||
event.keyCode == Keyboard.PAGE_DOWN)
{
var oldIndex:int = selectedIndex;
// Make sure we know we are handling a keyDown,
// so if the dropdown sends out a "change" event
// (like when an up-arrow or down-arrow changes
// the selection) we know not to close the dropdown.
bInKeyDown = _showingDropdown;
// Redispatch the event to the dropdown
// and let its keyDownHandler() handle it.
dropdown.dispatchEvent(event.clone());
event.stopPropagation();
bInKeyDown = false;
}
}
}
/**
* @private
* This acts as the destructor.
*/
private function unloadHandler(event:Event):void
{
if (inTween)
{
UIComponent.resumeBackgroundProcessing();
inTween = false;
}
if (_dropdown)
_dropdown.parent.removeChild(_dropdown);
}
/**
* @private
*/
private function removedFromStageHandler(event:Event):void
{
// Ensure we've unregistered ourselves from PopupManager, else
// we'll be leaked.
destroyDropdown();
}
//--------------------------------------------------------------------------
//
// Tween handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
mx_internal function onTweenUpdate(value:Number):void
{
if (_dropdown)
{
_dropdown.scrollRect = new Rectangle(0, value,
_dropdown.width, _dropdown.height);
}
}
/**
* @private
*/
mx_internal function onTweenEnd(value:Number):void
{
if (_dropdown)
{
// Clear the scrollRect here. This way if drop shadows are
// assigned to the dropdown they show up correctly
_dropdown.scrollRect = null;
inTween = false;
_dropdown.enabled = true;
_dropdown.visible = _showingDropdown;
if (bRemoveDropdown)
{
_dropdown.removeEventListener(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(FlexMouseEvent.MOUSE_WHEEL_OUTSIDE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, dropdown_mouseOutsideHandler);
_dropdown.removeEventListener(SandboxMouseEvent.MOUSE_WHEEL_SOMEWHERE, dropdown_mouseOutsideHandler);
PopUpManager.removePopUp(_dropdown);
_dropdown = null;
}
}
bRemoveDropdown = true;
UIComponent.resumeBackgroundProcessing();
var cbdEvent:DropdownEvent =
new DropdownEvent(_showingDropdown ? DropdownEvent.OPEN : DropdownEvent.CLOSE);
cbdEvent.triggerEvent = triggerEvent;
dispatchEvent(cbdEvent);
}
/**
* Determines default values of the height and width to use for each
* entry in the drop-down list, based on the maximum size of the label
* text in the first <code>numItems</code> items in the data provider.
*
* @param count The number of items to check to determine the value.
*
* @return An Object containing two properties: width and height.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function calculatePreferredSizeFromData(count:int):Object
{
var maxW:Number = 0;
var maxH:Number = 0;
var bookmark:CursorBookmark = iterator ? iterator.bookmark : null;
iterator.seek(CursorBookmark.FIRST, 0);
var more:Boolean = iterator != null;
var lineMetrics:TextLineMetrics;
for (var i:int = 0; i < count; i++)
{
var data:Object;
if (more)
data = iterator ? iterator.current : null;
else
data = null;
var txt:String = itemToLabel(data);
lineMetrics = measureText(txt);
maxW = Math.max(maxW, lineMetrics.width);
maxH = Math.max(maxH, lineMetrics.height);
if (iterator)
iterator.moveNext();
}
if (prompt)
{
lineMetrics = measureText(prompt);
maxW = Math.max(maxW, lineMetrics.width);
maxH = Math.max(maxH, lineMetrics.height);
}
maxW += getStyle("paddingLeft") + getStyle("paddingRight");
if (iterator)
iterator.seek(bookmark, 0);
return { width: maxW, height: maxH };
}
/**
* @private
*/
mx_internal function get isShowingDropdown():Boolean
{
return _showingDropdown;
}
}
}