//////////////////////////////////////////////////////////////////////////////// // // 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.DisplayObjectContainer; import flash.display.InteractiveObject; import flash.display.NativeMenu; import flash.display.NativeMenuItem; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.TimerEvent; import flash.ui.Keyboard; import flash.utils.Timer; import flash.xml.XMLNode; import mx.automation.IAutomationObject; import mx.collections.ArrayCollection; import mx.collections.ICollectionView; import mx.collections.XMLListCollection; import mx.collections.errors.ItemPendingError; import mx.controls.menuClasses.IMenuDataDescriptor; import mx.controls.treeClasses.DefaultDataDescriptor; import mx.core.Application; import mx.core.EventPriority; import mx.core.UIComponent; import mx.core.UIComponentGlobals; import mx.core.mx_internal; import mx.events.CollectionEvent; import mx.events.CollectionEventKind; import mx.events.FlexNativeMenuEvent; import mx.managers.ILayoutManagerClient; import mx.managers.ISystemManager; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched before a menu or submenu is displayed. * * @eventType mx.events.FlexNativeMenuEvent.MENU_SHOW * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="menuShow", type="mx.events.FlexNativeMenuEvent")] /** * Dispatched when a menu item is selected. * * @eventType mx.events.FlexNativeMenuEvent.ITEM_CLICK * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="itemClick", type="mx.events.FlexNativeMenuEvent")] /** * The FlexNativeMenu component provides a wrapper for AIR's NativeMenu class. The FlexNativeMenu * provides a way to define native operating system menus (such as window, application, and * context menus) using techniques that are familiar to Flex developers and consistent with * other Flex menu components, such as using MXML and data providers to specify menu structure. * However, unlike Flex menu components, the menus that are defined by a FlexNativeMenu * component are rendered by the host operating system as part of an AIR application, rather * than being created as visual components by Flex. * *
Like other Flex menu components, to define the structure of a menu represented by a
* FlexNativeMenu component, you create a data provider such as an XML hierarchy or an array
* of objects containing data to be used to define the menu. Several properties can be set to
* define how the data provider data is interpreted, such as the labelField
property
* to specify the data field that is used for the menu item label, the keyEquivalentField
* property to specify the field that defines a keyboard equivalent shortcut for the menu item,
* and the mnemonicIndexField
property to specify the field that defines the index
* position of the character in the label that is used as the menu item's mnemonic.
The data provider for FlexNativeMenu items can specify several attributes that determine how * the item is displayed and behaves, as the following XML data provider shows:
** <mx:XML format="e4x" id="myMenuData"> * <root> * <menuitem label="MenuItem A"> * <menuitem label="SubMenuItem A-1" enabled="False"/> * <menuitem label="SubMenuItem A-2"/> * </menuitem> * <menuitem label="MenuItem B" type="check" toggled="true"/> * <menuitem label="MenuItem C" type="check" toggled="false"/> * <menuitem type="separator"/> * <menuitem label="MenuItem D"> * <menuitem label="SubMenuItem D-1"/> * <menuitem label="SubMenuItem D-2"/> * <menuitem label="SubMenuItem D-3"/> * </menuitem> * </root> * </mx:XML>* *
The following table lists the attributes you can specify, * their data types, their purposes, and how the data provider must represent * them if the menu uses the DefaultDataDescriptor class to parse the data provider:
* *Attribute | *Type | *Description | *
---|---|---|
altKey |
* Boolean | *Specifies whether the Alt key is required as part of the key equivalent for the item. | *
cmdKey |
* Boolean | *Note: this attribute is deprecated as of Flex 3.2. Use
* commandKey instead. Specifies whether the Command key is required as part
* of the key equivalent for the item. |
*
commandKey |
* Boolean | *Specifies whether the Command key is required as part of the key equivalent for the item. | *
controlKey |
* Boolean | *Specifies whether the Control key is required as part of the key equivalent for the item. | *
ctrlKey |
* Boolean | *Note: this attribute is deprecated as of Flex 3.2. Use
* controlKey instead. Specifies whether the Control key is required as part
* of the key equivalent for the item. |
*
enabled |
* Boolean | *Specifies whether the user can select the menu item (true ),
* or not (false ). If not specified, Flex treats the item as if
* the value were true .
* If you use the default data descriptor, data providers must use an enabled
* XML attribute or object field to specify this characteristic. |
*
keyEquivalent |
* String | *Specifies a keyboard character which, when pressed, triggers an event as though
* the menu item was selected. The menu's keyEquivalentField or
* keyEquivalentFunction property determines the name of the field
* in the data that specifies the key equivalent, or a function for determining
* the key equivalents. (If the data provider is in E4X XML format, you must specify
* one of these properties to assign a key equivalent.) |
*
label |
* String | *Specifies the text that appears in the control. This item is used for all
* menu item types except separator .
* The menu's labelField or labelFunction property
* determines the name of the field in the data that specifies the label,
* or a function for determining the labels. (If the data provider is in E4X XML format,
* you must specify one of these properties to display a label.)
* If the data provider is an Array of Strings, Flex uses the String value as the label. |
*
mnemonicIndex |
* Integer | *Specifies the index position of the character in the label that is used as the
* mnemonic for the menu item. The menu's mnemonicIndexField or
* mnemonicIndexFunction property determines the name of the field
* in the data that specifies the mnemonic index, or a function for determining
* mnemonic index. (If the data provider is in E4X XML format, you must specify
* one of these properties to specify a mnemonic index in the data.) Alternatively,
* you can indicate that a character in the label is the menu item's mnemonic by
* including an underscore immediately to the left of that character. |
*
shiftKey |
* Boolean | *Specifies whether the Shift key is required as part of the key equivalent for the item. | *
toggled |
* Boolean | *Specifies whether a check item is selected.
* If not specified, Flex treats the item as if the value were false
* and the item is not selected.
* If you use the default data descriptor, data providers must use a toggled
* XML attribute or object field to specify this characteristic. |
*
type |
* String | *Specifies the type of menu item. Meaningful values are separator and
* check . Flex treats all other values,
* or nodes with no type entry, as normal menu entries.
* If you use the default data descriptor, data providers must use a type
* XML attribute or object field to specify this characteristic. |
*
To create a window menu, set the FlexNativeMenu as the menu
property of the
* Window or WindowedApplication instance on which the menu should appear. To create an application
* menu, assign the FlexNativeMenu as the menu
property of the application's
* WindowedApplication. To assign a FlexNativeMenu as the context menu for a portion of the user interface,
* call the FlexNativeMenu instance's setContextMenu()
method, passing the UI object
* as an argument. Call the FlexNativeMenu component's display()
method to display the
* menu as a pop-up menu anywhere on one of the application's windows.
To detect when menu items commands are triggered, register a listener for the itemClick
* event. You can also register a listener for the menuShow
event to determine when
* any menu or submenu is opened.
The <mx:FlexNativeMenu>
tag supports the following tag attributes:
* <mx:FlexNativeMenu * Properties * dataDescriptor="mx.controls.treeClasses.DefaultDataDescriptor" * dataProvider="undefined" * keyEquivalentField="keyEquivalent" * keyEquivalentFunction="undefined" * keyEquivalentModifiersFunction="undefined" * labelField="label" * labelFunction="undefined" * mnemonicIndexField="mnemonicIndex" * mnemonicIndexFunction="undefined" * showRoot="true" * * Events * itemClick="No default" * menuShow="No default" * /> ** * @see flash.display.NativeMenu * @see mx.events.FlexNativeMenuEvent * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class FlexNativeMenu extends EventDispatcher implements ILayoutManagerClient, IFlexContextMenu, IAutomationObject { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * The character to use to indicate the mnemonic index in a label. By * default, it is the underscore character, so in "C_ut", u would become * the character for the mnemonic index. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ private static var MNEMONIC_INDEX_CHARACTER:String = "_"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function FlexNativeMenu() { super(); _nativeMenu.addEventListener(Event.DISPLAYING, menuDisplayHandler, false, 0, true); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // // Properties: IAutomationObject // //-------------------------------------------------------------------------- //---------------------------------- // automationDelegate //---------------------------------- /** * @private * Storage for the
automationDelegate
property.
*/
private var _automationDelegate:IAutomationObject;
/**
* The delegate object that handles the automation-related functionality.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationDelegate():Object
{
return _automationDelegate;
}
/**
* @private
*/
public function set automationDelegate(value:Object):void
{
_automationDelegate = value as IAutomationObject;
}
//----------------------------------
// automationName
//----------------------------------
/**
* @private
* Storage for the automationName
property.
*/
private var _automationName:String = null;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationName():String
{
if (_automationName)
return _automationName;
if (automationDelegate)
return automationDelegate.automationName;
return "";
}
/**
* @private
*/
public function set automationName(value:String):void
{
_automationName = value;
}
/**
* @copy mx.automation.IAutomationObject#automationValue
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationValue():Array
{
if (automationDelegate)
return automationDelegate.automationValue;
return [];
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get numAutomationChildren():int
{
if (automationDelegate)
return automationDelegate.numAutomationChildren;
return 0;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationTabularData():Object
{
if (automationDelegate)
return automationDelegate.automationTabularData;
return null;
}
//----------------------------------
// automationOwner
//----------------------------------
/**
* @private
*/
private var _automationOwner:DisplayObjectContainer;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationOwner():DisplayObjectContainer
{
return _automationOwner ? _automationOwner : automationParent;
}
/**
* @private
*/
public function set automationOwner(value:DisplayObjectContainer):void
{
_automationOwner = value;
}
//----------------------------------
// automationParent
//----------------------------------
/**
* @private
*/
private var _automationParent:DisplayObjectContainer;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationParent():DisplayObjectContainer
{
return _automationParent;
}
/**
* @private
*/
public function set automationParent(value:DisplayObjectContainer):void
{
_automationParent = value;
}
//----------------------------------
// automationEnabled
//----------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationEnabled():Boolean
{
// this is always enabled
return true;
}
//----------------------------------
// automationVisible
//----------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get automationVisible():Boolean
{
// this is always "visible" (may be a context menu and hidden at the time, but
// in terms of automation, this is always visible)
return true;
}
//----------------------------------
// showInAutomationHierarchy
//----------------------------------
/**
* @private
* Storage for the showInAutomationHierarchy
property.
*/
private var _showInAutomationHierarchy:Boolean = true;
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get showInAutomationHierarchy():Boolean
{
return _showInAutomationHierarchy;
}
/**
* @private
*/
public function set showInAutomationHierarchy(value:Boolean):void
{
_showInAutomationHierarchy = value;
}
//--------------------------------------------------------------------------
//
// Properties: ILayoutManagerClient
//
//--------------------------------------------------------------------------
//----------------------------------
// initialized
//----------------------------------
/**
* @private
* Storage for the initialized property.
*/
private var _initialized:Boolean = false;
/**
* @copy mx.core.UIComponent#initialized
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get initialized():Boolean
{
return _initialized;
}
/**
* @private
*/
public function set initialized(value:Boolean):void
{
_initialized = value;
}
//----------------------------------
// nestLevel
//----------------------------------
/**
* @private
* Storage for the nestLevel property.
*/
private var _nestLevel:int = 1;
// no one will likely set nestLevel (but there's a setter in case
// someone wants to. We default nestLevel to 1 as it's a top-level
// component that goes in the chrome.
/**
* @copy mx.core.UIComponent#nestLevel
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get nestLevel():int
{
return _nestLevel;
}
/**
* @private
*/
public function set nestLevel(value:int):void
{
_nestLevel = value;
// After nestLevel is initialized, add this object to the
// LayoutManager's queue, so that it is drawn at least once
invalidateProperties();
}
//----------------------------------
// processedDescriptors
//----------------------------------
/**
* @private
* Storage for the processedDescriptors property.
*/
private var _processedDescriptors:Boolean = false;
/**
* @copy mx.core.UIComponent#processedDescriptors
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get processedDescriptors():Boolean
{
return _processedDescriptors;
}
/**
* @private
*/
public function set processedDescriptors(value:Boolean):void
{
_processedDescriptors = value;
}
//----------------------------------
// updateCompletePendingFlag
//----------------------------------
/**
* @private
* Storage for the updateCompletePendingFlag property.
*/
private var _updateCompletePendingFlag:Boolean = false;
/**
* A flag that determines if an object has been through all three phases
* of layout validation (provided that any were required).
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get updateCompletePendingFlag():Boolean
{
return _updateCompletePendingFlag;
}
/**
* @private
*/
public function set updateCompletePendingFlag(value:Boolean):void
{
_updateCompletePendingFlag = value;
}
//--------------------------------------------------------------------------
//
// Variables: Invalidation
//
//--------------------------------------------------------------------------
/**
* @private
* Whether this component needs to have its
* commitProperties() method called.
*/
private var invalidatePropertiesFlag:Boolean = false;
/**
* @private
*/
private var _nativeMenu:NativeMenu = new NativeMenu();
[Bindable("nativeMenuUpdate")]
//----------------------------------
// nativeMenu
//----------------------------------
/**
* Returns the flash.display.NativeMenu managed by this object,
* or null if there is not one.
*
* Any changes made directly to the underlying NativeMenu instance
* may be lost when changes are made to the menu or the underlying
* data provider.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get nativeMenu() : NativeMenu
{
return _nativeMenu;
}
//----------------------------------
// dataDescriptor
//----------------------------------
/**
* @private
*/
private var dataDescriptorChanged:Boolean = false;
/**
* @private
*/
private var _dataDescriptor:IMenuDataDescriptor =
new DefaultDataDescriptor();
[Inspectable(category="Data")]
/**
* The object that accesses and manipulates data in the data provider.
* The FlexNativeMenu control delegates to the data descriptor for information
* about its data. This data is then used to parse and move about the
* data source. The data descriptor defined for the FlexNativeMenu is used for
* all child menus and submenus.
*
* When you specify this property as an attribute in MXML, you must * use a reference to the data descriptor, not the string name of the * descriptor. Use the following format for setting the property:
* *<mx:FlexNativeMenu id="flexNativeMenu" dataDescriptor="{new MyCustomDataDescriptor()}"/>* *
Alternatively, you can specify the property in MXML as a nested * subtag, as the following example shows:
* *<mx:FlexNativeMenu> * <mx:dataDescriptor> * <myCustomDataDescriptor> * </mx:dataDescriptor> * ...* *
The default value is an internal instance of the * DefaultDataDescriptor class.
* * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get dataDescriptor():IMenuDataDescriptor { return IMenuDataDescriptor(_dataDescriptor); } /** * @private */ public function set dataDescriptor(value:IMenuDataDescriptor):void { _dataDescriptor = value; dataDescriptorChanged = true; } //---------------------------------- // dataProvider //---------------------------------- /** * @private */ private var dataProviderChanged:Boolean = false; /** * @private * Storage variable for the original dataProvider */ mx_internal var _rootModel:ICollectionView; [Bindable("collectionChange")] [Inspectable(category="Data")] /** * The hierarchy of objects that are used to define the structure * of menu items in the NativeMenu. Individual data objects define * menu items, and items with child items become menus and submenus. * *The FlexNativeMenu control handles the source data object as follows:
* *KEYNAME_XXXX
constants. For example,
* consult that list for the value for a control character such as Home, Insert, etc.
*
* Setting the keyEquivalentFunction
property causes this property to be ignored.
keyEquivalentField
property.
* If you specify this property, Flex ignores any keyEquivalentField
* property value.
*
* The keyEquivalentFunction
property is good for handling formatting,
* localization, and platform independence.
The key equivalent function must take a single argument, which is the item * in the data provider, and must return a String.
* *myKeyEquivalentFunction(item:Object):String
*
* @default "undefined"
* @see flash.ui.Keyboard
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get keyEquivalentFunction():Function
{
return _keyEquivalentFunction;
}
/**
* @private
*/
public function set keyEquivalentFunction(value:Function):void
{
if (_keyEquivalentFunction != value)
{
_keyEquivalentFunction = value;
keyEquivalentFieldChanged = true;
invalidateProperties();
dispatchEvent(new Event("keyEquivalentFunctionChanged"));
}
}
//----------------------------------
// keyEquivalentModifiersFunction
//----------------------------------
/**
* @private
*/
private var keyEquivalentModifiersFunctionChanged:Boolean = false;
/**
* @private
*/
private var _keyEquivalentModifiersFunction:Function = keyEquivalentModifiersDefaultFunction;
private function keyEquivalentModifiersDefaultFunction(data:Object):Array
{
var modifiers:Array = [];
var xmlModifiers:Array = ["@altKey", "@cmdKey", "@ctrlKey",
"@shiftKey", "@commandKey", "@controlKey"];
var objectModifiers:Array = ["altKey", "cmdKey", "ctrlKey",
"shiftKey", "commandKey", "controlKey"];
var keyboardModifiers:Array = [Keyboard.ALTERNATE, Keyboard.COMMAND,
Keyboard.CONTROL, Keyboard.SHIFT,
Keyboard.COMMAND, Keyboard.CONTROL];
if (data is XML)
{
for (var i:int = 0; i < xmlModifiers.length; i++)
{
try
{
var modifier:* = data[xmlModifiers[i]];
if (modifier[0] == true)
modifiers.push(keyboardModifiers[i]);
}
catch(e:Error)
{
}
}
}
else if (data is Object)
{
for (i = 0; i < objectModifiers.length; i++)
{
try
{
modifier = data[objectModifiers[i]];
if (String(modifier).toLowerCase() == "true")
modifiers.push(keyboardModifiers[i]);
}
catch(e:Error)
{
}
}
}
return modifiers;
}
[Bindable("keyEquivalentModifiersFunctionChanged")]
[Inspectable(category="Data")]
/**
* The function that determines the key equivalent modifiers for each menu item.
*
* If you omit this property, Flex uses its own default function to determine the
* Array of modifiers by looking in the data provider data for the presence of
* the following (boolean) fields: altKey
, commandKey
,
* controlKey
, and shiftKey
.
*
* The keyEquivalentModifiersFunction
property is good for handling
* formatting, localization, and platform independence.
The key equivalent modifiers function must take a single argument, which * is the item in the data provider, and must return an array of modifier key names.
* *myKeyEquivalentModifiersFunction(item:Object):Array
*
* @default "undefined"
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get keyEquivalentModifiersFunction():Function
{
return _keyEquivalentModifiersFunction;
}
/**
* @private
*/
public function set keyEquivalentModifiersFunction(value:Function):void
{
if (_keyEquivalentModifiersFunction != value)
{
_keyEquivalentModifiersFunction = value;
keyEquivalentModifiersFunctionChanged = true;
invalidateProperties();
dispatchEvent(new Event("keyEquivalentModifiersFunctionChanged"));
}
}
//----------------------------------
// labelField
//----------------------------------
/**
* @private
*/
private var labelFieldChanged:Boolean = false;
/**
* @private
*/
private var _labelField:String = "label";
[Bindable("labelFieldChanged")]
[Inspectable(category="Data", defaultValue="label")]
/**
* The name of the field in the data provider that determines the
* text to display for each menu item. If the data provider is an Array of
* Strings, Flex uses each string value as the label. If the data
* provider is an E4X XML object, you must set this property explicitly.
* For example, if each XML elementin an E4X XML Object includes a "label"
* attribute containing the text to display for each menu item, set
* the labelField to "@label"
.
*
* In a label, you can specify the character to be used as the mnemonic index
* by preceding it with an underscore. For example, a label value of "C_ut"
* sets the mnemonic index to 1. Only the first underscore present is used for this
* purpose. To display a literal underscore character in the label, you can escape it
* using a double underscore. For example, a label value of "C__u_t"
would
* result in a menu item with the label "C_ut" and a mnemonic index of 3 (the "t"
* character). If the field defined in the mnemonicIndexField
property
* is present and set to a value greater than zero, that value takes precedence over
* any underscore-specified mnemonic index value.
Setting the labelFunction
property causes this property to be ignored.
If you omit this property, Flex uses the contents of the field or
* attribute specified by the labelField
property.
* If you specify this property, Flex ignores any labelField
* property value.
The labelFunction
property can be helpful for handling formatting,
* localization, and platform-independence.
The label function must take a single argument, which is the item * in the data provider, and must return a String.
* *myLabelFunction(item:Object):String
*
* @default "undefined"
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get labelFunction():Function
{
return _labelFunction;
}
/**
* @private
*/
public function set labelFunction(value:Function):void
{
if (_labelFunction != value)
{
_labelFunction = value;
labelFieldChanged = true;
invalidateProperties();
dispatchEvent(new Event("labelFunctionChanged"));
}
}
//----------------------------------
// mnemonicIndexField
//----------------------------------
/**
* @private
*/
private var mnemonicIndexFieldChanged:Boolean = false;
/**
* @private
*/
private var _mnemonicIndexField:String = "mnemonicIndex";
[Bindable("mnemonicIndexChanged")]
[Inspectable(category="Data", defaultValue="mnemonicIndex")]
/**
* The name of the field in the data provider that determines the
* mnemonic index for each menu item.
*
* If the field specified by this property contains a number greater * than zero, that mnemonic index * takes precedence over one specified by an underscore in the label.
* *Setting the mnemonicIndexFunction
property causes
* this property to be ignored.
If you omit this property, Flex uses the contents of the field or
* attribute specified by the mnemonicIndexField
property.
* If you specify this property, Flex ignores any mnemonicIndexField
* property value.
If this property is defined and the function returns a number greater than * zero for a data item, the returned mnemonic index * takes precedence over one specified by an underscore in the label.
* *The mnemonicIndexFunction
property is good for handling formatting,
* localization, and platform independence.
The mnemonic index function must take a single argument which is the item * in the data provider and return an int.
* *myMnemonicIndexFunction(item:Object):int
*
* @default "undefined"
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get mnemonicIndexFunction():Function
{
return _mnemonicIndexFunction;
}
/**
* @private
*/
public function set mnemonicIndexFunction(value:Function):void
{
if (_mnemonicIndexFunction != value)
{
_mnemonicIndexFunction = value;
mnemonicIndexFieldChanged = true;
invalidateProperties();
dispatchEvent(new Event("mnemonicIndexFunctionChanged"));
}
}
//----------------------------------
// showRoot
//----------------------------------
/**
* @private
* Storage variable for showRoot flag.
*/
private var _showRoot:Boolean = true;
/**
* @private
*/
private var showRootChanged:Boolean = false;
[Inspectable(category="Data", enumeration="true,false", defaultValue="false")]
/**
* A Boolean flag that specifies whether to display the data provider's
* root node.
*
* If the data provider has a root node, and the showRoot
property
* is set to false
, the top-level menu items displayed by the
* FlexNativeMenu control correspond to the immediate descendants of the root node.
This flag has no effect when using a data provider without a root nodes, * such as a List or Array.
* * @default true * @see #hasRoot * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showRoot():Boolean { return _showRoot; } /** * @private */ public function set showRoot(value:Boolean):void { if (_showRoot != value) { showRootChanged = true; _showRoot = value; invalidateProperties(); } } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @copy mx.core.UIComponent#invalidateProperties() * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function invalidateProperties():void { // Don't try to add the object to the display list queue until we've // been assigned a nestLevel, or we'll get added at the wrong place in // the LayoutManager's priority queue. if (!invalidatePropertiesFlag && nestLevel > 0) { invalidatePropertiesFlag = true; if (UIComponentGlobals.layoutManager) UIComponentGlobals.layoutManager.invalidateProperties(this); else { var myTimer:Timer = new Timer(100, 1); myTimer.addEventListener(TimerEvent.TIMER, validatePropertiesTimerHandler); myTimer.start(); } } } /** * @private */ public function validatePropertiesTimerHandler(event:TimerEvent):void { validateProperties(); } /** * @inheritDoc * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function validateProperties():void { if (invalidatePropertiesFlag) { commitProperties(); invalidatePropertiesFlag = false; } } /** * @inheritDoc * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function validateSize(recursive:Boolean = false):void { } /** * @inheritDoc * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function validateDisplayList():void { } /** * Validates and updates the properties and layout of this object * and redraws it, if necessary. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function validateNow():void { // Since we don't have commit/measure/layout phases, // all we need to do here is the commit phase if (invalidatePropertiesFlag) validateProperties(); } /** * Sets the context menu of the InteractiveObject to the underlying native menu. * * @param data The interactive object. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setContextMenu(component:InteractiveObject):void { component.contextMenu = nativeMenu; if (component is Application) { var systemManager:ISystemManager = Application(component).systemManager; if (systemManager is InteractiveObject) InteractiveObject(systemManager).contextMenu = nativeMenu; } automationParent = component as DisplayObjectContainer; automationOwner = component as DisplayObjectContainer; component.dispatchEvent(new Event("flexContextMenuChanged")); } /** * Unsets the context menu of the InteractiveObject that has been set to * the underlying native menu. * * @param data The interactive object. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function unsetContextMenu(component:InteractiveObject):void { component.contextMenu = null; automationParent = null; automationOwner = null; component.dispatchEvent(new Event("flexContextMenuChanged")); } /** * Processes the properties set on the component. * * @see mx.core.UIComponent#commitProperties() * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function commitProperties():void { if (showRootChanged) { if (!_hasRoot) showRootChanged = false; } if (dataProviderChanged ||showRootChanged || labelFieldChanged || dataDescriptorChanged) { var tmpCollection:ICollectionView; //reset flags dataProviderChanged = false; showRootChanged = false; labelFieldChanged = false; dataDescriptorChanged = false; // are we swallowing the root? if (_rootModel && !_showRoot && _hasRoot) { var rootItem:* = _rootModel.createCursor().current; if (rootItem != null && _dataDescriptor.isBranch(rootItem, _rootModel) && _dataDescriptor.hasChildren(rootItem, _rootModel)) { // then get rootItem children tmpCollection = _dataDescriptor.getChildren(rootItem, _rootModel); } } // remove all items first. This is better than creating a new NativeMenu // as the root since we have the same reference clearMenu(_nativeMenu); // make top level items if (_rootModel) { if (!tmpCollection) tmpCollection = _rootModel; // not really a default handler, but we need to // be later than the wrapper tmpCollection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, EventPriority.DEFAULT_HANDLER, true); populateMenu(_nativeMenu, tmpCollection); } dispatchEvent(new Event("nativeMenuChange")); } } /** * Creates a menu and adds appropriate listeners * * @private * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ private function createMenu():NativeMenu { var menu:NativeMenu = new NativeMenu(); // need to do this in the constructor for the root nativeMenu menu.addEventListener(Event.DISPLAYING, menuDisplayHandler, false, 0, true); return menu; } /** * Clears out all items in a given menu * * @private */ private function clearMenu(menu:NativeMenu):void { var numItems:int = menu.numItems; for (var i:int = 0; i < numItems; i++) { menu.removeItemAt(0); } } /** * Populates a menu and the related submenus given a collection * * @private */ private function populateMenu(menu:NativeMenu, collection:ICollectionView):NativeMenu { var collectionLength:int = collection.length; for (var i:int = 0; i < collectionLength; i++) { try { insertMenuItem(menu, i, collection[i]); } catch(e:ItemPendingError) { //we probably dont need to actively recover from here } } return menu; } /** * Adds the NativeMenuItem to the NativeMenu. This methods looks at the * properties of the data sent in and sets them properly on the NativeMenuItem. * * @private */ private function insertMenuItem(menu:NativeMenu, index:int, data:Object):void { if (dataProviderChanged) { commitProperties(); return; } var type:String = dataDescriptor.getType(data).toLowerCase(); var isSeparator:Boolean = (type == "separator"); // label changes later, but separator is read-only so need to know here var nativeMenuItem:NativeMenuItem = new NativeMenuItem("", isSeparator); if (!isSeparator) { // enabled nativeMenuItem.enabled = dataDescriptor.isEnabled(data); // checked nativeMenuItem.checked = type == "check" && dataDescriptor.isToggled(data); // data nativeMenuItem.data = dataDescriptor.getData(data, _rootModel); // key equivalent nativeMenuItem.keyEquivalent = itemToKeyEquivalent(data); // key equivalent modifiers nativeMenuItem.keyEquivalentModifiers = itemToKeyEquivalentModifiers(data); // label and mnemonic index var labelData:String = itemToLabel(data); var mnemonicIndex:int = itemToMnemonicIndex(data); if (mnemonicIndex >= 0) { nativeMenuItem.label = parseLabelToString(labelData); nativeMenuItem.mnemonicIndex = mnemonicIndex; } else { nativeMenuItem.label = parseLabelToString(labelData); nativeMenuItem.mnemonicIndex = parseLabelToMnemonicIndex(labelData); } // event listeners nativeMenuItem.addEventListener(flash.events.Event.SELECT, itemSelectHandler, false, 0, true); // recursive if (dataDescriptor.isBranch(data, _rootModel) && dataDescriptor.hasChildren(data, _rootModel)) { nativeMenuItem.submenu = createMenu(); populateMenu(nativeMenuItem.submenu, dataDescriptor.getChildren(data, _rootModel)); } } // done! menu.addItem(nativeMenuItem); } /** * Pops up this menu at the specified location. * * @param stage The Stage object on which to display this menu. * * @param x The number of horizontal pixels, relative to the origin of stage, * at which to display this menu. * * @param y The number of vertical pixels, relative to the origin of stage, * at which to display this menu. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function display(stage:Stage, x:int, y:int):void { nativeMenu.display(stage, x, y); } /** * Returns the key equivalent for the given data object * based on thekeyEquivalentField
and keyEquivalentFunction
* properties. If the method cannot convert the parameter to a String, it returns an
* empty string.
*
* @param data Object to be displayed.
*
* @return The key equivalent based on the data.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToKeyEquivalent(data:Object):String
{
if (data == null)
return "";
if (keyEquivalentFunction != null)
return keyEquivalentFunction(data);
if (data is XML)
{
try
{
if (data[keyEquivalentField].length() != 0)
{
data = data[keyEquivalentField];
return data.toString();
}
//if (XMLList(data.@keyEquivalent).length() != 0)
//{
// data = data.@keyEquivalent;
//}
}
catch(e:Error)
{
}
}
else if (data is Object)
{
try
{
if (data[keyEquivalentField] != null)
{
data = data[keyEquivalentField];
return data.toString();
}
}
catch(e:Error)
{
}
}
return "";
}
/**
* Returns the key equivalent modifiers for the given data object
* based on the keyEquivalentModifiersFunction
property.
* If the method cannot convert the parameter to an Array of modifiers,
* it returns an empty Array.
*
* @param data Object to be displayed.
*
* @return The array of key equivalent modifiers based on the data.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToKeyEquivalentModifiers(data:Object):Array
{
if (data == null)
return [];
if (keyEquivalentModifiersFunction != null)
return keyEquivalentModifiersFunction(data);
return [];
}
/**
* Returns the String to use as the menu item label for the given data
* object, based on the labelField
and labelFunction
* properties.
* If the method cannot convert the parameter to a String, it returns a
* single space.
*
* @param data Object to be displayed.
*
* @return The string to be displayed based on the data.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToLabel(data:Object):String
{
if (data == null)
return " ";
if (labelFunction != null)
return labelFunction(data);
if (data is XML)
{
try
{
if (data[labelField].length() != 0)
data = data[labelField];
//if (XMLList(data.@label).length() != 0)
//{
// data = data.@label;
//}
}
catch(e:Error)
{
}
}
else if (data is Object)
{
try
{
if (data[labelField] != null)
data = data[labelField];
}
catch(e:Error)
{
}
}
else if (data is String)
return String(data);
try
{
return data.toString();
}
catch(e:Error)
{
}
return " ";
}
/**
* Returns the mnemonic index for the given data object
* based on the mnemonicIndexField
and mnemonicIndexFunction
* properties. If the method cannot convert the parameter to an integer, it returns -1.
*
* @param data Object to be displayed.
*
* @return The mnemonic index based on the data.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function itemToMnemonicIndex(data:Object):int
{
if (data == null)
return -1;
var mnemonicIndex:int;
if (mnemonicIndexFunction != null)
return mnemonicIndexFunction(data);
if (data is XML)
{
try
{
if (data[mnemonicIndexField].length() != 0)
{
mnemonicIndex = data[mnemonicIndexField]; // no need for parseInt??
return mnemonicIndex;
}
//if (XMLList(data.@mnemonicIndex).length() != 0)
//{
// data = data.@mnemonicIndex;
//}
}
catch(e:Error)
{
}
}
else if (data is Object)
{
try
{
if (data[mnemonicIndexField] != null)
{
mnemonicIndex = data[mnemonicIndexField];
return mnemonicIndex;
}
}
catch(e:Error)
{
}
}
return -1;
}
/**
* Determines the actual label to be used for the NativeMenuItem
* by removing underscore characters and converting escaped underscore
* characters, if there are any.
*
* @param data The data to parse for the label.
*
* @return The label.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function parseLabelToString(data:String):String
{
const singleCharacter:RegExp = new RegExp(MNEMONIC_INDEX_CHARACTER, "g");
const doubleCharacter:RegExp = new RegExp(MNEMONIC_INDEX_CHARACTER + MNEMONIC_INDEX_CHARACTER, "g");
var dataWithoutEscapedUnderscores:Array = data.split(doubleCharacter);
// now need to find lone underscores and remove it
var len:int = dataWithoutEscapedUnderscores.length;
for(var i:int = 0; i < len; i++)
{
var str:String = String(dataWithoutEscapedUnderscores[i]);
dataWithoutEscapedUnderscores[i] = str.replace(singleCharacter, "");
}
return dataWithoutEscapedUnderscores.join(MNEMONIC_INDEX_CHARACTER);
}
/**
* Extracts the mnemonic index from a label based on the presence of
* an underscore character. It finds the leading underscore character if
* there is one and uses that as the index.
*
* @param data The data to parse for the index.
*
* @return The index.
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function parseLabelToMnemonicIndex(data:String):int
{
const doubleCharacter:RegExp = new RegExp(MNEMONIC_INDEX_CHARACTER + MNEMONIC_INDEX_CHARACTER, "g");
var dataWithoutEscapedUnderscores:Array = data.split(doubleCharacter);
// now need to find first underscore
var len:int = dataWithoutEscapedUnderscores.length;
var strLengthUpTo:int = 0; // length of string accumulator
for(var i:int = 0; i < len; i++)
{
var str:String = String(dataWithoutEscapedUnderscores[i]);
var index:int = str.indexOf(MNEMONIC_INDEX_CHARACTER);
if (index >= 0)
return index + strLengthUpTo;
strLengthUpTo += str.length + MNEMONIC_INDEX_CHARACTER.length;
}
return -1;
}
//--------------------------------------------------------------------------
//
// Methods: IAutomationObject
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createAutomationIDPart(child:IAutomationObject):Object
{
if (automationDelegate)
return automationDelegate.createAutomationIDPart(child);
return null;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createAutomationIDPartWithRequiredProperties(child:IAutomationObject,
properties:Array):Object
{
if (automationDelegate)
return automationDelegate.createAutomationIDPartWithRequiredProperties(child, properties);
return null;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function resolveAutomationIDPart(criteria:Object):Array
{
if (automationDelegate)
return automationDelegate.resolveAutomationIDPart(criteria);
return [];
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getAutomationChildAt(index:int):IAutomationObject
{
if (automationDelegate)
return automationDelegate.getAutomationChildAt(index);
return null;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getAutomationChildren():Array
{
if (automationDelegate)
return automationDelegate.getAutomationChildren();
return null;
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function replayAutomatableEvent(event:Event):Boolean
{
if (automationDelegate)
return automationDelegate.replayAutomatableEvent(event);
return false;
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function itemSelectHandler(event:Event):void
{
var nativeMenuItem:NativeMenuItem = event.target as NativeMenuItem;
var type:String = dataDescriptor.getType(nativeMenuItem.data).toLowerCase();
if (type == "check")
{
var checked:Boolean = !dataDescriptor.isToggled(nativeMenuItem.data);
nativeMenuItem.checked = checked;
dataDescriptor.setToggled(nativeMenuItem.data, checked);
// this causes an update event which ends up re-creating
// the whole menu... (SDK-13109)
}
var menuEvent:FlexNativeMenuEvent = new FlexNativeMenuEvent(FlexNativeMenuEvent.ITEM_CLICK);
menuEvent.nativeMenu = nativeMenuItem.menu;
menuEvent.index = nativeMenuItem.menu.getItemIndex(nativeMenuItem);
menuEvent.nativeMenuItem = nativeMenuItem;
menuEvent.label = nativeMenuItem.label;
menuEvent.item = nativeMenuItem.data;
dispatchEvent(menuEvent);
}
/**
* @private
*/
private function menuDisplayHandler(event:Event):void
{
var nativeMenu:NativeMenu = event.target as NativeMenu;
var menuEvent:FlexNativeMenuEvent = new FlexNativeMenuEvent(FlexNativeMenuEvent.MENU_SHOW);
menuEvent.nativeMenu = nativeMenu;
dispatchEvent(menuEvent);
}
/**
* @private
*/
private function collectionChangeHandler(ce:CollectionEvent):void
{
//trace("[FlexNativeMenu] caught Model changed");
if (ce.kind == CollectionEventKind.ADD)
{
dataProviderChanged = true;
invalidateProperties();
// should handle elegantly with better performance
//trace("[FlexNativeMenu] add event");
}
else if (ce.kind == CollectionEventKind.REMOVE)
{
dataProviderChanged = true;
invalidateProperties();
// should handle elegantly with better performance
//trace("[FlexNativeMenu] remove event at:", ce.location);
}
else if (ce.kind == CollectionEventKind.REFRESH)
{
dataProviderChanged = true;
dataProvider = dataProvider; //start over
invalidateProperties();
//trace("[FlexNativeMenu] refresh event");
}
else if (ce.kind == CollectionEventKind.RESET)
{
dataProviderChanged = true;
invalidateProperties();
//trace("[FlexNativeMenu] reset event");
}
else if (ce.kind == CollectionEventKind.UPDATE)
{
dataProviderChanged = true;
invalidateProperties();
// should handle elegantly with better performance
// but can't right now
//trace("[FlexNativeMenu] update event");
}
}
}
}