| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package mx.controls |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.events.Event; |
| import flash.events.FocusEvent; |
| import flash.events.KeyboardEvent; |
| import flash.events.MouseEvent; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.ui.Keyboard; |
| import flash.utils.clearInterval; |
| import flash.utils.setTimeout; |
| import flash.xml.XMLNode; |
| |
| import mx.collections.ArrayCollection; |
| import mx.collections.CursorBookmark; |
| import mx.collections.ICollectionView; |
| import mx.collections.XMLListCollection; |
| import mx.controls.listClasses.BaseListData; |
| import mx.controls.listClasses.IListItemRenderer; |
| import mx.controls.menuClasses.IMenuBarItemRenderer; |
| import mx.controls.menuClasses.IMenuDataDescriptor; |
| import mx.controls.menuClasses.IMenuItemRenderer; |
| import mx.controls.menuClasses.MenuItemRenderer; |
| import mx.controls.menuClasses.MenuListData; |
| import mx.controls.treeClasses.DefaultDataDescriptor; |
| import mx.core.ClassFactory; |
| import mx.core.EdgeMetrics; |
| import mx.core.EventPriority; |
| import mx.core.FlexGlobals; |
| import mx.core.IFlexDisplayObject; |
| import mx.core.LayoutDirection; |
| import mx.core.ScrollPolicy; |
| import mx.core.UIComponent; |
| import mx.core.UIComponentGlobals; |
| import mx.core.mx_internal; |
| import mx.effects.Tween; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| import mx.events.FlexEvent; |
| import mx.events.FlexMouseEvent; |
| import mx.events.InterManagerRequest; |
| import mx.events.ListEvent; |
| import mx.events.MenuEvent; |
| import mx.events.SandboxMouseEvent; |
| import mx.managers.IActiveWindowManager; |
| import mx.managers.IFocusManagerContainer; |
| import mx.managers.ISystemManager; |
| import mx.managers.PopUpManager; |
| |
| use namespace mx_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispatched when selection changes as a result |
| * of user interaction. |
| * |
| * @eventType mx.events.MenuEvent.CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="change", type="mx.events.MenuEvent")] |
| |
| /** |
| * Dispatched when a menu item is selected. |
| * |
| * @eventType mx.events.MenuEvent.ITEM_CLICK |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemClick", type="mx.events.MenuEvent")] |
| |
| /** |
| * Dispatched when a menu or submenu is dismissed. |
| * |
| * @eventType mx.events.MenuEvent.MENU_HIDE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="menuHide", type="mx.events.MenuEvent")] |
| |
| /** |
| * Dispatched when a menu or submenu opens. |
| * |
| * @eventType mx.events.MenuEvent.MENU_SHOW |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="menuShow", type="mx.events.MenuEvent")] |
| |
| /** |
| * Dispatched when a user rolls the mouse out of a menu item. |
| * |
| * @eventType mx.events.MenuEvent.ITEM_ROLL_OUT |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemRollOut", type="mx.events.MenuEvent")] |
| |
| /** |
| * Dispatched when a user rolls the mouse over a menu item. |
| * |
| * @eventType mx.events.MenuEvent.ITEM_ROLL_OVER |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Event(name="itemRollOver", type="mx.events.MenuEvent")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| /** |
| * The colors used for menu or submenu menu items in an alternating pattern. |
| * The value can be an Array of one or more colors. |
| * This style is only used if <code>backgroundColor</code> is not specified. |
| * |
| * @default "undefined" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="alternatingItemColors", type="Array", arrayType="uint", format="Color", inherit="yes")] |
| |
| /** |
| * Number of pixels between children (icons and label) in the horizontal direction. |
| * |
| * @default 6 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="horizontalGap", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * The gap to the left of the label in a menu item. If the icons (custom |
| * icon and type icon) do not fit in this gap, the gap is expanded to |
| * fit them properly. |
| * The default value is 18. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="leftIconGap", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * The gap to the right of the label in a menu item. If the branch icon |
| * does not fit in this gap, the gap is expanded to fit it properly. |
| * The default value is 15. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="rightIconGap", type="Number", format="Length", inherit="no")] |
| |
| /** |
| * The duration of the menu or submenu opening transition, in milliseconds. |
| * The value 0 specifies no transition. |
| * |
| * The default value for the Halo theme is 250. |
| * The default value for the Spark theme is 0. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="openDuration", type="Number", format="Time", inherit="no")] |
| |
| /** |
| * The color of the menu item background when a user rolls the mouse over it. |
| * |
| * The default value for the Halo theme is <code>0xB2E1FF</code>. |
| * The default value for the Spark theme is <code>0xCEDBEF</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="rollOverColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * The color of the menu item background when a menu item is selected. |
| * |
| * The default value for the Halo theme is <code>0x7FCEFF</code>. |
| * The default value for the Spark theme is <code>0xA8C6EE</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="selectionColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * The reference to an <code>easingFunction</code> equation which is used to |
| * control programmatic tweening. |
| * |
| * @default "undefined" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="selectionEasingFunction", type="Function", inherit="no")] |
| |
| /** |
| * The offset of the first line of text from the left side of the menu or |
| * submenu menu item. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="textIndent", type="Number", format="Length", inherit="yes")] |
| |
| /** |
| * The color of the menu item text when a user rolls the mouse over the |
| * menu item. |
| * |
| * The default value for the Halo theme is <code>0x2B333C</code>. |
| * The default value for the Spark theme is <code>0x000000</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * The color of the menu item text when the menu item is selected. |
| * |
| * The default value for the Halo theme is <code>0x2B333C</code>. |
| * The default value for the Spark theme is <code>0x000000</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")] |
| |
| /** |
| * The icon for all enabled menu items that have submenus. |
| * |
| * The default value for the Halo theme is the "MenuBranchEnabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuArrow</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="branchIcon", type="Class", inherit="no")] |
| |
| /** |
| * The icon for all disabled menu items that have submenus. |
| * |
| * The default value for the Halo theme is the "MenuBranchDisabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuArrowDisabled</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="branchDisabledIcon", type="Class", inherit="no")] |
| |
| /** |
| * The icon for all enabled menu items whose type identifier is a check box. |
| * |
| * The default value for the Halo theme is the "MenuCheckEnabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuCheck</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="checkIcon", type="Class", inherit="no")] |
| |
| /** |
| * The icon for all dsiabled menu items whose type identifier is a check box. |
| * |
| * The default value for the Halo theme is the "MenuCheckDisabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuCheckDisabled</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="checkDisabledIcon", type="Class", inherit="no")] |
| |
| /** |
| * The icon for all enabled menu items whose type identifier is a radio |
| * button. |
| * |
| * The default value for the Halo theme is the "MenuRadioEnabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuRadio</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="radioIcon", type="Class", inherit="no")] |
| |
| /** |
| * The icon for all disabled menu items whose type identifier is a radio |
| * button. |
| * |
| * The default value for the Halo theme is the "MenuRadioDisabled" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuRadioDisabled</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="radioDisabledIcon", type="Class", inherit="no")] |
| |
| /** |
| * The skin for all menu items which are identified as separators. |
| * |
| * The default value for the Halo theme is the "MenuSeparator" symbol |
| * in the Assets.swf file. |
| * The default value for the Spark theme is <code>mx.skins.spark.MenuSeparatorSkin</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| [Style(name="separatorSkin", type="Class", inherit="no")] |
| |
| //-------------------------------------- |
| // Excluded APIs |
| //-------------------------------------- |
| |
| [Exclude(name="allowMultipleSelection", kind="property")] |
| [Exclude(name="horizontalScrollBarStyleName", kind="property")] |
| [Exclude(name="horizontalScrollPolicy", kind="property")] |
| [Exclude(name="horizontalScrollPosition", kind="property")] |
| [Exclude(name="liveScrolling", kind="property")] |
| [Exclude(name="maxHorizontalScrollPosition", kind="property")] |
| [Exclude(name="maxVerticalScrollPosition", kind="property")] |
| [Exclude(name="scrollTipFunction", kind="property")] |
| [Exclude(name="showScrollTips", kind="property")] |
| [Exclude(name="verticalScrollBarStyleName", kind="property")] |
| [Exclude(name="verticalScrollPolicy", kind="property")] |
| [Exclude(name="verticalScrollPosition", kind="property")] |
| |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [AccessibilityClass(implementation="mx.accessibility.MenuAccImpl")] |
| |
| [DefaultBindingProperty(destination="dataProvider")] |
| |
| [DefaultTriggerEvent("change")] |
| |
| [IconFile("Menu.png")] |
| |
| [RequiresDataBinding(true)] |
| |
| /** |
| * The Menu control creates a pop-up menu of individually selectable choices, |
| * similar to the File or Edit menu found in most software applications. The |
| * popped up menu can have as many levels of submenus as needed. |
| * After a Menu control has opened, it remains visible until it is closed by |
| * any of the following actions: |
| * |
| * <ul> |
| * <li>A call to the <code>Menu.hide()</code> method.</li> |
| * <li>When a user selects an enabled menu item.</li> |
| * <li>When a user clicks outside of the Menu control.</li> |
| * <li>When a user selects another component in the application.</li> |
| * </ul> |
| * |
| * <p>The Menu class has no corresponding MXML tag. You must create it using ActionScript.</p> |
| * |
| * <p>The Menu control has the following sizing characteristics:</p> |
| * <table class="innertable"> |
| * <tr> |
| * <th>Characteristic</th> |
| * <th>Description</th> |
| * </tr> |
| * <tr> |
| * <td>Default size</td> |
| * <td>The width is determined from the Menu text. The |
| * default height is the number of menu rows multiplied |
| * by 19 pixels per row (the default row height).</td> |
| * </tr> |
| * </table> |
| * |
| * <p>The data provider for Menu items can specify several attributes that determine how |
| * the item is displayed and behaves, as the following XML data provider shows:</p> |
| * <pre> |
| * <mx:XML format="e4x" id="myMenuData"> |
| * <root> |
| * <menuitem label="MenuItem A" icon="myTopIcon"> |
| * <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" icon="myTopIcon"/> |
| * <menuitem type="separator"/> |
| * <menuitem label="MenuItem D" icon="myTopIcon"> |
| * <menuitem label="SubMenuItem D-1" type="radio" groupName="one"/> |
| * <menuitem label="SubMenuItem D-2" type="radio" groupName="one" toggled="true"/> |
| * <menuitem label="SubMenuItem D-3" type="radio" groupName="one"/> |
| * </menuitem> |
| * </root> |
| * </mx:XML></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>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>groupName</code></td> |
| * <td>String</td> |
| * <td> (Required, and meaningful, for <code>radio</code> type only) The identifier that |
| * associates radio button items in a radio group. If you use the default data descriptor, |
| * data providers must use a <code>groupName</code> XML attribute or object field to |
| * specify this characteristic.</td> |
| * </tr> |
| * <tr> |
| * <td><code>icon</code></td> |
| * <td>Class</td> |
| * <td>Specifies the class identifier of an image asset. This item is not used for |
| * the <code>check</code>, <code>radio</code>, or <code>separator</code> types. |
| * You can use the <code>checkIcon</code> and <code>radioIcon</code> styles to |
| * specify the icons used for radio and check box items that are selected. |
| * The menu's <code>iconField</code> or <code>iconFunction</code> property determines |
| * the name of the field in the data that specifies the icon, or a function for determining the icons.</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>toggled</code></td> |
| * <td>Boolean</td> |
| * <td>Specifies whether a check or radio 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 toggled |
| * 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>, |
| * <code>check</code>, or <code>radio</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 type |
| * XML attribute or object field to specify this characteristic.</td> |
| * </tr> |
| * </table> |
| * |
| * |
| * @includeExample examples/SimpleMenuExample.mxml |
| * |
| * @see mx.controls.MenuBar |
| * @see mx.controls.PopUpMenuButton |
| * @see mx.controls.menuClasses.IMenuDataDescriptor |
| * @see mx.controls.treeClasses.DefaultDataDescriptor |
| * @see mx.effects.Tween |
| * @see mx.managers.PopUpManager |
| * |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class Menu extends List implements IFocusManagerContainer |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class mixins |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Placeholder for mixin by MenuAccImpl. |
| */ |
| mx_internal static var createAccessibilityImplementation:Function; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Creates and returns an instance of the Menu class. The Menu control's |
| * content is determined by the method's <code>mdp</code> argument. The |
| * Menu control is placed in the parent container specified by the |
| * method's <code>parent</code> argument. |
| * |
| * This method does not show the Menu control. Instead, |
| * this method just creates the Menu control and allows for modifications |
| * to be made to the Menu instance before the Menu is shown. To show the |
| * Menu, call the <code>Menu.show()</code> method. |
| * |
| * @param parent A container that the PopUpManager uses to place the Menu |
| * control in. The Menu control may not actually be parented by this object. |
| * |
| * @param mdp The data provider for the Menu control. |
| * @see mx.controls.Menu@dataProvider |
| * |
| * @param showRoot A Boolean flag that specifies whether to display the |
| * root node of the data provider. |
| * @see mx.controls.Menu@showRoot |
| * |
| * @return An instance of the Menu class. |
| * |
| * @see mx.controls.Menu#popUpMenu() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function createMenu(parent:DisplayObjectContainer, mdp:Object, showRoot:Boolean = true):Menu |
| { |
| var menu:Menu = new Menu(); |
| menu.tabEnabled = false; |
| menu.tabFocusEnabled = false; |
| menu.owner = DisplayObjectContainer(FlexGlobals.topLevelApplication); |
| menu.showRoot = showRoot; |
| popUpMenu(menu, parent, mdp); |
| return menu; |
| } |
| |
| /** |
| * Sets the dataProvider of an existing Menu control and places the Menu |
| * control in the specified parent container. |
| * |
| * This method does not show the Menu control; you must use the |
| * <code>Menu.show()</code> method to display the Menu control. |
| * |
| * The <code>Menu.createMenu()</code> method uses this method. |
| * |
| * @param menu Menu control to popup. |
| * |
| * @param parent A container that the PopUpManager uses to place the Menu |
| * control in. The Menu control may not actually be parented by this object. |
| * If you omit this property, the method sets the Menu control's parent to |
| * the application. |
| * |
| * @param mdp dataProvider object set on the popped up Menu. If you omit this |
| * property, the method sets the Menu data provider to a new, empty XML object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public static function popUpMenu(menu:Menu, parent:DisplayObjectContainer, mdp:Object):void |
| { |
| menu.parentDisplayObject = parent ? |
| parent : |
| DisplayObject(FlexGlobals.topLevelApplication); |
| |
| if (!mdp) |
| mdp = new XML(); |
| |
| menu.supposedToLoseFocus = true; |
| // menu.isPressed = true; |
| menu.dataProvider = mdp; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * <p>Applications do not normally call the Menu constructor directly. |
| * Instead, Applications will call the <code>Menu.createMenu()</code> |
| * method.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function Menu() |
| { |
| super(); |
| |
| itemRenderer = new ClassFactory(MenuItemRenderer); |
| setRowHeight(19); |
| iconField = "icon"; |
| |
| visible = false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * internal measuring stick |
| */ |
| private var hiddenItem:IListItemRenderer; |
| |
| /** |
| * @private |
| * The maximum width for icons. Used so they |
| * can align properly across all MenuItemRenderers |
| */ |
| private var maxMeasuredIconWidth:Number = 0; |
| |
| /** |
| * @private |
| * The maximum width for type icons (checkbox, radiobox). |
| * Used so they can align properly across all MenuItemRenderers |
| */ |
| private var maxMeasuredTypeIconWidth:Number = 0; |
| |
| /** |
| * @private |
| * The maximum width for branch icons. Used so they |
| * can align properly across all MenuItemRenderers |
| */ |
| private var maxMeasuredBranchIconWidth:Number = 0; |
| |
| /** |
| * @private |
| * Whether the left icons should layout into two separate columns |
| * (one for icons and one for type icons, like check and radio) |
| */ |
| private var useTwoColumns:Boolean = false; |
| |
| /** |
| * @private |
| * The menu bar that eventually spawned this menu |
| */ |
| mx_internal var sourceMenuBar:MenuBar; // optional |
| |
| /** |
| * @private |
| * the IMenuBarItemRenderer instance in the menubar |
| * that spawned this menu |
| */ |
| mx_internal var sourceMenuBarItem:IMenuBarItemRenderer; // optional |
| |
| /** |
| * @private |
| * Where to add this menu on the display list. |
| */ |
| mx_internal var parentDisplayObject:DisplayObject; |
| |
| /** |
| * @private |
| * Whether the menu was opened from the left or the right. |
| * This really only applies to submenus and helps with cascading submenus. |
| */ |
| private var isDirectionLeft:Boolean = false; |
| |
| // the anchor is the row in the parent menu that opened to be this menu |
| // or the row in this menu that opened to be a submenu |
| private var anchorRow:IListItemRenderer; |
| |
| //private var _anchor:String; // reference to the ID of the last opened submenu within a menu level |
| //private var _anchorIndex:int; // reference to the rowIndex of a menu's anchor in the parent menu |
| |
| private var subMenu:Menu; |
| |
| mx_internal var popupTween:Tween; |
| |
| mx_internal var supposedToLoseFocus:Boolean = false; |
| |
| /** |
| * @private |
| * When this timer fires, we'll open a submenu |
| */ |
| mx_internal var openSubMenuTimer:int = 0; |
| |
| /** |
| * @private |
| * When this timer fires, we'll hide this menu |
| */ |
| mx_internal var closeTimer:int = 0; |
| |
| /** |
| * @private |
| * Storage variable for the original dataProvider |
| */ |
| mx_internal var _rootModel:ICollectionView; |
| |
| /** |
| * @private |
| * Storage variable for dataProvider passed to parent |
| */ |
| mx_internal var _listDataProvider:ICollectionView; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //-------------------------------------------------------------------------- |
| // parentMenu |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _parentMenu:Menu; |
| |
| /** |
| * The parent menu in a hierarchical chain of menus, where the current |
| * menu is a submenu of the parent. |
| * |
| * @return The parent Menu control. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get parentMenu():Menu |
| { |
| return _parentMenu; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set parentMenu(value:Menu):void |
| { |
| _parentMenu = value; |
| |
| // setup weak references |
| value.addEventListener(FlexEvent.HIDE, parentHideHandler, false, 0, true); |
| value.addEventListener("rowHeightChanged", parentRowHeightHandler, false, 0, true); |
| value.addEventListener("iconFieldChanged", parentIconFieldHandler, false, 0, true); |
| value.addEventListener("iconFunctionChanged", parentIconFunctionHandler, false, 0, true); |
| value.addEventListener("labelFieldChanged", parentLabelFieldHandler, false, 0, true); |
| value.addEventListener("labelFunctionChanged", parentLabelFunctionHandler, false, 0, true); |
| value.addEventListener("itemRendererChanged", parentItemRendererHandler, false, 0, true); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // dataDescriptor |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var _dataDescriptor:IMenuDataDescriptor = |
| new DefaultDataDescriptor(); |
| |
| /** |
| * The object that accesses and manipulates data in the data provider. |
| * The Menu 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 root menu is used |
| * for all submenus. |
| * |
| * The default value is an internal instance of the |
| * DefaultDataDescriptor class. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @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; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // dataProvider |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal var dataProviderChanged:Boolean = false; |
| |
| /** |
| * @private |
| * Convert user data to collection. |
| * |
| * @see mx.controls.listClasses.ListBase |
| * @see mx.controls.List |
| * @see mx.controls.Tree |
| */ |
| override public function set dataProvider(value:Object):void |
| { |
| // 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(); |
| } |
| |
| //flag for processing in commitProps |
| dataProviderChanged = true; |
| invalidateProperties(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function get dataProvider():Object |
| { |
| var model:* = super.dataProvider; |
| if (model == null) |
| { |
| if (_rootModel != null ) |
| { |
| return _rootModel; |
| } |
| } |
| else |
| { |
| return model; |
| } |
| return null; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // showRoot |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Storage variable for showRoot flag. |
| */ |
| mx_internal var _showRoot:Boolean = true; |
| |
| /** |
| * @private |
| * Storage variable for changes to showRoot. |
| */ |
| mx_internal var showRootChanged:Boolean = false; |
| |
| /** |
| * A Boolean flag that specifies whether to display the data provider's |
| * root node. |
| * |
| * If the dataProvider object has a root node, and showRoot is set to |
| * <code>false</code>, the Menu control does not display the root node; |
| * only the descendants of the root node will be displayed. |
| * |
| * This flag has no effect on data providers without root nodes, |
| * like Lists and Arrays. |
| * |
| * @default true |
| * @see #hasRoot |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @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) |
| { |
| _showRoot = value; |
| showRootChanged = true; |
| invalidateProperties(); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // hasRoot |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Flag to indicate if the model has a root |
| */ |
| mx_internal var _hasRoot:Boolean = false; |
| |
| /** |
| * A flag that indicates that the current data provider has a root node; for example, |
| * a single top node in a hierarchical structure. XML and Object |
| * are examples of types that have a root node, while Lists and Arrays do |
| * not. |
| * |
| * @default false |
| * @see #showRoot |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get hasRoot():Boolean |
| { |
| return _hasRoot; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // defaultButton |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| public function get defaultButton():IFlexDisplayObject |
| { |
| return null; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set defaultButton(value:IFlexDisplayObject):void |
| { |
| |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function parentHideHandler(event:FlexEvent):void |
| { |
| visible = false; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentRowHeightHandler(event:Event):void |
| { |
| rowHeight = parentMenu.rowHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentIconFieldHandler(event:Event):void |
| { |
| iconField = parentMenu.iconField; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentIconFunctionHandler(event:Event):void |
| { |
| iconFunction = parentMenu.iconFunction; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentLabelFieldHandler(event:Event):void |
| { |
| labelField = parentMenu.labelField; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentLabelFunctionHandler(event:Event):void |
| { |
| labelFunction = parentMenu.labelFunction; |
| } |
| |
| /** |
| * @private |
| */ |
| private function parentItemRendererHandler(event:Event):void |
| { |
| itemRenderer = parentMenu.itemRenderer; |
| } |
| |
| // ------------------------------------------------------------------------- |
| // horizontalScrollPolicy |
| // ------------------------------------------------------------------------- |
| |
| [Inspectable(environment="none")] |
| |
| /** |
| * @private |
| */ |
| override public function get horizontalScrollPolicy():String |
| { |
| return ScrollPolicy.OFF; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set horizontalScrollPolicy(value:String):void |
| { |
| } |
| |
| // ------------------------------------------------------------------------- |
| // verticalScrollPolicy |
| // ------------------------------------------------------------------------- |
| |
| [Inspectable(environment="none")] |
| |
| /** |
| * @private |
| */ |
| override public function get verticalScrollPolicy():String |
| { |
| return ScrollPolicy.OFF; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set verticalScrollPolicy(value:String):void |
| { |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Called by the initialize() method of UIComponent |
| * to hook in the accessibility code. |
| */ |
| override protected function initializeAccessibility():void |
| { |
| if (createAccessibilityImplementation != null) |
| createAccessibilityImplementation(this); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| if (showRootChanged) |
| { |
| if (!_hasRoot) |
| showRootChanged = false; |
| } |
| |
| if (dataProviderChanged || showRootChanged) |
| { |
| var tmpCollection:ICollectionView; |
| |
| // Reset flags. |
| dataProviderChanged = false; |
| showRootChanged = 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); |
| } |
| } |
| |
| if (_listDataProvider) |
| { |
| _listDataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, |
| collectionChangeHandler, false); |
| } |
| |
| // At this point _rootModel may be null so we dont need to continue. |
| if (_rootModel) |
| { |
| if (!tmpCollection) |
| tmpCollection = _rootModel; |
| |
| _listDataProvider = tmpCollection; |
| super.dataProvider = tmpCollection; |
| |
| // not really a default handler, but we need to be later than the wrapper |
| _listDataProvider.addEventListener(CollectionEvent.COLLECTION_CHANGE, |
| collectionChangeHandler, |
| false, |
| EventPriority.DEFAULT_HANDLER, true); |
| } |
| else |
| { |
| _listDataProvider = null; |
| super.dataProvider = null; |
| } |
| } |
| |
| // Send it up the chain. |
| super.commitProperties(); |
| } |
| |
| |
| /** |
| * Calculates the preferred width and height of the Menu based on the |
| * widths and heights of its menu items. This method does not take into |
| * account the position and size of submenus. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override protected function measure():void |
| { |
| super.measure(); |
| |
| if (!dataProvider || dataProvider.length == 0) |
| { |
| // we show a collapsed menu if nothing in it so we can dispatch |
| // a menuShow and give someone a chance to add things |
| measuredWidth = 0; |
| measuredHeight = 0; |
| } |
| else |
| { |
| var vm:EdgeMetrics = viewMetrics; |
| measuredMinWidth = measuredWidth = measureWidthOfItems(0, dataProvider.length); |
| |
| var requiredHeight:int; |
| if (variableRowHeight) |
| requiredHeight = measureHeightOfItems(0, dataProvider.length); |
| else |
| requiredHeight = dataProvider.length * rowHeight; |
| |
| measuredMinHeight = measuredHeight = |
| requiredHeight + vm.top + vm.bottom; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override public function measureWidthOfItems(index:int = -1, count:int = 0):Number |
| { |
| var w:Number = 0; |
| |
| // reset max's so we can shrink if we should |
| var minimumLeftMargin:Number = getStyle("leftIconGap"); |
| var minimumRightMargin:Number = getStyle("rightIconGap"); |
| maxMeasuredIconWidth = 0; |
| maxMeasuredTypeIconWidth = 0; |
| maxMeasuredBranchIconWidth = 0; |
| useTwoColumns = false; |
| |
| if (collection && collection.length) |
| { |
| var placeHolder:CursorBookmark = iterator.bookmark; |
| var previousCount:int = count; |
| |
| // we loop through up to 2 times because the first time, we may |
| // need to do a re-draw due to the margins changing from a |
| // larger icon width |
| for (var i:int = 0; i < 2; i++) |
| { |
| iterator.seek(CursorBookmark.FIRST, index); |
| count = previousCount; |
| var valuesChanged:Boolean = false; |
| |
| while (count) |
| { |
| var data:Object = iterator.current; |
| var menuListData:MenuListData; |
| |
| var item:IListItemRenderer = hiddenItem = getMeasuringRenderer(data); |
| item.explicitWidth = NaN; |
| |
| setupRendererFromData(item, data); |
| w = Math.max(item.getExplicitOrMeasuredWidth(), w); |
| |
| if (item is IMenuItemRenderer) |
| { |
| var menuItemRenderer:IMenuItemRenderer = IMenuItemRenderer(item); |
| |
| if (menuItemRenderer.measuredIconWidth > maxMeasuredIconWidth) |
| { |
| maxMeasuredIconWidth = menuItemRenderer.measuredIconWidth; |
| valuesChanged = true; |
| } |
| |
| if (menuItemRenderer.measuredTypeIconWidth > maxMeasuredTypeIconWidth) |
| { |
| maxMeasuredTypeIconWidth = menuItemRenderer.measuredTypeIconWidth; |
| valuesChanged = true; |
| } |
| |
| if (menuItemRenderer.measuredBranchIconWidth > maxMeasuredBranchIconWidth) |
| { |
| maxMeasuredBranchIconWidth = menuItemRenderer.measuredBranchIconWidth; |
| valuesChanged = true; |
| } |
| |
| // use two columns if the same MenuItemRenderer can have both a type icon and a |
| // regular icon |
| if (menuItemRenderer.measuredIconWidth > 0 && menuItemRenderer.measuredTypeIconWidth) |
| { |
| useTwoColumns = true; |
| valuesChanged = true; |
| } |
| } |
| |
| count--; |
| if (!iterator.moveNext()) |
| break; |
| } |
| |
| if (i == 0) |
| { |
| if (!(valuesChanged && |
| (maxMeasuredIconWidth + maxMeasuredTypeIconWidth > minimumLeftMargin || |
| maxMeasuredBranchIconWidth > minimumRightMargin))) |
| { |
| break; |
| } |
| } |
| } |
| |
| iterator.seek(placeHolder, 0); |
| } |
| if (!w) |
| { |
| // If we couldn't calculate a width, use 200 |
| w = 200; |
| } |
| |
| w += getStyle("paddingLeft") + getStyle("paddingRight"); |
| |
| return w; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, |
| unscaledHeight:Number):void |
| { |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| if (border) |
| { |
| border.move(0, 0); |
| border.visible = (dataProvider != null && dataProvider.length > 0); |
| } |
| |
| // there's sort of a bug in the player that if we don't give size to the |
| // hiddenItem it gets set to width = 0 and then you can't measure text anymore. |
| if (hiddenItem) |
| hiddenItem.setActualSize(unscaledWidth, hiddenItem.getExplicitOrMeasuredHeight()); |
| } |
| |
| /** |
| * @private |
| * Relying on UIComponent description. |
| */ |
| override public function styleChanged(styleProp:String):void |
| { |
| super.styleChanged(styleProp); |
| |
| deleteDependentSubMenus(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function drawItem(item:IListItemRenderer, |
| selected:Boolean = false, |
| highlighted:Boolean = false, |
| caret:Boolean = false, |
| transition:Boolean = false):void |
| { |
| if (!getStyle("useRollOver")) |
| { |
| super.drawItem(item, selected, false, false, transition); |
| } |
| else |
| { |
| super.drawItem(item, selected, highlighted, caret, transition); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function configureScrollBars():void |
| { |
| } |
| |
| override mx_internal function clearHighlight(item:IListItemRenderer):void |
| { |
| var uid:String = itemToUID(item.data); |
| |
| drawItem(visibleData[uid], isItemSelected(item.data), |
| false, uid == caretUID); |
| |
| var pt:Point = itemRendererToIndices(item); |
| if (pt) |
| { |
| var menuEvent:MenuEvent = new MenuEvent(MenuEvent.ITEM_ROLL_OUT); |
| menuEvent.menu = this; |
| menuEvent.index = getRowIndex(item); |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(item.data); |
| menuEvent.item = item.data; |
| menuEvent.itemRenderer = item; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| } |
| } |
| |
| /** |
| * @private |
| * Determines if the itemrenderer is a separator. If so, return null to prevent separators |
| * from highlighting and emitting menu-level events. |
| */ |
| override protected function mouseEventToItemRenderer(event:MouseEvent):IListItemRenderer |
| { |
| var row:IListItemRenderer = super.mouseEventToItemRenderer(event); |
| |
| if (row && row.data && _dataDescriptor.getType(row.data) == "separator") |
| return null; |
| else return row; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function setFocus():void |
| { |
| super.setFocus(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function focusOutHandler(event:FocusEvent):void |
| { |
| super.focusOutHandler(event); |
| if (!supposedToLoseFocus) |
| { |
| hideAllMenus(); |
| } |
| supposedToLoseFocus = false; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function dispatchEvent(event:Event):Boolean |
| { |
| if (!(event is MenuEvent) && event is ListEvent && |
| (event.type == ListEvent.ITEM_ROLL_OUT || |
| event.type == ListEvent.ITEM_ROLL_OVER || |
| event.type == ListEvent.CHANGE)) |
| { |
| // we don't want to dispatch ListEvent.ITEM_ROLL_OVER or |
| // ListEvent.ITEM_ROLL_OUT or ListEvent.CHANGE events |
| // because Menu dispatches its own |
| event.stopImmediatePropagation(); |
| } |
| |
| // in case we encounter a ListEvent.ITEM_CLICK from |
| // a superclass that we did not account for, |
| // lets convert the ListEvent and pass it on up to |
| // avoid an RTE |
| if (!(event is MenuEvent) && event is ListEvent && |
| (event.type == ListEvent.ITEM_CLICK)) |
| { |
| var me:MenuEvent = new MenuEvent(event.type, |
| event.bubbles, |
| event.cancelable); |
| me.item = ListEvent(event).itemRenderer.data; |
| me.label = itemToLabel(ListEvent(event).itemRenderer); |
| return super.dispatchEvent(me); |
| } |
| |
| // we'll let everything else go through |
| return super.dispatchEvent(event); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Toggles the menu item. The menu item type identifier must be a |
| * check box or radio button, otherwise this method has no effect. |
| * |
| * @param item The menu item to toggle. |
| * @param toggle Boolean value that indicates whether the item is |
| * toggled. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| protected function setMenuItemToggled(item:Object, toggle:Boolean):void |
| { |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| if (_dataDescriptor.getType(item) == "radio") |
| { |
| var groupName:String = _dataDescriptor.getGroupName(item); |
| |
| // Find other items with the same groupName and toggle them to the state opposite the item. |
| // Toggle the item. |
| for (var i:int = 0; i < listItems.length; i++) |
| { |
| var row:IListItemRenderer = listItems[i][0]; |
| var rowItem:Object = row.data; |
| if (_dataDescriptor.getType(rowItem) == "radio" && |
| _dataDescriptor.getGroupName(rowItem) == groupName) |
| { |
| _dataDescriptor.setToggled(rowItem, (rowItem == item)); |
| } |
| } |
| } |
| if (toggle != _dataDescriptor.isToggled(item)) |
| { |
| _dataDescriptor.setToggled(item, toggle); |
| } |
| } |
| |
| /** |
| * Creates a new MenuListData instance and populates the fields based on |
| * the input data provider item. |
| * |
| * @param data The data provider item used to populate the ListData. |
| * @param uid The UID for the item. |
| * @param rowNum The index of the item in the data provider. |
| * |
| * @return A newly constructed ListData object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| override protected function makeListData(data:Object, uid:String, |
| rowNum:int):BaseListData |
| { |
| var menuListData:MenuListData = new MenuListData(itemToLabel(data), itemToIcon(data), |
| labelField, uid, this, rowNum); |
| |
| menuListData.maxMeasuredIconWidth = maxMeasuredIconWidth; |
| menuListData.maxMeasuredTypeIconWidth = maxMeasuredTypeIconWidth; |
| menuListData.maxMeasuredBranchIconWidth = maxMeasuredBranchIconWidth; |
| menuListData.useTwoColumns = useTwoColumns; |
| |
| return menuListData; |
| } |
| |
| /** |
| * Shows the Menu control. If the Menu control is not visible, this method |
| * places the Menu in the upper-left corner of the parent application at |
| * the given coordinates, resizes the Menu control as needed, and makes |
| * the Menu control visible. |
| * |
| * The x and y arguments of the <code>show()</code> method specify the |
| * coordinates of the upper-left corner of the Menu control relative to the |
| * sandbox root, which is not necessarily the direct parent of the |
| * Menu control. |
| * |
| * For example, if the Menu control is in an HBox container which is |
| * nested within a Panel container, the x and y coordinates are |
| * relative to the Application container, not to the HBox container. |
| * |
| * @param x Horizontal location of the Menu control's upper-left |
| * corner (optional). |
| * |
| * @param y Vertical location of the Menu control's upper-left |
| * corner (optional). |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function show(xShow:Object = null, yShow:Object = null):void |
| { |
| //this could be an empty menu so we'll return if it is |
| if (collection && collection.length == 0) |
| return; |
| |
| // If parent is closed, then don't show this submenu |
| if (parentMenu && !parentMenu.visible) |
| return; |
| |
| // If I'm already visible, then do nothing |
| if (visible) |
| return; |
| |
| if (parentDisplayObject && (!parent || !parent.contains(parentDisplayObject))) |
| { |
| PopUpManager.addPopUp(this, parentDisplayObject, false); |
| addEventListener(MenuEvent.MENU_HIDE, menuHideHandler, false, EventPriority.DEFAULT_HANDLER); |
| } |
| |
| // Fire an event |
| var menuEvent:MenuEvent = new MenuEvent(MenuEvent.MENU_SHOW); |
| menuEvent.menu = this; |
| menuEvent.menuBar = sourceMenuBar; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| // Activate the focus manager for that menu |
| var awm:IActiveWindowManager = |
| IActiveWindowManager(systemManager.getImplementation("mx.managers::IActiveWindowManager")); |
| awm.activate(this); |
| |
| // Position it |
| if (xShow !== null && !isNaN(Number(xShow))) |
| x = Number(xShow); |
| if (yShow !== null && !isNaN(Number(yShow))) |
| y = Number(yShow); |
| |
| // Adjust for menus that extend out of bounds |
| var sm:ISystemManager = systemManager.topLevelSystemManager; |
| var sbRoot:DisplayObject = sm.getSandboxRoot(); |
| if (this != getRootMenu()) |
| { |
| // do x |
| var screen:Rectangle; |
| var pt:Point = new Point(x, y); |
| pt = sbRoot.localToGlobal(pt); |
| |
| screen = sm.getVisibleApplicationRect(null, true); |
| |
| var shift:Number = pt.x + width - screen.right; |
| if (shift > 0) |
| x = Math.max(x - shift, 0); |
| |
| // now do y |
| shift = pt.y + height - screen.bottom; |
| if (shift > 0) |
| y = Math.max(y - shift, 0); |
| } |
| |
| // Make sure the Menu's width and height has been determined |
| // before we try to set the size for its mask |
| UIComponentGlobals.layoutManager.validateClient(this, true); |
| setActualSize(getExplicitOrMeasuredWidth(), getExplicitOrMeasuredHeight()); |
| |
| cacheAsBitmap = true; |
| |
| var duration:Number = getStyle("openDuration"); |
| if (duration != 0) |
| { |
| scrollRect = new Rectangle(0, 0, unscaledWidth, 0); |
| |
| // Make it visible |
| visible = true; |
| |
| UIComponentGlobals.layoutManager.validateNow(); |
| |
| // Block all layout, responses from web service, and other background |
| // processing until the tween finishes executing. |
| UIComponent.suspendBackgroundProcessing(); |
| |
| popupTween = new Tween(this, [0,0], [unscaledWidth,unscaledHeight], duration); |
| } |
| else |
| { |
| // if duration is zero, this should allow alternate |
| // effects specified via showEffect to work |
| UIComponentGlobals.layoutManager.validateNow(); |
| visible = true; |
| } |
| |
| focusManager.setFocus(this); |
| supposedToLoseFocus = true; |
| |
| // If the user clicks outside the menu, then hide the menu |
| sbRoot.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownOutsideHandler, false, 0, true); |
| addEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, mouseDownOutsideHandler, false, 0, true); |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function onTweenUpdate(value:Object):void |
| { |
| // Slide the mask down until it covers the Menu |
| scrollRect = new Rectangle(0, 0, value[0], value[1]); |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function onTweenEnd(value:Object):void |
| { |
| UIComponent.resumeBackgroundProcessing(); |
| scrollRect = null; |
| popupTween = null; |
| } |
| |
| /** |
| * Hides the Menu control and any of its submenus if the Menu control is |
| * visible. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function hide():void |
| { |
| if (visible) |
| { |
| // Kill any tween that's currently running |
| if (popupTween) |
| popupTween.endTween(); |
| |
| clearSelected(); |
| if (anchorRow) |
| { |
| drawItem(anchorRow, false, false); |
| anchorRow = null; |
| } |
| |
| visible = false; |
| // isPressed = false; |
| |
| // Now that the menu is no longer visible, it doesn't need |
| // to listen to mouseDown events anymore. |
| var sbRoot:DisplayObject = systemManager.getSandboxRoot(); |
| sbRoot.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownOutsideHandler); |
| removeEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, mouseDownOutsideHandler); |
| |
| // Fire an event |
| var menuEvent:MenuEvent = new MenuEvent(MenuEvent.MENU_HIDE); |
| menuEvent.menu = this; |
| menuEvent.menuBar = sourceMenuBar; |
| getRootMenu().dispatchEvent(menuEvent); |
| } |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Event Handlers |
| // ------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function collectionChangeHandler(event:Event):void |
| { |
| //trace("[Menu] caught model changed:", CollectionEvent(event).kind); |
| if (event is CollectionEvent) |
| { |
| var ce:CollectionEvent = CollectionEvent(event); |
| if (ce.kind == CollectionEventKind.ADD) |
| { |
| super.collectionChangeHandler(event); |
| dataProviderChanged = true; |
| invalidateProperties(); |
| invalidateSize(); |
| UIComponentGlobals.layoutManager.validateClient(this); |
| setActualSize(getExplicitOrMeasuredWidth(), getExplicitOrMeasuredHeight()); |
| } |
| else if (ce.kind == CollectionEventKind.REMOVE) |
| { |
| super.collectionChangeHandler(event); |
| dataProviderChanged = true; |
| invalidateProperties(); |
| invalidateSize(); |
| UIComponentGlobals.layoutManager.validateClient(this); |
| setActualSize(getExplicitOrMeasuredWidth(), getExplicitOrMeasuredHeight()); |
| } |
| else if (ce.kind == CollectionEventKind.REFRESH) |
| { |
| dataProviderChanged = true; |
| invalidateProperties(); |
| invalidateSize(); |
| } |
| else if (ce.kind == CollectionEventKind.RESET) |
| { |
| dataProviderChanged = true; |
| invalidateProperties(); |
| invalidateSize(); |
| } |
| } |
| itemsSizeChanged = true; |
| invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| */ |
| private function mouseDownOutsideHandler(event:Event):void |
| { |
| if (event is MouseEvent) |
| { |
| var mouseEvent:MouseEvent = MouseEvent(event); |
| if (!isMouseOverMenu(mouseEvent) && !isMouseOverMenuBarItem(mouseEvent)) |
| hideAllMenus(); |
| } |
| else if (event is SandboxMouseEvent) |
| hideAllMenus(); |
| } |
| |
| /** |
| * @private |
| * Removes the root menu from the display list. This is called only for |
| * menus created using "createMenu". |
| * |
| * MJM private static? |
| */ |
| private static function menuHideHandler(event:MenuEvent):void |
| { |
| var menu:Menu = Menu(event.target); |
| if (!event.isDefaultPrevented() && event.menu == menu) |
| { |
| menu.supposedToLoseFocus = true; |
| PopUpManager.removePopUp(menu); |
| menu.removeEventListener(MenuEvent.MENU_HIDE, menuHideHandler); |
| } |
| } |
| |
| /** |
| * @private |
| * Handle mouse release on an item. |
| * |
| * For separators or items with sub-menu, do nothing. |
| * For check items, toggle state, then fire change event. |
| * For radio items, toggle state if untoggled, then fire change event. |
| * For normal items, fire change event. |
| */ |
| override protected function mouseUpHandler(event:MouseEvent):void |
| { |
| var menuEvent:MenuEvent; |
| |
| if (!enabled || !selectable || !visible) |
| return; |
| |
| super.mouseUpHandler(event); |
| |
| var row:IListItemRenderer = mouseEventToItemRenderer(event); |
| |
| var item:Object; |
| if (row && row.data) |
| item = row.data; |
| |
| if (item != null && _dataDescriptor.isEnabled(item) && !_dataDescriptor.isBranch(item)) |
| { |
| // Toggle the item if it is a check or it is previously untoggled radio item. |
| // For previously toggled radio items, we will dispatch MenuEvent.ITEM_CLICK events but |
| // won't toggle or dispatch MenuEvent.CHANGE events. |
| // Custom item renderers can choose how to display this state |
| var toggleItem:Boolean = _dataDescriptor.getType(item) != "radio" |
| || !_dataDescriptor.isToggled(item); |
| |
| if (toggleItem) |
| setMenuItemToggled(item, !_dataDescriptor.isToggled(item)); |
| |
| menuEvent = new MenuEvent(MenuEvent.ITEM_CLICK); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(item); |
| menuEvent.item = item; |
| menuEvent.itemRenderer = row; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| if (toggleItem) |
| { |
| menuEvent = new MenuEvent(MenuEvent.CHANGE); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(item); |
| menuEvent.item = item; |
| menuEvent.itemRenderer = row; |
| getRootMenu().dispatchEvent(menuEvent); |
| } |
| |
| hideAllMenus(); |
| } |
| } |
| |
| /** |
| * @private |
| * Extend the behavior from ScrollSelectList to handle row presses over |
| * separators, branch items, and disabled row items. |
| */ |
| override protected function mouseDownHandler(event:MouseEvent):void |
| { |
| var row:IListItemRenderer = mouseEventToItemRenderer(event); |
| |
| var item:Object; |
| if (row && row.data) |
| item = row.data; |
| |
| // only allow action on items that are enabled which are not branches |
| if (item && _dataDescriptor.isEnabled(item) && !_dataDescriptor.isBranch(item)) |
| super.mouseDownHandler(event); |
| } |
| |
| |
| /** |
| * @private |
| * Notify listeners when the mouse leaves |
| */ |
| override protected function mouseOutHandler(event:MouseEvent):void |
| { |
| if (!enabled || !selectable || !visible) |
| return; |
| |
| systemManager.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true); |
| |
| // Fire the appropriate rollout event |
| var row:IListItemRenderer = mouseEventToItemRenderer(event); |
| |
| if (!row) |
| return; |
| |
| var item:Object; |
| if (row && row.data) |
| item = row.data; |
| |
| // If a submenu was waiting to open, then clear the timeout |
| // so it doesn't open |
| if (openSubMenuTimer) |
| { |
| clearInterval(openSubMenuTimer); |
| openSubMenuTimer = 0; |
| } |
| |
| // either we're rolling onto different subpieces of ourself or our |
| // highlight indicator, or the clearing of the highlighted item has |
| // already happened care of the mouseMove handler |
| if (itemRendererContains(row, event.relatedObject) || |
| itemRendererContains(row, DisplayObject(event.target)) || |
| event.relatedObject == highlightIndicator || |
| event.relatedObject == listContent || |
| !highlightItemRenderer) |
| { |
| return; |
| } |
| |
| if (getStyle("useRollOver") && item) |
| { |
| clearHighlight(row); |
| } |
| } |
| |
| /** |
| * @private |
| * Extend the behavior from ScrollSelectList to pop up submenus |
| */ |
| override protected function mouseOverHandler(event:MouseEvent):void |
| { |
| if (!enabled || !selectable || !visible) |
| return; |
| |
| systemManager.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler, true, 0, true); |
| |
| var row:IListItemRenderer = mouseEventToItemRenderer(event); |
| |
| if (!row) |
| return; |
| |
| var item:Object; |
| if (row && row.data) |
| item = row.data; |
| |
| isPressed = event.buttonDown; |
| |
| if (row && row != anchorRow) |
| { |
| if (anchorRow) |
| // no longer on anchor so close its submenu |
| drawItem(anchorRow, false, false); |
| if (subMenu) |
| { |
| subMenu.supposedToLoseFocus = true; |
| subMenu.closeTimer = setTimeout(closeSubMenu, 250, subMenu); |
| } |
| subMenu = null; |
| anchorRow = null; |
| } |
| else if (subMenu && subMenu.subMenu) |
| { |
| // Close grandchild submenus - only children are allowed to be open |
| subMenu.subMenu.hide(); |
| } |
| |
| // Update the view |
| if (_dataDescriptor.isBranch(item) && _dataDescriptor.isEnabled(item)) |
| { |
| anchorRow = row; |
| |
| // If there's a timer waiting to close this menu, cancel the |
| // timer so that the menu doesn't close |
| if (subMenu && subMenu.closeTimer) |
| { |
| clearInterval(subMenu.closeTimer); |
| subMenu.closeTimer = 0; |
| } |
| |
| // If the menu is not visible, pop it up after a short delay |
| if (!subMenu || !subMenu.visible) |
| { |
| if (openSubMenuTimer) |
| clearInterval(openSubMenuTimer); |
| |
| openSubMenuTimer = setTimeout( |
| function(row:IListItemRenderer):void |
| { |
| openSubMenu(row); |
| }, |
| 250, |
| row); |
| } |
| } |
| |
| // Send event and update view |
| if (item && _dataDescriptor.isEnabled(item)) |
| { |
| // we're rolling onto different subpieces of ourself or our highlight indicator |
| if (event.relatedObject) |
| { |
| if (itemRendererContains(row, event.relatedObject) || |
| row == lastHighlightItemRenderer || |
| event.relatedObject == highlightIndicator) |
| return; |
| } |
| } |
| |
| if (row) |
| { |
| drawItem(row, false, Boolean(item && _dataDescriptor.isEnabled(item))); |
| |
| if (isPressed) |
| { |
| if (item && _dataDescriptor.isEnabled(item)) |
| { |
| if (!_dataDescriptor.isBranch(item)) |
| selectItem(row, event.shiftKey, event.ctrlKey); |
| else |
| clearSelected(); |
| } |
| } |
| |
| if (item && _dataDescriptor.isEnabled(item)) |
| { |
| // Fire the appropriate rollover event |
| var menuEvent:MenuEvent = new MenuEvent(MenuEvent.ITEM_ROLL_OVER); |
| menuEvent.menu = this; |
| menuEvent.index = getRowIndex(row); |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(item); |
| menuEvent.item = item; |
| menuEvent.itemRenderer = row; |
| getRootMenu().dispatchEvent(menuEvent); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * ListBase uses the mouseClickHandler dispatch the ListEvent.ITEM_CLICK event. |
| * In Menu we chose to do that in the mouseUpHandler so we will do nothing |
| * in the mouseClickHandler. |
| */ |
| override protected function mouseClickHandler(event:MouseEvent):void |
| { |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function keyDownHandler(event:KeyboardEvent):void |
| { |
| var row:IListItemRenderer = selectedIndex == -1 ? null : listItems[selectedIndex - verticalScrollPosition][0]; |
| var rowData:Object = row ? row.data : null; |
| var menu:Menu = row ? IMenuItemRenderer(row).menu : null; |
| var menuEvent:MenuEvent; |
| |
| // If rtl layout, need to swap LEFT and RIGHT so correct action |
| // is done. |
| var keyCode:uint = mapKeycodeForLayoutDirection(event); |
| |
| // Handle Key.UP Navigation |
| if (keyCode == Keyboard.UP) |
| { |
| if (rowData && _dataDescriptor.isBranch(rowData) && menu && menu.visible) |
| { |
| supposedToLoseFocus = true; |
| menu.setFocus(); |
| |
| // start at end and move up 1 |
| menu.moveSelBy(menu.dataProvider.length, -1); |
| } |
| else |
| { |
| moveSelBy(selectedIndex, -1); |
| } |
| |
| event.stopPropagation(); |
| } |
| |
| // Handle Key.DOWN Navigation |
| else if (keyCode == Keyboard.DOWN) |
| { |
| if (rowData && _dataDescriptor.isBranch(rowData) && menu && menu.visible) |
| { |
| supposedToLoseFocus = true; |
| menu.setFocus(); |
| |
| // start at -1 and move down 1 |
| menu.moveSelBy(-1, 1); |
| } |
| else |
| { |
| moveSelBy(selectedIndex, 1); |
| } |
| |
| event.stopPropagation(); |
| } |
| |
| // Handle Key.RIGHT Navigation |
| else if (keyCode == Keyboard.RIGHT) |
| { |
| if (rowData && _dataDescriptor.isBranch(rowData)) |
| { |
| openSubMenu(row); |
| menu = IMenuItemRenderer(row).menu; |
| |
| supposedToLoseFocus = true; |
| menu.setFocus(); |
| |
| // start at -1 and move down 1 |
| menu.moveSelBy(-1, 1); |
| } |
| else |
| { |
| // jump to next sibling in the menubar |
| if (sourceMenuBar) |
| { |
| supposedToLoseFocus = true; |
| sourceMenuBar.setFocus(); |
| // Redispatch the event to the MenuBar |
| // and let its keyDownHandler() handle it. |
| sourceMenuBar.dispatchEvent(event); |
| } |
| } |
| event.stopPropagation(); |
| } |
| |
| // Handle Key.LEFT Navigation |
| else if (keyCode == Keyboard.LEFT) |
| { |
| if (_parentMenu) |
| { |
| supposedToLoseFocus = true; |
| hide(); // hide this menu |
| _parentMenu.setFocus(); |
| } |
| else |
| { |
| // jump to previous sibling in the menubar |
| if (sourceMenuBar) |
| { |
| supposedToLoseFocus = true; |
| sourceMenuBar.setFocus(); |
| // Redispatch the event to the MenuBar |
| // and let its keyDownHandler() handle it. |
| sourceMenuBar.dispatchEvent(event); |
| } |
| } |
| |
| event.stopPropagation(); |
| } |
| |
| // Handle Key.ENTER Commands |
| else if (keyCode == Keyboard.ENTER || keyCode == Keyboard.SPACE) |
| { |
| if (rowData && _dataDescriptor.isBranch(rowData)) |
| { |
| openSubMenu(row); |
| menu = IMenuItemRenderer(row).menu; |
| |
| supposedToLoseFocus = true; |
| menu.setFocus(); |
| |
| // start at -1 and move down 1 |
| menu.moveSelBy(-1, 1); |
| } |
| else if (rowData) |
| { |
| // Toggle the item regardless of whether it is a check or radio |
| // Custom item renderers can choose how to display this state |
| setMenuItemToggled(rowData, !_dataDescriptor.isToggled(rowData)); |
| |
| menuEvent = new MenuEvent(MenuEvent.ITEM_CLICK); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(rowData); |
| menuEvent.item = rowData; |
| menuEvent.itemRenderer = row; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| menuEvent = new MenuEvent(MenuEvent.CHANGE); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(rowData); |
| menuEvent.item = rowData; |
| menuEvent.itemRenderer = row; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| hideAllMenus(); |
| } |
| |
| event.stopPropagation(); |
| } |
| |
| // Handle Key.ESCAPE commands |
| else if (keyCode == Keyboard.TAB) |
| { |
| menuEvent = new MenuEvent(MenuEvent.MENU_HIDE); |
| menuEvent.menu = getRootMenu(); |
| menuEvent.menuBar = sourceMenuBar; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| hideAllMenus(); |
| |
| event.stopPropagation(); |
| } |
| |
| else if (keyCode == Keyboard.ESCAPE) |
| { |
| if (_parentMenu) |
| { |
| supposedToLoseFocus = true; |
| hide(); // hide this menu |
| _parentMenu.setFocus(); |
| } |
| else |
| { |
| menuEvent = new MenuEvent(MenuEvent.MENU_HIDE); |
| menuEvent.menu = getRootMenu(); |
| menuEvent.menuBar = sourceMenuBar; |
| getRootMenu().dispatchEvent(menuEvent); |
| |
| hideAllMenus(); |
| |
| event.stopPropagation(); |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function moveSelBy(oldIndex:Number, incr:Number):void |
| { |
| var curIndex:Number = oldIndex; |
| if (isNaN(curIndex)) |
| curIndex = -1; |
| |
| var limit:Number = Math.max(0, Math.min(rowCount, collection.length) - 1); |
| |
| var newIndex:Number = curIndex; |
| var item:Object; |
| var curItem:int = 0; |
| |
| do |
| { |
| newIndex = newIndex + incr; |
| |
| if (curItem > limit) |
| return; |
| else |
| curItem++; |
| |
| if (newIndex > limit) |
| newIndex = 0; |
| else if (newIndex < 0) |
| newIndex = limit; |
| |
| item = listItems[newIndex][0]; |
| |
| } |
| while (item.data && (_dataDescriptor.getType(item.data) == "separator" || !_dataDescriptor.isEnabled(item.data))); |
| |
| var menuEvent:MenuEvent; |
| |
| if (selectedIndex != -1) |
| { |
| var oldItem:Object = listItems[selectedIndex][0]; |
| |
| menuEvent = new MenuEvent(MenuEvent.ITEM_ROLL_OUT); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(oldItem.data); |
| menuEvent.item = oldItem.data; |
| menuEvent.itemRenderer = IListItemRenderer(oldItem); |
| getRootMenu().dispatchEvent(menuEvent); |
| } |
| |
| if (item.data) |
| { |
| selectItem(listItems[newIndex - verticalScrollPosition][0], false, false); |
| |
| menuEvent = new MenuEvent(MenuEvent.ITEM_ROLL_OVER); |
| menuEvent.menu = this; |
| menuEvent.index = this.selectedIndex; |
| menuEvent.menuBar = sourceMenuBar; |
| menuEvent.label = itemToLabel(item.data); |
| menuEvent.item = item.data; |
| menuEvent.itemRenderer = IListItemRenderer(item); |
| getRootMenu().dispatchEvent(menuEvent); |
| } |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Menu visibility management |
| // ------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| mx_internal function openSubMenu(row:IListItemRenderer):void |
| { |
| supposedToLoseFocus = true; |
| |
| var r:Menu = getRootMenu(); |
| var menu:Menu; |
| |
| // check to see if the menu exists, if not create it |
| if (!IMenuItemRenderer(row).menu) |
| { |
| menu = new Menu(); |
| menu.parentMenu = this; |
| menu.owner = this; |
| menu.showRoot = showRoot; |
| menu.dataDescriptor = r.dataDescriptor; |
| menu.styleName = r; |
| menu.labelField = r.labelField; |
| menu.labelFunction = r.labelFunction; |
| menu.iconField = r.iconField; |
| menu.iconFunction = r.iconFunction; |
| menu.itemRenderer = r.itemRenderer; |
| menu.rowHeight = r.rowHeight; |
| menu.scaleY = r.scaleY; |
| menu.scaleX = r.scaleX; |
| |
| // if there's data and it has children then add the items |
| if (row.data && |
| _dataDescriptor.isBranch(row.data) && |
| _dataDescriptor.hasChildren(row.data)) |
| { |
| menu.dataProvider = _dataDescriptor.getChildren(row.data); |
| } |
| menu.sourceMenuBar = sourceMenuBar; |
| menu.sourceMenuBarItem = sourceMenuBarItem; |
| |
| IMenuItemRenderer(row).menu = menu; |
| PopUpManager.addPopUp(menu, r, false); |
| } |
| else |
| { |
| menu = IMenuItemRenderer(row).menu; |
| } |
| |
| var _do:DisplayObject = DisplayObject(row); |
| var sandBoxRootPoint:Point = new Point(0,0); |
| sandBoxRootPoint = _do.localToGlobal(sandBoxRootPoint); |
| // when loadMovied, you may not be in global coordinates |
| if (_do.root) //verify this is sufficient |
| sandBoxRootPoint = _do.root.globalToLocal(sandBoxRootPoint); |
| |
| // If the layout is mirrored, and isDirectionLeft then we add the |
| // menu's width to find the origin of menu, rather than subtracting. |
| // Similarly, if isDirectionLeft=false, we subtract the menu item's width. |
| |
| const isLayoutMirrored:Boolean = |
| (layoutDirection == LayoutDirection.RTL); |
| const menuWidth:Number = menu.getExplicitOrMeasuredWidth(); |
| const menuItemWidth:Number = row.width; |
| |
| // showX, showY are in sandbox root coordinates |
| var showY:Number = sandBoxRootPoint.y; |
| var showX:Number; |
| if (!isDirectionLeft) |
| showX = sandBoxRootPoint.x + ((isLayoutMirrored) ? -menuItemWidth : +menuItemWidth); |
| else |
| showX = sandBoxRootPoint.x + ((isLayoutMirrored) ? +menuWidth : -menuWidth); |
| |
| // If the layout has been mirrored, then showX is currently menu's right |
| // edge; compensate here. |
| if (isLayoutMirrored) |
| showX -= menuWidth; |
| |
| // convert to global coordinates to compare with getVisibleApplicationRect(). |
| // the screen is the visible coordinates of our sandbox (written in global coordinates) |
| var screen:Rectangle = systemManager.getVisibleApplicationRect(null, true); |
| var sbRoot:DisplayObject = systemManager.getSandboxRoot(); |
| |
| var screenPoint:Point = sbRoot.localToGlobal(new Point(showX, showY)); |
| |
| // do x |
| var shift:Number = screenPoint.x + menuWidth - screen.right; |
| if (shift > 0 || screenPoint.x < screen.x) |
| { |
| // if we want to ensure our parent's visible, let's |
| // modify the shift so that we're not just on-screen |
| // but we're also shifted away from our parent. |
| var shiftForParent:Number = getExplicitOrMeasuredWidth() + menu.getExplicitOrMeasuredWidth(); |
| |
| // if was going left, shift to right. otherwise, shift to left |
| if (isDirectionLeft) |
| shiftForParent *= -1; |
| |
| showX = Math.max(showX - shiftForParent, 0); |
| |
| // now make sure we're still on-screen again |
| screenPoint = new Point(showX, showY); |
| screenPoint = sbRoot.localToGlobal(screenPoint); |
| |
| // only shift if greater our position + width > width of screen |
| shift = Math.max(0, screenPoint.x + width - screen.right); |
| |
| showX = Math.max(showX - shift, 0); |
| } |
| |
| menu.isDirectionLeft = (isLayoutMirrored) ? (this.x <= showX) : (this.x > showX); |
| |
| // now do y |
| shift = screenPoint.y + menu.getExplicitOrMeasuredHeight() - screen.bottom; |
| if (shift > 0 || screenPoint.y < screen.y) |
| showY = Math.max(showY - shift, 0); |
| |
| menu.show(showX, showY); |
| |
| subMenu = menu; |
| clearInterval(openSubMenuTimer); |
| openSubMenuTimer = 0; |
| } |
| |
| /** |
| * @private |
| */ |
| private function closeSubMenu(menu:Menu):void |
| { |
| menu.hide(); |
| clearInterval(menu.closeTimer); |
| menu.closeTimer = 0; |
| } |
| |
| mx_internal function deleteDependentSubMenus():void |
| { |
| var n:int = listItems.length; |
| for (var i:int = 0; i < n; i++) |
| { |
| |
| // Check to see if the listItems array has a renderer at this index. |
| if (listItems[i][0]) |
| { |
| var subMenu:Menu = IMenuItemRenderer(listItems[i][0]).menu; |
| if (subMenu) |
| { |
| subMenu.deleteDependentSubMenus(); |
| PopUpManager.removePopUp(subMenu); |
| IMenuItemRenderer(listItems[i][0]).menu = null; |
| } |
| } |
| } |
| } |
| |
| mx_internal function hideAllMenus():void |
| { |
| getRootMenu().hide(); |
| getRootMenu().deleteDependentSubMenus(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // Internal utilities |
| //-------------------------------------------------------------------------- |
| |
| private function isMouseOverMenu(event:MouseEvent):Boolean |
| { |
| var target:DisplayObject = DisplayObject(event.target); |
| while (target) |
| { |
| if (target is Menu) |
| return true; |
| target = target.parent; |
| } |
| |
| return false; |
| } |
| |
| private function isMouseOverMenuBarItem(event:MouseEvent):Boolean |
| { |
| if (!sourceMenuBarItem) |
| return false; |
| |
| var target:DisplayObject = DisplayObject(event.target); |
| while (target) |
| { |
| if (target == sourceMenuBarItem) |
| return true; |
| target = target.parent; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * From any menu, walks up the parent menu chain and finds the root menu. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal function getRootMenu():Menu |
| { |
| var target:Menu = this; |
| |
| while (target.parentMenu) |
| target = target.parentMenu; |
| |
| return target; |
| } |
| |
| /** |
| * Given a row, find the row's index in the Menu. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| private function getRowIndex(row:IListItemRenderer):int |
| { |
| for (var i:int = 0; i < listItems.length; i++) |
| { |
| var item:IListItemRenderer = listItems[i][0]; |
| if (item && item.data && !(_dataDescriptor.getType(item.data) == "separator")) |
| if (item == row) |
| return i; |
| } |
| return -1; |
| } |
| |
| /** |
| * For autotesting, get the current set of submenus |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal function get subMenus():Array |
| { |
| var arr:Array = []; |
| for (var i:int = 0; i < listItems.length; i++) |
| { |
| arr.push(listItems[i][0].menu); |
| } |
| return arr; |
| } |
| |
| } |
| |
| } |