//////////////////////////////////////////////////////////////////////////////// // // 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:

* *

* View menu *

* *

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; } } }