//////////////////////////////////////////////////////////////////////////////// // // ADOBE SYSTEMS INCORPORATED // Copyright 2005-2007 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file // in accordance with the terms of the license agreement accompanying it. // //////////////////////////////////////////////////////////////////////////////// package flexlib.baseClasses { import flash.events.Event; import flash.events.MouseEvent; import mx.collections.ICollectionView; import mx.collections.IViewCursor; import mx.collections.CursorBookmark; import mx.controls.listClasses.IListItemRenderer; import mx.controls.menuClasses.IMenuDataDescriptor; import mx.controls.treeClasses.DefaultDataDescriptor; import mx.core.IUIComponent; import mx.core.mx_internal; import mx.events.FlexEvent; import mx.events.ListEvent; import mx.events.MenuEvent; import mx.managers.PopUpManager; import mx.controls.PopUpButton; import mx.controls.Menu; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when a user selects an item from the pop-up menu. * * @eventType mx.events.MenuEvent.ITEM_CLICK */ [Event(name="itemClick", type="mx.events.MenuEvent")] /** * The name of a CSS style declaration used by the dropdown menu. * This property allows you to control the appearance of the dropdown menu. * The default value sets the fontWeight to normal and * the textAlign to left. * * @default "popUpMenu" */ [Style(name="popUpStyleName", type="String", inherit="no")] //-------------------------------------- // Excluded APIs //-------------------------------------- [Exclude(name="toggle", kind="property")] [Exclude(name="selectedDisabledIcon", kind="style")] [Exclude(name="selectedDisabledSkin", kind="style")] [Exclude(name="selectedDownIcon", kind="style")] [Exclude(name="selectedDownSkin", kind="style")] [Exclude(name="selectedOverIcon", kind="style")] [Exclude(name="selectedOverSkin", kind="style")] [Exclude(name="selectedUpIcon", kind="style")] [Exclude(name="selectedUpSkin", kind="style")] //-------------------------------------- // Other metadata //-------------------------------------- //[IconFile("PopUpMenuButton.png")] [RequiresDataBinding(true)] /** * PopUpMenuButtonBase is a copy/paste version of the original PopUpMenuButton class in the Flex framework. * *

The only modifications made to this class were to change some properties and * methods from private to protected so we can override them in a subclass.

* *

The PopUpMenuButton control creates a PopUpButton control with a main * sub-button and a secondary sub-button. * Clicking on the secondary (right) sub-button drops down a menu that * can be popluated through a dataProvider property. * Unlike the Menu and MenuBar controls, the PopUpMenuButton control * supports only a single-level menu. This means that the menu cannot contain * cascading submenus.

* *

The main sub-button of the PopUpMenuButton control can have a * text label, an icon, or both on its face. * When a user selects an item from the drop-down menu or clicks * the main button of the PopUpMenuButton control, the control * dispatches an itemClick event. * When a user clicks the main button of the * control, the control also dispatches a click event. * You can customize the look of a PopUpMenuButton control.

* *

The PopUpMenuButton control has the following sizing * characteristics:

* * * * * * * * * * * * * * * * * *
CharacteristicDescription
Default sizeSufficient to accommodate the label and any icon on * the main button, and the icon on the pop-up button. * The control does not reserve space for the menu.
Minimum size0 pixels.
Maximum size10000 by 10000.
* * @mxml * *

The <mx:PopUpMenuButton> tag inherits all of the tag * attributes of its superclass, and adds the following tag attributes:

* *
 *  <mx:PopUpMenuButton
 *    Properties
 *    dataDescriptor="instance of DefaultDataDescriptor"
 *    dataProvider="undefined"
 *    iconField="icon"
 *    iconFunction="undefined"
 *    labelField="label"
 *    labelFunction="undefined"
 *    showRoot="false|true"
 *     
 *    Event
 *    change=No default
 *  />
 *  
