////////////////////////////////////////////////////////////////////////////////
//
// 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 flash.display.StageOrientation;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.StageOrientationEvent;
import flash.ui.Keyboard;
import mx.core.FlexGlobals;
import mx.core.InteractionMode;
import mx.core.mx_internal;
import mx.events.SandboxMouseEvent;
import mx.managers.IFocusManagerComponent;
import spark.core.NavigationUnit;
use namespace mx_internal;
[DefaultProperty("items")]
//--------------------------------------
// States
//--------------------------------------
/**
* Normal and landscape state.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[SkinState("normalAndLandscape")]
/**
* Closed and landscape state.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[SkinState("closedAndLandscape")]
/**
* Disabled and landscape state.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
[SkinState("disabledAndLandscape")]
/**
* The ViewMenu container defines a menu in a View container.
* Each menu item is defined by using the ViewMenuItem control.
* The application container automatically creates and displays a
* ViewMenu container when the user presses the device's menu button.
* You can also use the ViewNavigatorApplicationBase.viewMenuOpen
property
* to open the menu programmatically.
*
*
The following image shows a ViewMenu at the bottom of the screen
* with five menu items:
*
*
*
*
*
* The ViewMenuLayout class define the layout of the menu.
* Alternatively, you can create your own custom layout class.
*
* Define the menu items by using the View.viewMenuItems
property,
* as the following example shows:
*
*
* <s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
* xmlns:s="library://ns.adobe.com/flex/spark"
* title="Home">
*
* ...
*
* <s:viewMenuItems>
* <s:ViewMenuItem label="Add" click="itemClickInfo(event);"/>
* <s:ViewMenuItem label="Cancel" click="itemClickInfo(event);"/>
* <s:ViewMenuItem label="Delete" click="itemClickInfo(event);"/>
* <s:ViewMenuItem label="Edit" click="itemClickInfo(event);"/>
* <s:ViewMenuItem label="Search" click="itemClickInfo(event);"/>
* </s:viewMenuItems>
*
* </s:View>
*
*
* Notice that you do not explicitly define the ViewMenu container in MXML.
* The ViewMenu container is created automatically
* to hold the ViewMenuItem controls.
*
* @see spark.components.ViewMenuItem
* @see spark.layouts.ViewMenuLayout
* @see spark.components.supportClasses.ViewNavigatorApplicationBase
* @see spark.skins.mobile.ViewMenuSkin
*
* @includeExample examples/ViewMenuExampleHome.mxml -noswf
* @includeExample examples/ViewMenuExample.mxml -noswf
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public class ViewMenu extends SkinnablePopUpContainer
implements IFocusManagerComponent
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function ViewMenu()
{
super();
// Listen for orientation change events when we are attached to the stage
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
// Tracks whether the mouse is down on the ViewMenu. If so, prevent keyboard
private var isMouseDown:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// caretIndex
//----------------------------------
private var _caretIndex:int = -1;
private var oldCaretIndex:int = -1;
private var caretIndexChanged:Boolean = false;
/**
* The menu item that is currently in the caret state.
* A value of -1 means that no item is in the caret state.
*
* @default -1
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get caretIndex():int
{
return _caretIndex;
}
/**
* @private
*/
public function set caretIndex(value:int):void
{
if (_caretIndex == value)
return;
oldCaretIndex = _caretIndex;
_caretIndex = value;
caretIndexChanged = true;
invalidateProperties();
}
//----------------------------------
// items
//----------------------------------
private var _items:Vector.;
/**
* The Vector of ViewMenuItem controls to display
* in the ViewMenu container.
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
public function get items():Vector.
{
return _items;
}
/**
* @private
*/
public function set items(value:Vector.):void
{
_items = value;
var elements:Array = [];
if (value)
{
for (var i:int = 0; i < value.length; i++)
{
elements.push(_items[i]);
}
}
mxmlContent = elements;
}
//--------------------------------------------------------------------------
//
// Overridden Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (caretIndexChanged)
{
caretIndexChanged = false;
// Hide the old caret and show the new one
setShowsCaret(oldCaretIndex, false);
setShowsCaret(caretIndex, true);
}
}
/**
* @private
* Build in basic keyboard navigation support in ViewMenu.
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
super.keyDownHandler(event);
if (!items || !layout || event.isDefaultPrevented() || isMouseDown)
return;
// 3. Was a navigation key hit (like an arrow key,
// or Shift+arrow key)?
// Delegate to the layout to interpret the navigation
// key and adjust the selection and caret item based
// on the combination of keystrokes encountered.
adjustSelectionAndCaretUponNavigation(event);
}
/**
* @private
*/
override protected function getCurrentSkinState():String
{
var skinState:String = super.getCurrentSkinState();
if (FlexGlobals.topLevelApplication.aspectRatio == "portrait")
return super.getCurrentSkinState();
else
return skinState + "AndLandscape";
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Adjusts the selection based on what keystroke or
* keystroke combinations were encountered. The keystroke
* is sent down to the layout and it is up to the layout's
* getNavigationDestinationIndex() method to determine
* what the index to navigate to based on the item that
* is currently in focus. Once the index is determined,
* single selection, caret item and if necessary, multiple
* selections are updated to reflect the newly selected
* item.
*
* @param event The Keyboard Event encountered
*
* @langversion 3.0
* @playerversion AIR 2.5
* @productversion Flex 4.5
*/
private function adjustSelectionAndCaretUponNavigation(event:KeyboardEvent):void
{
// If rtl layout, need to swap Keyboard.LEFT and Keyboard.RIGHT.
var navigationUnit:uint = mapKeycodeForLayoutDirection(event);
// Some unrecognized key stroke was entered, return.
if (!NavigationUnit.isNavigationUnit(event.keyCode))
return;
// Delegate to the layout to tell us what the next item is we should select or focus into.
// TODO (dsubrama): At some point we should refactor this so we don't depend on layout
// for keyboard handling. If layout doesn't exist, then use some other keyboard handler
var proposedNewIndex:int = layout.getNavigationDestinationIndex(caretIndex, navigationUnit, false);
// Note that the KeyboardEvent is canceled even if the current selected or in focus index
// doesn't change because we don't want another component to start handling these
// events when the index reaches a limit.
if (proposedNewIndex == -1)
return;
event.preventDefault();
// Entering the caret state with the Ctrl key down
// TODO (rfrishbe): shouldn't just check interactionMode but should depend on
// either the platform or whether it was a 5-way button or whether
// soem other keyboardSelection style.
if (event.ctrlKey || getStyle("interactionMode") == InteractionMode.TOUCH)
{
setShowsCaret(caretIndex, false);
_caretIndex = proposedNewIndex;
setShowsCaret(caretIndex, true);
}
}
/**
* Called when a particular item is selected using the ENTER or SPACE key
* @private
*/
private function selectItemAt(index:int):void
{
if (index < 0 || !items || index >= items.length)
return;
var item:ViewMenuItem = ViewMenuItem(getElementAt(index));
if (item.enabled)
item.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
/**
* Helper function which updates the item's caret state
* @private
*/
private function setShowsCaret(index:int, showsCaret:Boolean):void
{
if (index < 0 || !items || index >= items.length)
return;
var item:ViewMenuItem = ViewMenuItem(getElementAt(index));
item.showsCaret = showsCaret;
if (showsCaret)
item.setFocus();
}
//--------------------------------------------------------------------------
//
// Event Handlers
//
//--------------------------------------------------------------------------
private function addedToStageHandler(event:Event):void
{
addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
systemManager.stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGE, orientationChangeHandler, true);
}
private function removedFromStageHandler(event:Event):void
{
removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
systemManager.stage.removeEventListener(StageOrientationEvent.ORIENTATION_CHANGE, orientationChangeHandler, true);
}
private function orientationChangeHandler(event:StageOrientationEvent):void
{
invalidateSkinState();
}
private function mouseDownHandler(event:MouseEvent):void
{
// Clear the caret
caretIndex = -1;
isMouseDown = true;
// Listen for mouse up anywhere
systemManager.getSandboxRoot().addEventListener(
MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true /* useCapture */);
systemManager.getSandboxRoot().addEventListener(
SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler);
}
private function systemManager_mouseUpHandler(event:Event):void
{
systemManager.getSandboxRoot().removeEventListener(
MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true /* useCapture */);
systemManager.getSandboxRoot().removeEventListener(
SandboxMouseEvent.MOUSE_UP_SOMEWHERE, systemManager_mouseUpHandler);
isMouseDown = false;
}
}
}