2020-10-20 00:58:15 +02:00

5 lines
40 KiB
ActionScript
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (c) 2009 Yahoo! Inc. All rights reserved.
The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license
*/
package com.yahoo.astra.fl.controls{
import com.yahoo.astra.fl.containers.IRendererContainer;
import com.yahoo.astra.fl.controls.menuClasses.MenuCellRenderer;
import com.yahoo.astra.fl.data.XMLDataProvider;
import com.yahoo.astra.fl.events.MenuEvent;
import com.yahoo.astra.fl.managers.PopUpManager;
import com.yahoo.astra.fl.utils.UIComponentUtil;
import com.yahoo.astra.utils.InstanceFactory;
import fl.containers.BaseScrollPane;
import fl.controls.List;
import fl.core.InvalidationType;
import fl.data.DataProvider;
import fl.events.ListEvent;
import fl.transitions.*;
import fl.transitions.easing.*;
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.filters.BlurFilter;
import flash.geom.Point;
import flash.text.TextFormat;
import flash.ui.Keyboard;
import flash.utils.*;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the user rolls the pointer over a row.
*
* @eventType com.yahoo.astra.fl.events.MenuEvent.ITEM_ROLL_OVER
*
* @see #event:itemRollOut
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="itemRollOver", type="com.yahoo.astra.fl.events.MenuEvent")]
/**
* Dispatched when the user rolls the pointer off a row.
*
* @eventType com.yahoo.astra.fl.events.MenuEvent.ITEM_ROLL_OUT
*
* @see #event:itemRollOver
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="itemRollOut", type="com.yahoo.astra.fl.events.MenuEvent")]
/**
* Dispatched when the user clicks an item in the menu.
*
* @eventType com.yahoo.astra.fl.events.MenuEvent.ITEM_CLICK
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="itemClick", type="com.yahoo.astra.fl.events.MenuEvent")]
/**
* Dispatched when a different item is selected in the Menu.
*
* @eventType flash.events.Event.CHANGE
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="change", type="flash.events.Event")]
/**
* Dispatched when a menu is shown
*
* @eventType com.yahoo.astra.fl.events.MenuEvent.MENU_SHOW
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="menuShow", type="com.yahoo.astra.fl.events.MenuEvent")]
/**
* Dispatched when a menu is hidden.
*
* @eventType com.yahoo.astra.fl.events.MenuEvent.MENU_HIDE
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Event(name="menuHide", type="com.yahoo.astra.fl.events.MenuEvent")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* The number of pixels between the left edge of the menu and the left edge of
* the stage when the stage is too narrow to fit the menu. If the value were set
* to 0, the left edge of the menu would be flush with the left edge of the stage.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="leftMargin", type="Number")]
/**
* The number of pixels between the bottom of the menuBar and the top of the
* menu when the stage is to short to fit the menu. If the value were set to 0,
* the top of the menu would be flush with bottom edge of the menuBar.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="topMargin", type="Number")]
/**
* The number of pixels for a bottom gutter to a menu when the stage to short to
* fit the menu. If the value were set to 0, the bottom of the menu would be
* flush with the bottom edge of the stage.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="bottomMargin", type="Number")]
/**
* The number of pixels between the right edge of the menu and the right edge of
* the stage when the stage is too narrow to fit the menu. If the value were set
* to 0, the right edge of the menu would be flush with the left edge of the
* stage.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="rightMargin", type="Number")]
/**
* The number of pixels that each submenu will appear to right of its parent
* menu. A negative value can be used to have the menus overlap.
*
* @default -3
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="xOffset", type="Number")]
/**
* The number of pixels that each submenu will appear below the top of its parent
* menu. A negative value can be used to have the menu appear above its parent
* menu.
*
* @default 3
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
[Style(name="yOffset", type="Number")]
//--------------------------------------
// Other metadata
//--------------------------------------
[IconFile("assets/Menu.png")]
//--------------------------------------
// Class description
//--------------------------------------
/**
* The Menu class creates an array of menus from an XML data provider.
* Add the Menu to your library (either by dragging it onto the stage and then deleting it, or
* dragging it directly to your library). Then call the static <code>Menu.createMenu(parent)</code>
* method to create a menu, and call <code>yourMenuInstance.show()</code> to display it.
*
* @author Alaric Cole
*/
public class Menu extends List implements IRendererContainer {
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* <p>Applications do not normally call the Menu constructor directly.
* Call the <code>Menu.createMenu()</code> method.</p>
* @see #createMenu()
*/
public function Menu() {
super();
//Make it initially invisible
visible=false;
tabEnabled=false;
verticalScrollPolicy = horizontalScrollPolicy = "off";
addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);
addEventListener(MenuEvent.ITEM_ROLL_OVER,itemRollOver);
addEventListener(MenuEvent.ITEM_ROLL_OUT,itemRollOut);
}
//--------------------------------------------------------------------------
//
// Class mixins
//
//--------------------------------------------------------------------------
/**
* Placeholder for mixin by MenuAccImpl.
*/
public static var createAccessibilityImplementation:Function;
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Indicates whether or not a menu item with a submenu is clickable.
*/
public var parentMenuClickable:Boolean;
/**
* Coordinates indicating the ideal position of the menu. These are used to determine where
* to position the menu when the stage is resized outward.
*/
public var specifiedPoint:Point;
/**
* Object that contains arrays of groups. Used when menu items toggle with each other.
*/
public var groups:Object;
/**
* Indicates whether the menu will close when the mouse leaves the stage.
*/
public var closeOnMouseLeave:Boolean;
/**
* @private
*/
private var realParent:DisplayObjectContainer;
// 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:MenuCellRenderer;
// reference to the ID of the last opened submenu within a menu level
private var anchor:String;
// reference to the rowIndex of a menu's anchor in the parent menu
private var anchorIndex:int=-1;
private var subMenu:Menu;
/**
* @private
* When this timer fires, we'll open a submenu
*/
private var openSubMenuTimer:int=0;
/**
* @private
* When this timer fires, we'll hide this menu
*/
private var closeTimer:int=0;
/**
* @private
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
private static var defaultStyles:Object = {
skin:"MenuSkin",
cellRenderer:MenuCellRenderer,
focusRectSkin:null,
focusRectPadding:null,
contentPadding:null,
disabledAlpha:null,
leftMargin:0,
rightMargin:0,
topMargin:0,
bottomMargin:0,
xOffset:0,
yOffset:0
};
/**
* @private
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
private static var defaultRendererStyles:Object = {
};
/**
* @private
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
public static function getStyleDefinition():Object {
return mergeStyles(defaultStyles, BaseScrollPane.getStyleDefinition());
}
//--------------------------------------------------------------------------
// parentMenu
//--------------------------------------------------------------------------
/**
* @private
*/
protected 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.
*/
public function get parentMenu():Menu {
return _parentMenu;
}
/**
* @private
*/
public function set parentMenu(value:Menu):void {
_parentMenu=value;
}
/**
* Get the current set of submenus for a Menu
* @return all submenus of this menu, if any
*/
public function get subMenus():Array {
var arr:Array=[];
for (var i:int=0; i < dataProvider.length; i++) {
var d:Object=dataProvider.getItemAt(i);
if (d) {
var c:MenuCellRenderer=MenuCellRenderer(itemToCellRenderer(d));
if (c) {
var m:Menu=c.subMenu;
//
if (m) {
arr.push(m);
}
}
}
}
return arr;
}
/**
* @private
*/
protected var tween:Tween;
//--------------------------------------------------------------------------
//
// Overridden Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// labelField
//----------------------------------
[Inspectable(category="Data", defaultValue="label")]
/**
* Name of the field in the items in the <code>dataProvider</code>
* Array to display as the label in the Menu.
* By default, the control uses a property named <code>label</code>
* on each Array object and displays it.
* <p>However, if the <code>dataProvider</code> items do not contain
* a <code>label</code> property, you can set the <code>labelField</code>
* property to use a different property.</p>
*
*/
override public function get labelField():String
{
return _labelField;
}
/**
* @private
*/
override public function set labelField(value:String):void
{
//support @ modifiers for XML converted to Object via XMLDataProvider
if(value.indexOf("@") == 0) value = value.slice(1);
super.labelField = value;
}
//--------------------------------------------------------------------------
// dataProvider
//--------------------------------------------------------------------------
[Collection(collectionClass="com.yahoo.astra.fl.data.XMLDataProvider", collectionItem="fl.data.SimpleCollectionItem", identifier="item")]
/**
* Gets or sets the data model of the list of items to be viewed.
* A data provider can be shared by multiple list-based components.
* Changes to the data provider are immediately available to all components that use it as a data source.
*
* To use XML data, you should set the dataProvider as type <code>XMLDataProvider</code>:
* <code>menu.dataProvider = new XMLDataProvider(someXML);</code>
*
* <p>You can also use an array of objects in the format:
* <code>
*
* var dp:Array = [
* {
* label:"top level item A",
* data:
* [
* { label:"submenu A1" },
* { label:"submenu A2" }
* ]
* },
* {
* label:"top level item B"
* },
* {
* label:"top level item C"
* }
* ];
*
* menu.dataProvider = new DataProvider(dp);
* </code>
* </p>
* However, using the <code>createMenu()</code> method and passing in XML or an array will do this for you.
* @see com.yahoo.astra.fl.data.XMLDataProvider
* @see #labelField
* @default null
*/
override public function get dataProvider():DataProvider
{
return _dataProvider;
}
override public function set dataProvider(value:DataProvider):void {
if((value is DataProvider) || value is XMLDataProvider)
{
_dataProvider = value;
clearSelection();
invalidateList();
}
else throw new TypeError("Error: Type Coercion failed: cannot convert " + value + " to fl.data.DataProvider");
}
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* Creates and returns an instance of the Menu class. The Menu control's
* content is determined by the method's <code>xmlDataProvider</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 xmlDataProvider The data provider for the Menu control.
* @see #dataProvider
*
* @return An instance of the Menu class.
*
* @see #popUpMenu()
* @see com.yahoo.astra.fl.data.XMLDataProvider
*/
public static function createMenu(inParent:DisplayObjectContainer,xmlDataProvider:Object = null):Menu {
var menu:Menu=new Menu;
menu.realParent=inParent;
//add to the stage as popup
popUpMenu(menu,inParent,xmlDataProvider);
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 stage.
*
* @param xmlDataProvider 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.
*/
public static function popUpMenu(menu:Menu,parent:DisplayObjectContainer,dp:Object = null):void {
if (!dp) dp = new XML();
if(dp is XML) menu.dataProvider = new XMLDataProvider(dp);
else menu.dataProvider = new DataProvider(dp);
menu.invalidateList();
}
/**
* @private
* Removes the root menu from the display list. This is called only for
* menus created using "createMenu".
*
*/
private static function menuHideHandler(event:MenuEvent):void {
var menu:Menu=Menu(event.target);
if (! event.isDefaultPrevented() && event.menu == menu) {
PopUpManager.removePopUp(menu);
menu.removeEventListener(MenuEvent.MENU_HIDE,menuHideHandler);
}
}
/**
* @private
* Removes the main menu and submenus from the stage.
*
*/
protected function hideAllMenus():void {
getRootMenu().hide();
getRootMenu().deleteDependentSubMenus();
}
/**
* @private
* Removes all menus and submenus from the stage.
*
*/
protected function deleteDependentSubMenus():void {
if (subMenus.length > 0) {
var n:int=subMenus.length;
for (var i:int=0; i < n; i++) {
var m:Menu=subMenus[i] as Menu;
m.deleteDependentSubMenus();
//m.hide();
//more responsive to make invisble rather than hide()
m.visible=false;
}
}
}
/**
* Shows the Menu control. If the Menu control is not visible, this method
* places the Menu in the upper-left corner of the stage, resizes the Menu control as needed,
* and makes it 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
* stage, which is not necessarily the direct parent of the Menu control.
*
* For example, if the Menu control is in a container, the x and y coordinates are
* relative to the stage, not the 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).
*/
public function show(xShow:Object=null,yShow:Object=null):void {
//if this is a top level menu, set the point based off of xShow and yShow params)
if(!parentMenu)
{
specifiedPoint = new Point(Number(xShow), Number(yShow));
}
//if empty, forget it
if (dataProvider && dataProvider.length == 0) {
return;
}
// If parent is closed, then don't show a submenu
if (parentMenu && ! parentMenu.visible && parentMenu.selectedIndex < 0) {
return;
}
// If already visible, why bother?
//if (visible) {
//return;
//}
//don't highlight anything
selectedIndex= caretIndex = 0;
selectedIndex= caretIndex = -1;
//Pop up the menu
if(!this.parent && realParent)
{
PopUpManager.addPopUp(this,realParent);
}
//if it's been added by dragging onto stage
else
{
if(parent)
{
realParent = parent;
parent.removeChild(this);
PopUpManager.addPopUp(this,realParent);
}
}
//resize the menu to its rows
var maxW:int = 0;
var totalHeights:int = 0;
var n:int=dataProvider.length;
//Must set row count before drawing, otherwise there will be a problem
//with itemToCellRenderer not resolving the last item
//this is due to the way recycling of item renderers is done,
rowCount = n;
drawNow();
groups = {};
//loop through all renderers
for (var i:int=0; i < n; i++) {
var item:Object = dataProvider.getItemAt(i);
var c:MenuCellRenderer;
var w:int;
var h:int;
c=itemToCellRenderer(item) as MenuCellRenderer;
//build arrays containing member MenuCellRenderers for each group that is declared
for(var j:String in item)
{
if(j == "group")
{
var key:String = item[j].toString();
var isArray:Boolean = (groups[key] != null && groups[key] is Array)
if(!isArray) groups[key] = [];
groups[key].push(c);
}
}
if(!c)
{
trace(itemToLabel(item) + " couldn't be coerced into a MenuCellRenderer")
}
w = (c.width?c.width:10);
h= c.height;
totalHeights += h;
if( w > maxW)
{
maxW = w;
}
}
//set the width of the menu to the largest width of the renderers
width = (maxW < 10 ? 10: maxW);
//height = totalHeights;
//HACK: separator isn't resizing due to the row's width not being
//set immediately, so we force all row widths to the Menu's width
for (var t:int=0; t < n; t++) {
var itm:Object = dataProvider.getItemAt(t);
var r:MenuCellRenderer=itemToCellRenderer(itm) as MenuCellRenderer;
if(!r)
break;
r.width = maxW;
r.drawNow();
}
addEventListener(MenuEvent.MENU_HIDE,menuHideHandler,false,-50);
// Fire an event
var menuEvent:MenuEvent=new MenuEvent(MenuEvent.MENU_SHOW);
menuEvent.menu=this;
//menuEvent.menuBar = sourceMenuBar;
getRootMenu().dispatchEvent(menuEvent)
//var showFunc:Function = function(){getRootMenu().dispatchEvent(menuEvent)};
//callLater(showFunc);
// Position it
if (xShow !== null && ! isNaN(Number(xShow))) {
x=Number(xShow);
}
if (yShow !== null && ! isNaN(Number(yShow))) {
y=Number(yShow);
}
// Make it visible
visible=true;
//disabled items aren't rendering properly with addtostage feature
drawNow();
var pt:Point = new Point(x, y);
pt = realParent.localToGlobal(pt);
if(pt.x + width > this.stage.stageWidth - Number(getStyleValue("rightMargin"))) x = realParent.globalToLocal(new Point(Math.max(this.stage.stageWidth - width - Number(getStyleValue("rightMargin")), Number(getStyleValue("leftMargin"))),0)).x;
if(pt.y + height > this.stage.stageHeight) y = realParent.globalToLocal(new Point(0, Math.max(this.stage.stageHeight - height - Number(getStyleValue("bottomMargin")), realParent.y + realParent.height + Number(getStyleValue("topMargin"))))).y;
// If the user clicks outside the menu, then hide the menu
stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownOutsideHandler,false,0,true);
stage.addEventListener(Event.RESIZE, stageResizeHandler,false,0,true);
if(closeOnMouseLeave) stage.addEventListener(Event.MOUSE_LEAVE, mouseOutsideApplicationHandler,false,0,true);
// Activate the menu
//this.setFocus();
setFocus();
//set a timeout to fix an issue with accessibility events
var func:Function = function(){ dispatchEvent(new Event(Event.CHANGE))};
callLater(func);
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
* Set up keyboard navigation specific to the Menu
*/
override protected function keyDownHandler(event:KeyboardEvent):void {
//Override the List key down function entirely because it stops propagation of the keyboard event. The event needs to bubble up to the parent.
var row:MenuCellRenderer=selectedIndex < 0 ? null : itemToCellRenderer(dataProvider.getItemAt(selectedIndex)) as MenuCellRenderer;
var rowData:Object=row?row.data.data:null;
var mnu:Menu=row?MenuCellRenderer(row).menu:null;
if (!selectable) { return; }
switch (event.keyCode) {
case Keyboard.UP:
case Keyboard.DOWN:
case Keyboard.END:
case Keyboard.HOME:
case Keyboard.PAGE_UP:
case Keyboard.PAGE_DOWN:
moveSelectionVertically(event.keyCode, event.shiftKey && _allowMultipleSelection, event.ctrlKey && _allowMultipleSelection);
break;
case Keyboard.LEFT:
if (parentMenu) {
//parentMenu.selectedIndex =
parentMenu.selectedIndex=anchorIndex;//?anchorIndex:-1;
parentMenu.setFocus();
hide();
event.stopPropagation();
}
break;
case Keyboard.RIGHT:
if (row) {
if (rowData) {
//if the row has its own data (taken from XML and thrown into an object)
var m2:Menu=openSubMenu(row);
m2.selectedIndex=0;
event.stopPropagation();
}
}
break;
case Keyboard.SPACE:
if (row) {
if (rowData)
{
//if the row has its own data (taken from XML and thrown into an object)
var m:Menu=openSubMenu(row);
m.selectedIndex = 0;
}
else
{
row.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
}
break;
case Keyboard.ENTER:
if(row)
{
row.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
break;
case Keyboard.ESCAPE :
hideAllMenus();
break;
/* case Keyboard.TAB :
hideAllMenus();
if(realParent is MenuBar)
{
realParent.dispatchEvent(event);
}
event.stopPropagation();
break; */
default:
var nextIndex:int = getNextIndexAtLetter(String.fromCharCode(event.keyCode), selectedIndex);
if (nextIndex > -1) {
selectedIndex = nextIndex;
scrollToSelected();
}
break;
}
}
/**
* @private (protected)
*
* disables default tab key behavior
*
*/
protected function keyFocusChangeHandler(event:FocusEvent):void
{
if(event.keyCode == Keyboard.TAB)
{
hideAllMenus();
/* if(realParent is MenuBar)
{
MenuBar(realParent)._buttonRow.dispatchEvent(event);
trace("cur targ" + event.currentTarget);
trace("aff obj" + event.relatedObject);
}
event.preventDefault();
event.stopPropagation(); */
}
}
/**
* @private (protected)
* Moves the selection in a vertical direction in response
* to the user selecting items using the up-arrow or down-arrow
*
* @param code The key that was pressed (e.g. Keyboard.DOWN)
*
* @param shiftKey <code>true</code> if the shift key was held down
*
* @param ctrlKey <code>true</code> if the ctrl key was held down
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
override protected function moveSelectionVertically(code:uint, shiftKey:Boolean, ctrlKey:Boolean):void {
var pageSize:int = Math.max(Math.floor(calculateAvailableHeight() / rowHeight), 1);
var newCaretIndex:int = -1;
var dir:int = 0;
switch (code) {
case Keyboard.UP :
if (caretIndex > 0) {
newCaretIndex = caretIndex - 1;
}
else
{
newCaretIndex = length - 1;
}
break;
case Keyboard.DOWN :
if (caretIndex < length - 1) {
newCaretIndex = caretIndex + 1;
}
else
{
newCaretIndex = 0;
}
break;
case Keyboard.PAGE_UP :
if (caretIndex > 0) {
newCaretIndex = Math.max(caretIndex - pageSize, 0);
}
break;
case Keyboard.PAGE_DOWN :
if (caretIndex < length - 1) {
newCaretIndex = Math.min(caretIndex + pageSize, length - 1);
}
break;
case Keyboard.HOME :
if (caretIndex > 0) {
newCaretIndex = 0;
}
break;
case Keyboard.END :
if (caretIndex < length - 1) {
newCaretIndex = length - 1;
}
break;
}
if (newCaretIndex >= 0) {
//set selected to false for the previously selected row
if(caretIndex > -1 && !isNaN(caretIndex))
{
var oldRow:MenuCellRenderer = itemToCellRenderer(dataProvider.getItemAt(caretIndex)) as MenuCellRenderer;
oldRow.selected = false;
}
var item:Object = dataProvider.getItemAt(newCaretIndex);
var row:MenuCellRenderer=itemToCellRenderer(item) as MenuCellRenderer;
doKeySelection(newCaretIndex, shiftKey, ctrlKey);
scrollToSelected();
if(!row.enabled) moveSelectionVertically(code, shiftKey, ctrlKey);
}
}
/**
* @private
* Recreates ListEvents as MenuEvents
*/
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) ) {
// don't dispatch specific List Events as Menu dispatches its own
event.stopImmediatePropagation();
//add index parameter to menu event
var meV:MenuEvent = new MenuEvent(event.type, event.bubbles,event.cancelable, null, this, ListEvent(event).item, null, itemToLabel(ListEvent(event).item), ListEvent(event).index);
return super.dispatchEvent(meV);
}
// In case we encounter a ListEvent.ITEM_CLICK from
// a superclass that we did not account for,
// convert the ListEvent and pass it up
if (!(event is MenuEvent) && event is ListEvent &&
(event.type == ListEvent.ITEM_CLICK)) {
event.stopImmediatePropagation();
//add index parameter to menu event
var me:MenuEvent = new MenuEvent(event.type, event.bubbles, event.cancelable, null, this, ListEvent(event).item, null, itemToLabel(ListEvent(event).item), ListEvent(event).index);
return super.dispatchEvent(me);
}
// we'll let everything else go through
return super.dispatchEvent(event);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Hides the Menu control and any of its submenus if visible
*/
public function hide(useTween:Boolean = true):void {
if(stage!=null) stage.removeEventListener(Event.RESIZE, stageResizeHandler);
if(closeOnMouseLeave) stage.removeEventListener(Event.MOUSE_LEAVE, mouseOutsideApplicationHandler);
// Stop any tween that's running
if (tween)
tween.stop();
//if there are submenus, delete them
if(subMenus.length > 0) deleteDependentSubMenus();
if(useTween)
{
if (visible) {
var duration:Number = getStyle("openDuration") as Number;
if (!duration) {
duration = .125;
}
//Hack to get certain transitions to work on device fonts; thanks, Scott!
if (filters.length < 1) {
//if no one has put any filters, we do so
//this causes it to be cached as a bitmap
//and allows transparency on device fonts
var filter:BlurFilter=new BlurFilter(0,0,1);
var fltrs:Array = [filter];
filters = fltrs;
}
tween=new Tween(this,'alpha',Regular.easeOut,1,0, duration,true);
tween.addEventListener(TweenEvent.MOTION_FINISH,onTweenEnd);
}
}
else
{
alpha = 1;
visible = false;
// Now that the menu is no longer visible, it doesn't need
// to listen to mouseDown events anymore.
stage.removeEventListener(MouseEvent.MOUSE_DOWN,mouseDownOutsideHandler);
// Fire an event
var menEvent:MenuEvent=new MenuEvent(MenuEvent.MENU_HIDE);
menEvent.menu=this;
dispatchEvent(menEvent);
}
}
/**
* @private
* Creates a submenu for this menu
*/
protected function openSubMenu(row:MenuCellRenderer):Menu {
var rootMenu:Menu=getRootMenu();
var mnu:Menu;
// check to see if the menu exists, if not create it
if (! MenuCellRenderer(row).menu) {
mnu=Menu.createMenu(rootMenu,row.data.data);
this.copyRendererStylesToChild(mnu, this.rendererStyles);
mnu.parentMenuClickable = rootMenu.parentMenuClickable;
mnu.setStyle("xOffset", Number(rootMenu.getStyleValue("xOffset")));
mnu.setStyle("yOffset", Number(rootMenu.getStyleValue("yOffset")));
mnu.visible=false;
mnu.parentMenu=this;
//mnu.showRoot = showRoot;
mnu.labelField=rootMenu.labelField;
mnu.labelFunction=rootMenu.labelFunction;
mnu.iconField=rootMenu.iconField;
mnu.iconFunction=rootMenu.iconFunction;
mnu.rowHeight=rootMenu.rowHeight;
mnu.setStyle("leftMargin", Number(getStyleValue("leftMargin")));
mnu.setStyle("topMargin", Number(getStyleValue("topMargin")));
mnu.setStyle("rightMargin", Number(getStyleValue("rightMargin")));
mnu.setStyle("bottomMargin", Number(getStyleValue("bottomMargin")));
mnu.anchorRow=row;
mnu.anchorIndex=row.listData.index;
selectedIndex = row.listData.index;
row.subMenu=mnu;
//setting scales causes a redraw loop
//mnu.scaleY = rootMenu.scaleY;
//mnu.scaleX = rootMenu.scaleX;
mnu.realParent=this.realParent;
// mnu.sourceMenuBar = sourceMenuBar;
} else {
mnu=MenuCellRenderer(row).menu;
}
//not sure if this is necessary. If you base the coordinates off of the parent menu, it will not matter.
var r:DisplayObject=DisplayObject(row);
var pt:Point=new Point(0,0);
pt = r.localToGlobal(pt);
// if this is loaded from another swf, global coordinates could be wrong
if (r.root) {
pt=r.root.globalToLocal(pt);
}
mnu.specifiedPoint = new Point(specifiedPoint.x + width + Number(getStyleValue("xOffset")), specifiedPoint.y + row.y + Number(getStyleValue("yOffset")));
mnu.show(mnu.specifiedPoint.x, mnu.specifiedPoint.y);
//hack for resetting caretIndex (used by moveSelectionVertically)
//caretIndex isn't being updated to synchronize with selectedIndex, so
//you'd have to press the down key twice to get the selection to move
mnu.caretIndex = 0;
return mnu;
}
/**
* @private
*/
private function closeSubMenu(menu:Menu):void {
menu.hide();
menu.closeTimer=0;
}
/**
* @private
* Sets everything back to normal when the hidden Menu's fade ends
*/
protected function onTweenEnd(event:TweenEvent):void {
//set the menu back to normal, and off the display list
var m:Menu = event.currentTarget.obj as Menu;
m.visible=false;
// Now that the menu is no longer visible, it doesn't need
// to listen to mouseDown events anymore.
stage.removeEventListener(MouseEvent.MOUSE_DOWN,mouseDownOutsideHandler);
m.alpha=1;
// Fire an event
//could be fired earlier, but it messes with the tween
var menEvent:MenuEvent=new MenuEvent(MenuEvent.MENU_HIDE);
menEvent.menu=m;
//menuEvent.menuBar = sourceMenuBar;
dispatchEvent(menEvent);
}
/**
* @private
* From any menu, walks up the parent menu chain and finds the root menu.
*/
protected function getRootMenu():Menu {
var target:Menu=this;
while (target.parentMenu) {
target = target.parentMenu;
}
return target;
}
/**
* @private
* Checks to see if a mouse event was fired from a Menu
*/
private function isMouseOverMenu(event:MouseEvent):Boolean {
var target:DisplayObject = DisplayObject(event.target);
while (target) {
//include the parent as target so that clicking on the menubar does not call hide all menus
if (target is Menu || target == realParent) {
return true;
}
target = target.parent;
}
return false;
}
// -------------------------------------------------------------------------
// Event Handlers
// -------------------------------------------------------------------------
/**
* @private (protected)
* Does Menu-specific item clicks
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
override protected function handleCellRendererClick(event:MouseEvent):void {
var renderer:MenuCellRenderer = event.currentTarget as MenuCellRenderer;
var itemIndex:uint = renderer.listData.index;
if(parentMenuClickable || !renderer.data.data)
{
if (!getRootMenu().dispatchEvent(new MenuEvent(MenuEvent.ITEM_CLICK,false,true,null,
renderer.listData.owner as Menu,renderer.data, renderer,itemToLabel(renderer.data) ,itemIndex)) || !_selectable) {
return;
}
var selectIndex:int = selectedIndices.indexOf(itemIndex);
var i:int;
renderer.selected = true;
_selectedIndices = [itemIndex];
lastCaretIndex = caretIndex = itemIndex;
var item:Object = renderer.data;
var hasSelectableIcon:Boolean = item.hasOwnProperty("selectedIcon") || (item.hasOwnProperty("type") && (item.type.toLowerCase() == "check" || item.type.toLowerCase() == "radio"))
if(hasSelectableIcon)
{
if(item.hasOwnProperty("group"))
{
var groupArr:Array = groups[item.group] as Array;
var groupLen:int = groupArr.length;
for(var j:int = 0; j < groupLen;j++)
{
var mcr:MenuCellRenderer = groupArr[j] as MenuCellRenderer;
if(mcr != renderer)
mcr.data.selected = "false";
}
}
renderer.data.selected = !renderer.data.selected;
invalidate();
}
hideAllMenus();
//dispatchEvent(new Event(Event.CHANGE));
invalidate(InvalidationType.DATA);
}
}
/**
* @private
*/
private function mouseDownOutsideHandler(event:MouseEvent):void {
if (!isMouseOverMenu(event) )
hide();
}
/**
* @private
* Extend the behavior from SelectableList to pop up submenus
*/
protected function itemRollOver(event:MenuEvent):void {
deleteDependentSubMenus();
var row:MenuCellRenderer=itemToCellRenderer(event.item) as MenuCellRenderer;
//set the selected index to the index of the row that fired the event (previously set to -1)
selectedIndex = caretIndex = event.index;
if (row.data.data) {
//if the row has its own data (taken from XML and thrown into an object)
// If the menu is not visible, pop it up after a short delay
if (!row.subMenu || !row.subMenu.visible)
{
if (openSubMenuTimer)
clearInterval(openSubMenuTimer);
openSubMenuTimer = setTimeout(
function(row:MenuCellRenderer):void
{
//add check so the submenu is only opened when the mouse is over the row
if(mouseX > 0 && mouseX < row.x + row.width && mouseY > 0 && mouseY < row.y + row.height && visible)
openSubMenu(row);
},
175,
row);
}
}
}
/**
* @private
* Make submenu disappear when another item is hovered
*/
protected function itemRollOut(event:MenuEvent):void {
var row:MenuCellRenderer=itemToCellRenderer(event.item) as MenuCellRenderer;
//only reset if there is no visible subMenu and the selectedIndex is the same as the index of the row that fired the event
if(row)
{
if ((!row.subMenu || !row.subMenu.visible) && event.index == selectedIndex)
{
selectedIndex=-1;
}
}
}
/**
* @private
* <p>stageResizeHandler</p>
* <p>Adjusts menu postion, if necessary, when the stage resizes</p>
* <p>Will adjust menu position to keep in view when the stage size decreases</p>
* <p>Will adjust menu position towards its specified x and y coordinates when the stage size increases</p>
*/
protected function stageResizeHandler(event:Event):void
{
if(!isNaN(this.stage.stageWidth) && !isNaN(this.stage.stageHeight) && specifiedPoint != null)
{
if(visible && !isNaN(width) && !isNaN(specifiedPoint.x) && !isNaN(specifiedPoint.y) && !isNaN(height))
{
var pt:Point = new Point(x, y);
pt = realParent.localToGlobal(pt);
var specifiedPt:Point = realParent.localToGlobal(specifiedPoint);
if(pt.x + width > this.stage.stageWidth - Number(getStyleValue("rightMargin")))
{
x = realParent.globalToLocal(new Point(Math.max(this.stage.stageWidth - width - Number(getStyleValue("rightMargin")), Number(getStyleValue("leftMargin"))),0)).x;
}
else if(pt.x < specifiedPt.x)
{
x = Math.min(realParent.globalToLocal(new Point(this.stage.stageWidth - width - Number(getStyleValue("rightMargin")), 0)).x, specifiedPoint.x);
}
if(pt.y + height > this.stage.stageHeight - Number(getStyleValue("bottomMargin")))
{
y = Math.max(this.stage.stageHeight - height - Number(getStyleValue("bottomMargin")), getRootMenu().specifiedPoint.y + Number(getStyleValue("topMargin")));
}
else if(pt.y < specifiedPt.y)
{
y = Math.min(realParent.globalToLocal(new Point(0, this.stage.stageHeight - height - Number(getStyleValue("bottomMargin")))).y, specifiedPoint.y);
}
}
}
}
/**
* @private (protected)
* hide menus if the mouse moves off of the stage
*/
protected function mouseOutsideApplicationHandler(event:Event):void
{
hide();
}
/**
* Sets a style on the renderers in the list.
*
* @param name The name of the style to be set.
* @param style The value of the style to be set.
*
* @includeExample examples/SelectableList.setRendererStyle.1.as -noswf
* @includeExample examples/SelectableList.setRendererStyle.2.as -noswf
* @includeExample examples/SelectableList.setRendererStyle.3.as -noswf
* @includeExample examples/SelectableList.setRendererStyle.4.as -noswf
*
* @see #clearRendererStyle()
* @see #getRendererStyle()
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
override public function setRendererStyle(name:String, style:Object, column:uint=0):void
{
super.setRendererStyle(name, style, column);
if(this.subMenus != null && this.subMenus.length > 0)
{
var menus:Array = this.subMenus;
var len:int = menus.length;
for(var i:int = 0; i < len; i++)
{
this.copyRendererStylesToChild(menus[i], this.rendererStyles);
}
}
}
/**
* @private (protected)
*
* @langversion 3.0
* @playerversion Flash 9.0.28.0
*/
protected function copyRendererStylesToChild(child:Menu,styleMap:Object):void
{
for (var n:String in styleMap)
{
child.setRendererStyle(n, styleMap[n]);
}
}
/**
*
*
*/
override public function setStyle(style:String, value:Object):void
{
//Use strict equality so we can set a style to null ... so if the instanceStyles[style] == undefined, null is still set.
//We also need to work around the specific use case of TextFormats
if (instanceStyles[style] === value && !(value is TextFormat)) { return; }
if(value is InstanceFactory)
{
instanceStyles[style] = UIComponentUtil.getDisplayObjectInstance(this, (value as InstanceFactory).createInstance());
}
else
{
instanceStyles[style] = value;
}
invalidate(InvalidationType.STYLES);
}
/**
* @inheritDoc
*/
override protected function initializeAccessibility():void
{
if (Menu.createAccessibilityImplementation != null)
Menu.createAccessibilityImplementation(this);
}
}
}