* * * * @see mx.controls.Menu * @see mx.controls.MenuBar * * @tiptext Provides ability to pop up a menu and act as a button * @helpid 3441 */ public class PopUpMenuButtonBase extends PopUpButton { //include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. */ public function PopUpMenuButtonBase() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var dataProviderChanged:Boolean = false; /** * @private */ private var labelSet:Boolean = false; /** * @private */ protected var popUpMenu:Menu = null; /** * @private */ private var selectedIndex:int = -1; /** * @private */ private var itemRenderer:IListItemRenderer = null; /** * @private */ private var explicitIcon:Class = null; /** * @private */ private var menuSelectedStyle:Boolean = false; //-------------------------------------------------------------------------- // // Overridden properties // //-------------------------------------------------------------------------- /** * A reference to the pop-up Menu object. * *

This property is read-only, and setting it has no effect. * Set the dataProvider property, instead. * (The write-only indicator appears in the syntax summary because the * property in the superclass is read-write and this class overrides * the setter with an empty implementation.)

*/ override public function set popUp(value:IUIComponent):void { super.popUp = value; } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // dataDescriptor //-------------------------------------------------------------------------- /** * @private * Storage for the dataDescriptor property. */ private var _dataDescriptor:IMenuDataDescriptor = new DefaultDataDescriptor(); /** * The data descriptor accesses and manipulates data in the data provider. *

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 the property:

* *
<mx:PopUpMenuButton id="menubar" dataDescriptor="{new MyCustomDataDescriptor()}"/>
* *

Alternatively, you can specify the property in MXML as a nested * subtag, as the following example shows:

* *
<mx:PopUpMenuButton>
     *  <mx:dataDescriptor>
     *     <myCustomDataDescriptor>
     *  </mx:dataDescriptor>
     *  ...
* *

The default value is an internal instance of the * DefaultDataDescriptor class.

*/ public function get dataDescriptor():IMenuDataDescriptor { return IMenuDataDescriptor(_dataDescriptor); } /** * @private */ public function set dataDescriptor(value:IMenuDataDescriptor):void { _dataDescriptor = value; } //-------------------------------------------------------------------------- // dataProvider //-------------------------------------------------------------------------- /** * @private * Storage for dataProvider property. */ private var _dataProvider:Object = null; [Bindable("collectionChange")] [Inspectable(category="Data", defaultValue="null")] /** * DataProvider for popUpMenu. * * @default null */ public function get dataProvider():Object { if (popUpMenu) return Menu(popUpMenu).dataProvider; return _dataProvider; } /** * @private */ public function set dataProvider(value:Object):void { _dataProvider = value; dataProviderChanged = true; invalidateProperties(); } //-------------------------------------------------------------------------- // iconField //-------------------------------------------------------------------------- /** * @private * Storage for the iconField property. */ private var _iconField:String = "icon"; [Bindable("iconFieldChanged")] [Inspectable(category="Data", defaultValue="icon")] /** * Name of the field in the dataProvider Array that contains the icon to * show for each menu item. * The iconFunction property, if set, overrides this property. * *

The renderers will look in the data provider object for a property of * the name supplied as the iconField. If the value of the property is a * Class, it will instantiate that class and expect it to be an instance * of an IFlexDisplayObject. If the value of the property is a String, * it will look to see if a Class exists with that name in the application, * and if it can't find one, it will also look for a property on the * document with that name and expect that property to map to a Class.

* * If the data provider is an E4X XML object, you must set this property * explicitly; for example, use @icon to specify the icon attribute. * * @default "icon" */ public function get iconField():String { return _iconField; } /** * @private */ public function set iconField(value:String):void { if (_iconField != value) { _iconField = value; if (popUpMenu) popUpMenu.iconField = _iconField; dispatchEvent(new Event("iconFieldChanged")); } } //-------------------------------------------------------------------------- // iconFunction //-------------------------------------------------------------------------- /** * @private * Storage for the iconFunction property. */ private var _iconFunction:Function; [Inspectable(category="Data")] /** * A function that determines the icon to display for each menu item. * If you omit this property, Flex uses the contents of the field or attribute * determined by the iconField property. * If you specify this property, Flex ignores any iconField * property value. * * By default the menu does not try to display icons with the text * in the rows. However, by specifying an icon function, you can specify * a Class for a graphic that will be created and displayed as an icon * in the row. * *

The iconFunction takes a single argument which is the item * in the data provider and returns a Class.

* *
* iconFunction(item:Object):Class *
* * @default null */ public function get iconFunction():Function { return _iconFunction; } /** * @private */ public function set iconFunction(value:Function):void { if (_iconFunction != value) { _iconFunction = value; if (popUpMenu) popUpMenu.iconFunction = _iconFunction; } } //-------------------------------------------------------------------------- // label //-------------------------------------------------------------------------- /** * @private * Storage for the label property. */ private var _label:String = ""; [Inspectable(category="General", defaultValue="")] /** * @private */ override public function set label(value:String):void { // labelSet is different from labelChanged as it is never unset. labelSet = true; _label = value; super.label = _label; } //-------------------------------------------------------------------------- // labelField //-------------------------------------------------------------------------- /** * @private * Storage for the labelField property. */ private var _labelField:String = "label"; [Bindable("labelFieldChanged")] [Inspectable(category="Data", defaultValue="label")] /** * Name of the field in the dataProvider Array that contains the text to * show for each menu item. * The labelFunction property, if set, overrides this property. * 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, use @label to specify the label attribute. * * @default "label" */ public function get labelField():String { return _labelField; } /** * @private */ public function set labelField(value:String):void { if (_labelField != value) { _labelField = value; if (popUpMenu) popUpMenu.labelField = _labelField; dispatchEvent(new Event("labelFieldChanged")); } } //-------------------------------------------------------------------------- // labelFunction //-------------------------------------------------------------------------- /** * @private * Storage for the labelFunction property. */ private var _labelFunction:Function; [Inspectable(category="Data")] /** * A function that determines the text to display for each menu item. * If you omit this property, Flex uses the contents of the field or attribute * determined by the labelField property. * If you specify this property, Flex ignores any labelField * property value. * *

If you specify this property, the label function must find the * appropriate field or fields and return a displayable string. * The labelFunction property is good for handling formatting * and localization.

* *

The label function must take a single argument which is the item * in the dataProvider and return a String.

* *
* labelFunction(item:Object):String *
* * @default null */ public function get labelFunction():Function { return _labelFunction; } /** * @private */ public function set labelFunction(value:Function):void { if (_labelFunction != value) { _labelFunction = value; if (popUpMenu) popUpMenu.labelFunction = _labelFunction; } } //-------------------------------------------------------------------------- // showRoot //-------------------------------------------------------------------------- /** * @private * Storage for the showRoot property. */ mx_internal var _showRoot:Boolean = true; /** * @private */ private var _showRootChanged:Boolean = false; [Inspectable(category="Data", enumeration="true,false", defaultValue="true")] /** * Specifies whether to display the top-level node or nodes of the data provider. * * If this is set to false, the control displays * only descendants of the first top-level node. * Any other top-level nodes are ignored. * You normally set this property to false for * E4X format XML data providers, where the top-level node is the document * object. * * @default true */ public function get showRoot():Boolean { return _showRoot; } /** * @private */ public function set showRoot(value:Boolean):void { if (_showRoot != value) { _showRoot = value; _showRootChanged = true; invalidateProperties(); } } //-------------------------------------------------------------------------- // // Overridden methods: UIComponent // //-------------------------------------------------------------------------- /** * @private */ override protected function commitProperties():void { if (dataProviderChanged && !popUpMenu) { // In general we shouldn't create the popUp until // they are actually popped up. However, in this case // the initial label, icon and action on the main button's // click are to be borrowed from the popped menu. // Moreover since PopUpMenuButton doesn't expose selectedIndex // selectedItem etc., one should be able to access them // prior to popping up the menu. getPopUp(); } if (_showRootChanged) { _showRootChanged = false; if (popUpMenu != null) popUpMenu.showRoot = _showRoot; invalidateDisplayList(); } if (popUpMenu && dataProviderChanged) { popUpMenu.dataProvider = _dataProvider; popUpMenu.validateNow(); if (dataProvider.length) { selectedIndex = 0; var cursor:IViewCursor = dataProvider.createCursor() cursor.seek(CursorBookmark.FIRST, 0); var item:* = cursor.current; // Set button label. if (labelSet) super.label = _label; else super.label = popUpMenu.itemToLabel(item); // Set button icon, setSafeIcon(popUpMenu.itemToIcon(item)); } else { selectedIndex = -1; if (labelSet) super.label = _label; else super.label = ""; clearStyle("icon"); } dataProviderChanged = false; } super.commitProperties(); } /** * @private */ override public function styleChanged(styleProp:String):void { super.styleChanged(styleProp); //style is actually set here already. if (styleProp == "icon" || styleProp == null || styleProp == "styleName" ) { if (menuSelectedStyle) { if (explicitIcon) { menuSelectedStyle = false; setStyle("icon", explicitIcon); } } else { explicitIcon = getStyle("icon"); } } } //-------------------------------------------------------------------------- // // Overridden methods: PopUpButton // //-------------------------------------------------------------------------- /** * @private */ override mx_internal function getPopUp():IUIComponent { super.getPopUp(); if (!popUpMenu || !super.popUp) { popUpMenu = new Menu(); popUpMenu.iconField = _iconField; popUpMenu.iconFunction = _iconFunction; popUpMenu.labelField = _labelField; popUpMenu.labelFunction = _labelFunction; popUpMenu.showRoot = _showRoot; popUpMenu.dataDescriptor = _dataDescriptor; popUpMenu.dataProvider = _dataProvider; popUpMenu.addEventListener(MenuEvent.ITEM_CLICK, menuChangeHandler); popUpMenu.addEventListener(FlexEvent.VALUE_COMMIT, menuValueCommitHandler); super.popUp = popUpMenu; // Add PopUp to PopUpManager here so that // commitProperties of Menu gets called even // before the PopUp is opened. This is // necessary to get the initial label and dp. PopUpManager.addPopUp(super.popUp, this, false); super.popUp.owner = this; } return popUpMenu; } //-------------------------------------------------------------------------- // // private helper methods // //-------------------------------------------------------------------------- /** * @private */ private function setSafeIcon(iconClass:Class):void { menuSelectedStyle = true; setStyle("icon", iconClass); menuSelectedStyle = false; } //-------------------------------------------------------------------------- // // Overridden event handlers: Button // //-------------------------------------------------------------------------- /** * @private */ override protected function clickHandler(event:MouseEvent):void { super.clickHandler(event); if (!overArrowButton(event)) menuClickHandler(event); } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private */ private function menuClickHandler(event:MouseEvent):void { if (selectedIndex >= 0) { var menuEvent:MenuEvent = new MenuEvent(MenuEvent.ITEM_CLICK); menuEvent.menu = popUpMenu; menuEvent.menu.selectedIndex = selectedIndex; var cursor:IViewCursor = dataProvider.createCursor(); cursor.seek(CursorBookmark.FIRST, selectedIndex); menuEvent.item = cursor.current menuEvent.itemRenderer = itemRenderer; menuEvent.index = selectedIndex; menuEvent.label = popUpMenu.itemToLabel(cursor.current); dispatchEvent(menuEvent); // Reset selection after the change event is dispatched // just like in a menu. popUpMenu.selectedIndex = -1; } } /** * @private */ protected function menuValueCommitHandler(event:FlexEvent):void { // Change label/icon if selectedIndex is changed programatically. if (popUpMenu.selectedIndex >= 0) { var cursor:IViewCursor = dataProvider.createCursor(); cursor.seek(CursorBookmark.FIRST, selectedIndex); selectedIndex = popUpMenu.selectedIndex; if (labelSet) super.label = _label; else super.label = popUpMenu.itemToLabel(cursor.current); setSafeIcon(popUpMenu.itemToIcon(cursor.current)); } } /** * @private */ protected function menuChangeHandler(event:MenuEvent):void { if (event.index >= 0) { var menuEvent:MenuEvent = new MenuEvent(MenuEvent.ITEM_CLICK); menuEvent.label = popUpMenu.itemToLabel(event.item); if (labelSet) super.label = _label; else super.label = popUpMenu.itemToLabel(event.item); setSafeIcon(popUpMenu.itemToIcon(event.item)); menuEvent.menu = popUpMenu; menuEvent.menu.selectedIndex = menuEvent.index = selectedIndex = event.index; menuEvent.item = event.item; itemRenderer = menuEvent.itemRenderer = event.itemRenderer; dispatchEvent(menuEvent); } } } }