frameworks/projects/airframework/src/mx/controls/FlexNativeMenu.as (1,996 lines of code) (raw):

//////////////////////////////////////////////////////////////////////////////// // // 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. * * <p>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 <code>labelField</code> property * to specify the data field that is used for the menu item label, the <code>keyEquivalentField</code> * property to specify the field that defines a keyboard equivalent shortcut for the menu item, * and the <code>mnemonicIndexField</code> 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.</p> * * <p>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:</p> * <pre> * &lt;mx:XML format=&quot;e4x&quot; id=&quot;myMenuData&quot;&gt; * &lt;root&gt; * &lt;menuitem label=&quot;MenuItem A&quot;&gt; * &lt;menuitem label=&quot;SubMenuItem A-1&quot; enabled=&quot;False&quot;/&gt; * &lt;menuitem label=&quot;SubMenuItem A-2&quot;/&gt; * &lt;/menuitem&gt; * &lt;menuitem label=&quot;MenuItem B&quot; type=&quot;check&quot; toggled=&quot;true&quot;/&gt; * &lt;menuitem label=&quot;MenuItem C&quot; type=&quot;check&quot; toggled=&quot;false&quot;/&gt; * &lt;menuitem type=&quot;separator&quot;/&gt; * &lt;menuitem label=&quot;MenuItem D&quot;&gt; * &lt;menuitem label=&quot;SubMenuItem D-1&quot;/&gt; * &lt;menuitem label=&quot;SubMenuItem D-2&quot;/&gt; * &lt;menuitem label=&quot;SubMenuItem D-3&quot;/&gt; * &lt;/menuitem&gt; * &lt;/root&gt; * &lt;/mx:XML&gt;</pre> * * <p>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:</p> * * <table class="innertable"> * <tr> * <th>Attribute</th> * <th>Type</th> * <th>Description</th> * </tr> * <tr> * <td><code>altKey</code></td> * <td>Boolean</td> * <td>Specifies whether the Alt key is required as part of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>cmdKey</code></td> * <td>Boolean</td> * <td><strong>Note:</strong> this attribute is deprecated as of Flex 3.2. Use * <code>commandKey</code> instead. Specifies whether the Command key is required as part * of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>commandKey</code></td> * <td>Boolean</td> * <td>Specifies whether the Command key is required as part of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>controlKey</code></td> * <td>Boolean</td> * <td>Specifies whether the Control key is required as part of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>ctrlKey</code></td> * <td>Boolean</td> * <td><strong>Note:</strong> this attribute is deprecated as of Flex 3.2. Use * <code>controlKey</code> instead. Specifies whether the Control key is required as part * of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>enabled</code></td> * <td>Boolean</td> * <td>Specifies whether the user can select the menu item (<code>true</code>), * or not (<code>false</code>). If not specified, Flex treats the item as if * the value were <code>true</code>. * If you use the default data descriptor, data providers must use an <code>enabled</code> * XML attribute or object field to specify this characteristic.</td> * </tr> * <tr> * <td><code>keyEquivalent</code></td> * <td>String</td> * <td>Specifies a keyboard character which, when pressed, triggers an event as though * the menu item was selected. The menu's <code>keyEquivalentField</code> or * <code>keyEquivalentFunction</code> 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.)</td> * </tr> * <tr> * <td><code>label</code></td> * <td>String</td> * <td>Specifies the text that appears in the control. This item is used for all * menu item types except <code>separator</code>. * The menu's <code>labelField</code> or <code>labelFunction</code> 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.</td> * </tr> * <tr> * <td><code>mnemonicIndex</code></td> * <td>Integer</td> * <td>Specifies the index position of the character in the label that is used as the * mnemonic for the menu item. The menu's <code>mnemonicIndexField</code> or * <code>mnemonicIndexFunction</code> 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.</td> * </tr> * <tr> * <td><code>shiftKey</code></td> * <td>Boolean</td> * <td>Specifies whether the Shift key is required as part of the key equivalent for the item.</td> * </tr> * <tr> * <td><code>toggled</code></td> * <td>Boolean</td> * <td>Specifies whether a check item is selected. * If not specified, Flex treats the item as if the value were <code>false</code> * and the item is not selected. * If you use the default data descriptor, data providers must use a <code>toggled</code> * XML attribute or object field to specify this characteristic.</td> * </tr> * <tr> * <td><code>type</code></td> * <td>String</td> * <td>Specifies the type of menu item. Meaningful values are <code>separator</code> and * <code>check</code>. 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 <code>type</code> * XML attribute or object field to specify this characteristic.</td> * </tr> * </table> * * <p>To create a window menu, set the FlexNativeMenu as the <code>menu</code> property of the * Window or WindowedApplication instance on which the menu should appear. To create an application * menu, assign the FlexNativeMenu as the <code>menu</code> 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 <code>setContextMenu()</code> method, passing the UI object * as an argument. Call the FlexNativeMenu component's <code>display()</code> method to display the * menu as a pop-up menu anywhere on one of the application's windows.</p> * * <p>To detect when menu items commands are triggered, register a listener for the <code>itemClick</code> * event. You can also register a listener for the <code>menuShow</code> event to determine when * any menu or submenu is opened.</p> * * @mxml * <p>The <code>&lt;mx:FlexNativeMenu&gt</code> tag supports the following tag attributes:</p> * * <pre> * &lt;mx:FlexNativeMenu * <b>Properties</b> * dataDescriptor="<i>mx.controls.treeClasses.DefaultDataDescriptor</i>" * dataProvider="<i>undefined</i>" * keyEquivalentField="keyEquivalent" * keyEquivalentFunction="<i>undefined</i>" * keyEquivalentModifiersFunction="<i>undefined</i>" * labelField="label" * labelFunction="<i>undefined</i>" * mnemonicIndexField="mnemonicIndex" * mnemonicIndexFunction="<i>undefined</i>" * showRoot="true" * * <b>Events</b> * itemClick="<i>No default</i>" * menuShow="<i>No default</i>" * /&gt; * </pre> * * @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 <code>automationDelegate</code> 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 <code>automationName</code> 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 <code>showInAutomationHierarchy</code> 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. * * <p>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:</p> * * <pre>&lt;mx:FlexNativeMenu id="flexNativeMenu" dataDescriptor="{new MyCustomDataDescriptor()}"/&gt;</pre> * * <p>Alternatively, you can specify the property in MXML as a nested * subtag, as the following example shows:</p> * * <pre>&lt;mx:FlexNativeMenu&gt; * &lt;mx:dataDescriptor&gt; * &lt;myCustomDataDescriptor&gt; * &lt;/mx:dataDescriptor&gt; * ...</pre> * * <p>The default value is an internal instance of the * DefaultDataDescriptor class.</p> * * @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. * * <p>The FlexNativeMenu control handles the source data object as follows:</p> * * <ul> * <li>A String containing valid XML text is converted to an XML object.</li> * <li>An XMLNode is converted to an XML object.</li> * <li>An XMLList is converted to an XMLListCollection.</li> * <li>Any object that implements the ICollectionView interface is cast to * an ICollectionView.</li> * <li>An Array is converted to an ArrayCollection.</li> * <li>Any other type object is wrapped in an Array with the object as its sole * entry.</li> * </ul> * * @default "undefined" * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get dataProvider():Object { if (_rootModel) { return _rootModel; } else return null; } /** * @private */ public function set dataProvider(value:Object):void { if (_rootModel) { _rootModel.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler); } // handle strings and xml if (typeof(value)=="string") value = new XML(value); else if (value is XMLNode) value = new XML(XMLNode(value).toString()); else if (value is XMLList) value = new XMLListCollection(value as XMLList); if (value is XML) { _hasRoot = true; var xl:XMLList = new XMLList(); xl += value; _rootModel = new XMLListCollection(xl); } //if already a collection dont make new one else if (value is ICollectionView) { _rootModel = ICollectionView(value); if (_rootModel.length == 1) _hasRoot = true; } else if (value is Array) { _rootModel = new ArrayCollection(value as Array); } //all other types get wrapped in an ArrayCollection else if (value is Object) { _hasRoot = true; // convert to an array containing this one item var tmp:Array = []; tmp.push(value); _rootModel = new ArrayCollection(tmp); } else { _rootModel = new ArrayCollection(); } //add listeners as weak references _rootModel.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionChangeHandler, false, 0, true); //flag for processing in commitProps dataProviderChanged = true; invalidateProperties(); var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); event.kind = CollectionEventKind.RESET; collectionChangeHandler(event); dispatchEvent(event); } //---------------------------------- // hasRoot //---------------------------------- /** * @private * Flag to indicate if the model has a root */ private var _hasRoot:Boolean = false; /** * @copy mx.controls.Menu#hasRoot * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get hasRoot():Boolean { return _hasRoot; } //---------------------------------- // keyEquivalentField //---------------------------------- /** * @private */ private var keyEquivalentFieldChanged:Boolean = false; /** * @private */ private var _keyEquivalentField:String = "keyEquivalent"; [Bindable("keyEquivalentChanged")] [Inspectable(category="Data", defaultValue="keyEquivalent")] /** * The name of the field in the data provider that determines the * key equivalent for each menu item. The set of values is defined * in the Keyboard class, in the <code>KEYNAME_XXXX</code> constants. For example, * consult that list for the value for a control character such as Home, Insert, etc. * * <p>Setting the <code>keyEquivalentFunction</code> property causes this property to be ignored.</p> * * @default "keyEquivalent" * @see flash.ui.Keyboard * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get keyEquivalentField():String { return _keyEquivalentField; } /** * @private */ public function set keyEquivalentField(value:String):void { if (_keyEquivalentField != value) { _keyEquivalentField = value; keyEquivalentFieldChanged = true; invalidateProperties(); dispatchEvent(new Event("keyEquivalentFieldChanged")); } } //---------------------------------- // keyEquivalentFunction //---------------------------------- /** * @private */ private var _keyEquivalentFunction:Function; [Bindable("keyEquivalentFunctionChanged")] [Inspectable(category="Data")] /** * The function that determines the key equivalent for each menu item. * If you omit this property, Flex uses the contents of the field or * attribute specified by the <code>keyEquivalentField</code> property. * If you specify this property, Flex ignores any <code>keyEquivalentField</code> * property value. * * <p>The <code>keyEquivalentFunction</code> property is good for handling formatting, * localization, and platform independence.</p> * * <p>The key equivalent function must take a single argument, which is the item * in the data provider, and must return a String.</p> * * <pre><code>myKeyEquivalentFunction(item:Object):String</code></pre> * * @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: <code>altKey</code>, <code>commandKey</code>, * <code>controlKey</code>, and <code>shiftKey</code>. * * <p>The <code>keyEquivalentModifiersFunction</code> property is good for handling * formatting, localization, and platform independence.</p> * * <p>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.</p> * * <pre><code>myKeyEquivalentModifiersFunction(item:Object):Array</code></pre> * * @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 <code>"&#064;label"</code>. * * <p>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 <code>"C_ut"</code> * 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 <code>"C__u_t"</code> 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 <code>mnemonicIndexField</code> property * is present and set to a value greater than zero, that value takes precedence over * any underscore-specified mnemonic index value.</p> * * <p>Setting the <code>labelFunction</code> property causes this property to be ignored.</p> * * @default "label" * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get labelField():String { return _labelField; } /** * @private */ public function set labelField(value:String):void { if (_labelField != value) { _labelField = value; labelFieldChanged = true; invalidateProperties(); dispatchEvent(new Event("labelFieldChanged")); } } //---------------------------------- // labelFunction //---------------------------------- /** * @private */ private var _labelFunction:Function; [Bindable("labelFunctionChanged")] [Inspectable(category="Data")] /** * The function that determines the text to display for each menu item. * The label function must find the appropriate field or fields in the * data provider and return a displayable string. * * <p>If you omit this property, Flex uses the contents of the field or * attribute specified by the <code>labelField</code> property. * If you specify this property, Flex ignores any <code>labelField</code> * property value.</p> * * <p>The <code>labelFunction</code> property can be helpful for handling formatting, * localization, and platform-independence.</p> * * <p>The label function must take a single argument, which is the item * in the data provider, and must return a String.</p> * * <pre><code>myLabelFunction(item:Object):String</code></pre> * * @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. * * <p>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.</p> * * <p>Setting the <code>mnemonicIndexFunction</code> property causes * this property to be ignored.</p> * * @default "mnemonicIndex" * * @see #labelField * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get mnemonicIndexField():String { return _mnemonicIndexField; } /** * @private */ public function set mnemonicIndexField(value:String):void { if (_mnemonicIndexField != value) { _mnemonicIndexField = value; mnemonicIndexFieldChanged = true; invalidateProperties(); dispatchEvent(new Event("mnemonicIndexFieldChanged")); } } //---------------------------------- // mnemonicIndexFunction //---------------------------------- /** * @private */ private var _mnemonicIndexFunction:Function; [Bindable("mnemonicIndexFunctionChanged")] [Inspectable(category="Data")] /** * The function that determines the mnemonic index for each menu item. * * <p>If you omit this property, Flex uses the contents of the field or * attribute specified by the <code>mnemonicIndexField</code> property. * If you specify this property, Flex ignores any <code>mnemonicIndexField</code> * property value.</p> * * <p>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.</p> * * <p>The <code>mnemonicIndexFunction</code> property is good for handling formatting, * localization, and platform independence.</p> * * <p>The mnemonic index function must take a single argument which is the item * in the data provider and return an int.</p> * * <pre><code>myMnemonicIndexFunction(item:Object):int</code></pre> * * @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. * * <p>If the data provider has a root node, and the <code>showRoot</code> property * is set to <code>false</code>, the top-level menu items displayed by the * FlexNativeMenu control correspond to the immediate descendants of the root node.</p> * * <p>This flag has no effect when using a data provider without a root nodes, * such as a List or Array.</p> * * @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 the <code>keyEquivalentField</code> and <code>keyEquivalentFunction</code> * 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 <code>keyEquivalentModifiersFunction</code> 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 <code>labelField</code> and <code>labelFunction</code> * 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 <code>mnemonicIndexField</code> and <code>mnemonicIndexFunction</code> * 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"); } } } }