blob: 37c89f87072f7a0a006963f2b356390852252dfd [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 spark.components
{
import spark.components.listClasses.IListItemRenderer;
import spark.components.itemRenderers.*;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import flash.utils.getQualifiedClassName;
import mx.core.IFlexModuleFactory;
import mx.core.IVisualElement;
import mx.events.FlexMouseEvent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.IStyleManager2;
import mx.styles.StyleManager;
import spark.components.IItemRenderer;
import spark.components.List;
import spark.events.IndexChangeEvent;
import spark.events.MenuEvent;
import spark.skins.MenuSkin;
// for asdoc
[Experimental]
/**
* Plain simple Menu class, based upon list.
* It is used as standalone and in combination with MenuBar
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[Event(name="selected", type="spark.events.MenuEvent")]
[Event(name="checked", type="spark.events.MenuEvent")]
/**
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
/**
* @author Bogdan Dinu (http://www.badu.ro)
*/
public class Menu extends List
{
protected var _storedSelectedIndex : int = -1;
protected var _ignoreFocus : Boolean;
protected var _openedItem : MenuCoreItemRenderer;
/**
* Constructor
*
* We never use virtual layout, since it's not needed
* requireSelection is set to false in order to allow checkable items to be checked
* If you remove requireSelection the checkable items will work, but
* the menu will close immediately after clicking that item
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function Menu()
{
super();
useVirtualLayout = false;
requireSelection = false;
}
/**
**/
protected var _parentMenu : List;
public function get parentMenu():List
{
return _parentMenu;
}
public function set parentMenu(value:List):void
{
_parentMenu = value;
}
/**
* Sets for every item renderer the instance of this,
* so we can dispatch events and access properties like labelField
*/
override public function updateRenderer(renderer:IVisualElement, itemIndex:int, data:Object):void
{
super.updateRenderer(renderer, itemIndex, data);
if (renderer is IListItemRenderer)
{
(renderer as IListItemRenderer).listOwner = this;
}
}
/**
* From keyboard navigation, called by a child to pass over focus and close popup on opened item
*/
public function keyDownInChild(e:KeyboardEvent):void
{
if (parentMenu is Menu)
{
_openedItem.popup.displayPopUp = false;
}
closeSelectedItemRenderer();
_ignoreFocus = true;
setFocus();
}
/**
* From keyboard navigation, checks if submenu is present
*/
protected function get selectedItemRendererShouldOpen():Boolean
{
if (selectedIndex < 0) return false;
return (dataGroup.getElementAt(selectedIndex) as MenuCoreItemRenderer).dataProvider.length > 0;
}
/**
* From keyboard navigation, when we need to close a submenu and cleanup
*/
protected function closeSelectedItemRenderer():void
{
_openedItem.selected = false;
_openedItem.setHovered ( false );
_openedItem = null;
}
/**
* From keyboard navigation, when we are entitled to open a submenu
*/
protected function openAndFocusSelectedItemRenderer():void
{
var item : MenuCoreItemRenderer = dataGroup.getElementAt(selectedIndex) as MenuCoreItemRenderer;
if (item)
{
_openedItem = item;
item.selected = false;
item.setHovered ( true );
}
}
/**
* From keyboard navigation, when submenu closes and parents need focus
*/
protected function closeAndFocusParent(e:KeyboardEvent):void
{
parentMenu.setFocus();
if (parentMenu is MenuBar)
{
(parentMenu as MenuBar).keyDownInChild(e);
}
else if (parentMenu is Menu)
{
(parentMenu as Menu).keyDownInChild(e);
}
}
/**
* From keyboard navigation, we check if it's necessary to open a submenu
*/
public function openIfNecessary():void
{
if (selectedItemRendererShouldOpen)
{
if (!_openedItem)
{
openAndFocusSelectedItemRenderer();
}
}
}
/**
* Handle navigation, up and down arrow keys and enter or space selects
* Escape key, closes menu
*
* Doesn't work very very well, probably the focus manager is sometimes
* too slow for Spark components.
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
override protected function keyDownHandler(e:KeyboardEvent):void
{
switch (e.keyCode)
{
case Keyboard.RIGHT:
selectedIndex = _storedSelectedIndex;
if (_openedItem)
{
_openedItem.subMenu.selectedIndex = 0;
_openedItem.subMenu.setFocus();
(_openedItem.subMenu as Menu).openIfNecessary();
}
else if (selectedItemRendererShouldOpen)
{
if (!_openedItem)
{
openAndFocusSelectedItemRenderer();
}
}
else
{
if (parentMenu is MenuBar)
{
closeAndFocusParent(e);
}
}
break;
case Keyboard.LEFT:
closeAndFocusParent(e);
break;
case Keyboard.UP:
selectedIndex = _storedSelectedIndex;
if (_openedItem)
{
closeSelectedItemRenderer();
}
if (selectedIndex > 0)
{
do
{
selectedIndex--;
} while (dataProvider.getItemAt(selectedIndex).@separator.toString() == "true");
}
_storedSelectedIndex = selectedIndex;
if (selectedItemRendererShouldOpen)
{
if (!_openedItem)
{
openAndFocusSelectedItemRenderer();
}
}
break;
case Keyboard.DOWN:
selectedIndex = _storedSelectedIndex;
if (_openedItem)
{
closeSelectedItemRenderer();
}
if (selectedIndex < dataProvider.length - 1)
{
do
{
selectedIndex++;
} while (dataProvider.getItemAt(selectedIndex).@separator.toString() == "true");
}
_storedSelectedIndex = selectedIndex;
if (selectedItemRendererShouldOpen)
{
if (!_openedItem)
{
openAndFocusSelectedItemRenderer();
}
}
break;
case Keyboard.ESCAPE:
dispatchEvent(new FlexMouseEvent(FlexMouseEvent.MOUSE_DOWN_OUTSIDE, false, false, 0, 0, null, false, false, false, false, 0));
break;
case Keyboard.ENTER:
case Keyboard.SPACE:
if (selectedItem.@isCheckable.toString() == 'true')
{
if (dataProvider.getItemAt(selectedIndex).@isChecked == 'true')
{
dataProvider.getItemAt(selectedIndex).@isChecked = 'false';
}
else
{
dataProvider.getItemAt(selectedIndex).@isChecked = 'true';
}
dispatchEvent(new MenuEvent(MenuEvent.CHECKED, false, false, this, dataProvider.getItemAt(selectedIndex)));
}
else
{
dispatchEvent(new MenuEvent(MenuEvent.SELECTED, false, false, this, selectedItem));
}
break;
default:
super.keyDownHandler(e);
break;
}
}
/**
* Overriden to implement checkable behavior.
* If an item is checkable, we alter data and dispatch the event
* If not, we just using the super method
*/
override protected function item_mouseDownHandler(event:MouseEvent):void
{
var canCallSuper : Boolean = true;
if (event.currentTarget is IItemRenderer)
{
var itemRendererTarget : IItemRenderer = (event.currentTarget as IItemRenderer);
if (itemRendererTarget.data.@isCheckable.toString() == 'true')
{
canCallSuper = false;
if (itemRendererTarget.data.@isChecked == 'true')
{
itemRendererTarget.data.@isChecked = 'false';
}
else
{
itemRendererTarget.data.@isChecked = 'true';
}
dispatchEvent(new MenuEvent(MenuEvent.CHECKED, false, false, this, itemRendererTarget.data));
}
}
if (canCallSuper)
{
super.item_mouseDownHandler(event);
}
}
/**
* We are supposed to dispatch MenuEvent instead of IndexChangeEvent
*
* @langversion 3.0
* @playerversion Flash 10.1
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
override public function dispatchEvent(e:Event):Boolean
{
if (!(e is MenuEvent))
{
if (e is IndexChangeEvent)
{
if (selectedItem)
{
if (selectedItem.@separator.toString() == "true")
{
selectedIndex = -1;
return true;
}
if (selectedItem.children().length() > 0)
{
selectedIndex = -1;
return true;
}
if (e.type == IndexChangeEvent.CHANGE)
{
return super.dispatchEvent(new MenuEvent(MenuEvent.SELECTED, e.bubbles, true, this, selectedItem));
}
}
}
}
return super.dispatchEvent(e);
}
}
}