//////////////////////////////////////////////////////////////////////////////// // // 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 mx.managers { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.InteractiveObject; import flash.display.Sprite; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.FocusEvent; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.system.Capabilities; import flash.system.IME; import flash.text.TextField; import flash.ui.Keyboard; import mx.core.FlexSprite; import mx.core.IButton; import mx.core.IChildList; import mx.core.IIMESupport; import mx.core.IRawChildrenContainer; import mx.core.ISWFLoader; import mx.core.IToggleButton; import mx.core.IUIComponent; import mx.core.IVisualElement; import mx.core.mx_internal; import mx.events.FlexEvent; use namespace mx_internal; /** * The FocusManager class manages the focus on components in response to mouse * activity or keyboard activity (Tab key). There can be several FocusManager * instances in an application. Each FocusManager instance * is responsible for a set of components that comprise a "tab loop". If you * hit Tab enough times, focus traverses through a set of components and * eventually get back to the first component that had focus. That is a "tab loop" * and a FocusManager instance manages that loop. If there are popup windows * with their own set of components in a "tab loop" those popup windows will have * their own FocusManager instances. The main application always has a * FocusManager instance. * *

The FocusManager manages focus from the "component level". * In Flex, a UITextField in a component is the only way to allow keyboard entry * of text. To the Flash Player or AIR, that UITextField has focus. However, from the * FocusManager's perspective the component that parents the UITextField has focus. * Thus there is a distinction between component-level focus and player-level focus. * Application developers generally only have to deal with component-level focus while * component developers must understand player-level focus.

* *

All components that can be managed by the FocusManager must implement * mx.managers.IFocusManagerComponent, whereas objects managed by player-level focus do not.

* *

The FocusManager also managers the concept of a defaultButton, which is * the Button on a form that dispatches a click event when the Enter key is pressed * depending on where focus is at that time.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class FocusManager extends EventDispatcher implements IFocusManager { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * @private * * Default value of parameter, ignore. */ private static const FROM_INDEX_UNSPECIFIED:int = -2; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private * * Place to hook in additional classes */ public static var mixins:Array; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * *

A FocusManager manages the focus within the children of an IFocusManagerContainer. * It installs itself in the IFocusManagerContainer during execution * of the constructor.

* * @param container An IFocusManagerContainer that hosts the FocusManager. * * @param popup If true, indicates that the container * is a popup component and not the main application. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function FocusManager(container:IFocusManagerContainer, popup:Boolean = false) { super(); this.popup = popup; IMEEnabled = true; browserMode = Capabilities.playerType == "ActiveX" && !popup; desktopMode = Capabilities.playerType == "Desktop" && !popup; // Flash main windows come up activated, AIR main windows don't windowActivated = !desktopMode; container.focusManager = this; // this property name is reserved in the parent // trace("FocusManager constructor " + container + ".focusManager"); _form = container; focusableObjects = []; focusPane = new FlexSprite(); focusPane.name = "focusPane"; addFocusables(DisplayObject(container)); // Listen to the stage so we know when the root application is loaded. container.addEventListener(Event.ADDED, addedHandler); container.addEventListener(Event.REMOVED, removedHandler); container.addEventListener(FlexEvent.SHOW, showHandler); container.addEventListener(FlexEvent.HIDE, hideHandler); container.addEventListener(FlexEvent.HIDE, childHideHandler, true); container.addEventListener("_navigationChange_",viewHideHandler, true); //special case application and window if (container.systemManager is SystemManager) { // special case application. It shouldn't need to be made // active and because we defer appCreationComplete, this // would steal focus back from any popups created during // instantiation if (container != SystemManager(container.systemManager).application) container.addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler); } if (mixins) { var n:int = mixins.length; for (var i:int = 0; i < n; i++) { new mixins[i](this); } } // Make sure the SystemManager is running so it can tell us about // mouse clicks and stage size changes. try { var awm:IActiveWindowManager = IActiveWindowManager(container.systemManager.getImplementation("mx.managers::IActiveWindowManager")); if (awm) awm.addFocusManager(container); // build a message that does the equal if (hasEventListener("initialize")) dispatchEvent(new Event("initialize")); } catch (e:Error) { // ignore null pointer errors caused by container using a // systemManager from another sandbox. } } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- private var LARGE_TAB_INDEX:int = 99999; mx_internal var calculateCandidates:Boolean = true; /** * @private * * True if this focus manager is a popup, false if it is a main application. * */ mx_internal var popup:Boolean; /** * @private * * True if this focus manager will try to enable/disable the IME based on * whether the focused control uses IME. Leaving this as a backdoor just in case. * */ mx_internal var IMEEnabled:Boolean; /** * @private * We track whether we've been last activated or saw a TAB * This is used in browser tab management */ mx_internal var lastAction:String; /** * @private * Tab management changes based on whether were in a browser or not * This value is also affected by whether you are a modal dialog or not */ public var browserMode:Boolean; /** * @private * Activation changes depending on whether we're running in AIR or not */ public var desktopMode:Boolean; /** * @private * Tab management changes based on whether were in a browser or not * If non-null, this is the object that will * lose focus to the browser */ private var browserFocusComponent:InteractiveObject; /** * @private * Total set of all objects that can receive focus * but might be disabled or invisible. */ mx_internal var focusableObjects:Array; /** * @private * Filtered set of objects that can receive focus right now. */ private var focusableCandidates:Array; /** * @private */ private var activated:Boolean; /** * @private */ private var windowActivated:Boolean; /** * @private * * true if focus was changed to one of focusable objects. False if focus passed to * the browser. */ mx_internal var focusChanged:Boolean; /** * @private * * if non-null, the location to move focus from instead of the object * that has focus in the stage. */ mx_internal var fauxFocus:DisplayObject; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // showFocusIndicator //---------------------------------- /** * @private * Storage for the showFocusIndicator property. */ mx_internal var _showFocusIndicator:Boolean = false; /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showFocusIndicator():Boolean { return _showFocusIndicator; } /** * @private */ public function set showFocusIndicator(value:Boolean):void { var changed:Boolean = _showFocusIndicator != value; // trace("FM " + this + " showFocusIndicator = " + value); _showFocusIndicator = value; if (hasEventListener("showFocusIndicator")) dispatchEvent(new Event("showFocusIndicator")); } //---------------------------------- // defaultButton //---------------------------------- /** * @private * The current default button. */ private var defButton:IButton; /** * @private */ private var _defaultButton:IButton; /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get defaultButton():IButton { return _defaultButton; } /** * @private * We don't type the value as Button for dependency reasons */ public function set defaultButton(value:IButton):void { var button:IButton = value ? IButton(value) : null; if (button != _defaultButton) { if (_defaultButton) _defaultButton.emphasized = false; if (defButton) defButton.emphasized = false; _defaultButton = button; if (defButton != _lastFocus || _lastFocus == _defaultButton) { defButton = button; if (button) button.emphasized = true; } } } //---------------------------------- // defaultButtonEnabled //---------------------------------- /** * @private * Storage for the defaultButtonEnabled property. */ private var _defaultButtonEnabled:Boolean = true; /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get defaultButtonEnabled():Boolean { return _defaultButtonEnabled; } /** * @private */ public function set defaultButtonEnabled(value:Boolean):void { _defaultButtonEnabled = value; // Synchronize with the new value. We ensure that our // default button is de-emphasized if defaultButtonEnabled // is false. if (defButton) defButton.emphasized = value; } //---------------------------------- // focusPane //---------------------------------- /** * @private * Storage for the focusPane property. */ private var _focusPane:Sprite; /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get focusPane():Sprite { return _focusPane; } /** * @private */ public function set focusPane(value:Sprite):void { _focusPane = value; } //---------------------------------- // form //---------------------------------- /** * @private * Storage for the form property. */ private var _form:IFocusManagerContainer; /** * @private * The form is the property where we store the IFocusManagerContainer * that hosts this FocusManager. */ mx_internal function get form():IFocusManagerContainer { return _form; } /** * @private */ mx_internal function set form (value:IFocusManagerContainer):void { _form = value; } //---------------------------------- // _lastFocus //---------------------------------- /** * @private * the object that last had focus */ private var _lastFocus:IFocusManagerComponent; /** * @private */ mx_internal function get lastFocus():IFocusManagerComponent { return _lastFocus; } /** * @private */ mx_internal function set lastFocus(value:IFocusManagerComponent):void { _lastFocus = value; } //---------------------------------- // nextTabIndex //---------------------------------- /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get nextTabIndex():int { return getMaxTabIndex() + 1; } /** * Gets the highest tab index currently used in this Focus Manager's form or subform. * * @return Highest tab index currently used. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private function getMaxTabIndex():int { var z:Number = 0; var n:int = focusableObjects.length; for (var i:int = 0; i < n; i++) { var t:Number = focusableObjects[i].tabIndex; if (!isNaN(t)) z = Math.max(z, t); } return z; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function getFocus():IFocusManagerComponent { var stage:Stage = form.systemManager.stage; if (!stage) return null; var o:InteractiveObject = stage.focus; // If a Stage* object (such as StageText or StageWebView) has focus, // stage.focus will be set to null. Much of the focus framework is not // set up to handle this. So, if stage.focus is null, we return the last // IFocusManagerComponent that had focus. In ADL, focus works slightly // different than it does on device when using StageText. In ADL, when // the focus is a StageText component, a TextField whose parent is the // stage is assigned focus. if ((!o && _lastFocus) || (o is TextField && o.parent == stage)) return _lastFocus; return findFocusManagerComponent(o); } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setFocus(o:IFocusManagerComponent):void { // trace("FM " + this + " setting focus to " + o); o.setFocus(); if (hasEventListener("setFocus")) dispatchEvent(new Event("setFocus")); // trace("FM set focus"); } /** * @private */ private function focusInHandler(event:FocusEvent):void { var target:InteractiveObject = InteractiveObject(event.target); // trace("FocusManager focusInHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FM " + this + " focusInHandler " + target); // dispatch cancelable FocusIn to see if Marshal Plan mixin wants it if (hasEventListener(FocusEvent.FOCUS_IN)) if (!dispatchEvent(new FocusEvent(FocusEvent.FOCUS_IN, false, true, target))) return; if (isParent(DisplayObjectContainer(form), target)) { if (_defaultButton) { if (target is IButton && target != _defaultButton && !(target is IToggleButton)) _defaultButton.emphasized = false; else if (_defaultButtonEnabled) _defaultButton.emphasized = true; } // trace("FM " + this + " setting last focus " + target); _lastFocus = findFocusManagerComponent(InteractiveObject(target)); if (Capabilities.hasIME) { var usesIME:Boolean; if (_lastFocus is IIMESupport) { var imeFocus:IIMESupport = IIMESupport(_lastFocus); if (imeFocus.enableIME) usesIME = true; } if (IMEEnabled) IME.enabled = usesIME; } // handle default button here // we can't check for Button because of cross-versioning so // for now we just check for an emphasized property if (_lastFocus is IButton && !(_lastFocus is IToggleButton)) { defButton = _lastFocus as IButton; } else { // restore the default button to be the original one if (defButton && defButton != _defaultButton) defButton = _defaultButton; } } } /** * @private Useful for debugging */ private function focusOutHandler(event:FocusEvent):void { var target:InteractiveObject = InteractiveObject(event.target); // trace("FocusManager focusOutHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FM " + this + " focusOutHandler " + target); } /** * @private * restore focus to whoever had it last */ private function activateHandler(event:Event):void { // var target:InteractiveObject = InteractiveObject(event.target); // trace("FM " + this + " activateHandler ", _lastFocus); // if we were the active FM when we were deactivated // and we're not running in AIR, then dispatch the event now // otherwise wait for the AIR events to fire if (activated && !desktopMode) { dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_ACTIVATE)); // restore focus if this focus manager had last focus if (_lastFocus && !browserMode) _lastFocus.setFocus(); lastAction = "ACTIVATE"; } } /** * @private * Dispatch event if we're not running in AIR. AIR will * dispatch windowDeactivate that we respond to instead */ private function deactivateHandler(event:Event):void { // var target:InteractiveObject = InteractiveObject(event.target); // trace("FM " + this + " deactivateHandler ", _lastFocus); // if we are the active FM when we were deactivated // and we're not running in AIR, then dispatch the event now // otherwise wait for the AIR events to fire if (activated && !desktopMode) { dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_DEACTIVATE)); } } /** * @private * restore focus to whoever had it last */ private function activateWindowHandler(event:Event):void { // var target:InteractiveObject = InteractiveObject(event.target); // trace("FM " + this + " activateWindowHandler ", _lastFocus); windowActivated = true; if (activated) { dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_ACTIVATE)); // restore focus if this focus manager had last focus if (_lastFocus && !browserMode) _lastFocus.setFocus(); lastAction = "ACTIVATE"; } } /** * @private * If we're responsible for the focused control, remove focus from it * so it gets the same events as it would if the whole app lost focus */ private function deactivateWindowHandler(event:Event):void { // var target:InteractiveObject = InteractiveObject(event.target); // trace("FM " + this + " deactivateWindowHandler ", _lastFocus); windowActivated = false; if (activated) { dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_DEACTIVATE)); if (form.systemManager.stage) form.systemManager.stage.focus = null; } } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function showFocus():void { if (!showFocusIndicator) { showFocusIndicator = true; if (_lastFocus) _lastFocus.drawFocus(true); } } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function hideFocus():void { // trace("FOcusManger " + this + " Hide Focus"); if (showFocusIndicator) { showFocusIndicator = false; if (_lastFocus) _lastFocus.drawFocus(false); } // trace("END FOcusManger Hide Focus"); } /** * The SystemManager activates and deactivates a FocusManager * if more than one IFocusManagerContainer is visible at the same time. * If the mouse is clicked in an IFocusManagerContainer with a deactivated * FocusManager, the SystemManager will call * the activate() method on that FocusManager. * The FocusManager that was activated will have its deactivate() method * called prior to the activation of another FocusManager. * *

The FocusManager adds event handlers that allow it to monitor * focus related keyboard and mouse activity.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function activate():void { // we can get a double activation if we're popping up and becoming visible // like the second time a menu appears if (activated) { // trace("FocusManager is already active " + this); return; } // trace("FocusManager activating = " + this._form.systemManager.loaderInfo.url); // trace("FocusManager activating " + this); // listen for focus changes, use weak references for the stage // form.systemManager can be null if the form is created in a sandbox and // added as a child to the root system manager. var sm:ISystemManager = form.systemManager; if (sm) { if (sm.isTopLevelRoot()) { sm.stage.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, mouseFocusChangeHandler, false, 0, true); sm.stage.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler, false, 0, true); sm.stage.addEventListener(Event.ACTIVATE, activateHandler, false, 0, true); sm.stage.addEventListener(Event.DEACTIVATE, deactivateHandler, false, 0, true); } else { sm.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, mouseFocusChangeHandler, false, 0, true); sm.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler, false, 0, true); sm.addEventListener(Event.ACTIVATE, activateHandler, false, 0, true); sm.addEventListener(Event.DEACTIVATE, deactivateHandler, false, 0, true); } } form.addEventListener(FocusEvent.FOCUS_IN, focusInHandler, true); form.addEventListener(FocusEvent.FOCUS_OUT, focusOutHandler, true); form.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); form.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownCaptureHandler, true); form.addEventListener(KeyboardEvent.KEY_DOWN, defaultButtonKeyHandler); form.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler, true); if (sm) { // AIR Window events, but don't want to link in AIREvent // use capture phase because these get sent by the main Window // and we might be managing a popup in that window sm.addEventListener("windowActivate", activateWindowHandler, true, 0, true); sm.addEventListener("windowDeactivate", deactivateWindowHandler, true, 0, true); } activated = true; dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_ACTIVATE)); // Restore focus to the last control that had it if there was one. if (_lastFocus) setFocus(_lastFocus); if (hasEventListener("activateFM")) dispatchEvent(new Event("activateFM")); } /** * The SystemManager activates and deactivates a FocusManager * if more than one IFocusManagerContainer is visible at the same time. * If the mouse is clicked in an IFocusManagerContainer with a deactivated * FocusManager, the SystemManager will call * the activate() method on that FocusManager. * The FocusManager that was activated will have its deactivate() method * called prior to the activation of another FocusManager. * *

The FocusManager removes event handlers that allow it to monitor * focus related keyboard and mouse activity.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function deactivate():void { // trace("FocusManager deactivating " + this); // trace("FocusManager deactivating = " + this._form.systemManager.loaderInfo.url); // listen for focus changes var sm:ISystemManager = form.systemManager; if (sm) { if (sm.isTopLevelRoot()) { sm.stage.removeEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, mouseFocusChangeHandler); sm.stage.removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler); sm.stage.removeEventListener(Event.ACTIVATE, activateHandler); sm.stage.removeEventListener(Event.DEACTIVATE, deactivateHandler); } else { sm.removeEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, mouseFocusChangeHandler); sm.removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler); sm.removeEventListener(Event.ACTIVATE, activateHandler); sm.removeEventListener(Event.DEACTIVATE, deactivateHandler); } } form.removeEventListener(FocusEvent.FOCUS_IN, focusInHandler, true); form.removeEventListener(FocusEvent.FOCUS_OUT, focusOutHandler, true); form.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler); form.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownCaptureHandler, true); form.removeEventListener(KeyboardEvent.KEY_DOWN, defaultButtonKeyHandler); // stop listening for default button in Capture phase form.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler, true); activated = false; dispatchEvent(new FlexEvent(FlexEvent.FLEX_WINDOW_DEACTIVATE)); if (hasEventListener("deactivateFM")) dispatchEvent(new Event("deactivateFM")); } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function findFocusManagerComponent( o:InteractiveObject):IFocusManagerComponent { return findFocusManagerComponent2(o) as IFocusManagerComponent; } /** * @private * * This version of the method differs from the old one to support SWFLoader * being in the focusableObjects list but not being a component that * gets focus. SWFLoader is in the list of focusable objects so * focus may be passed over a bridge to the components on the other * side of the bridge. */ private function findFocusManagerComponent2( o:InteractiveObject):DisplayObject { try { while (o) { if ((o is IFocusManagerComponent && IFocusManagerComponent(o).focusEnabled) || o is ISWFLoader) return o; o = o.parent; } } catch (error:SecurityError) { // can happen in a loaded child swf // trace("findFocusManagerComponent: handling security error"); } // tab was set somewhere else return null; } /** * @private * Returns true if p is a parent of o. */ private function isParent(p:DisplayObjectContainer, o:DisplayObject):Boolean { if (p == o) return false; if (p is IRawChildrenContainer) return IRawChildrenContainer(p).rawChildren.contains(o); return p.contains(o); } private function isEnabledAndVisible(o:DisplayObject):Boolean { var formParent:DisplayObjectContainer = DisplayObjectContainer(form); while (o != formParent) { if (o is IUIComponent) if (!IUIComponent(o).enabled) return false; if (o is IVisualElement) if (IVisualElement(o).designLayer && !IVisualElement(o).designLayer.effectiveVisibility) return false; if (!o.visible) return false; o = o.parent; // if no parent, then not on display list if (!o) return false; } return true; } /** * @private */ private function sortByTabIndex(a:InteractiveObject, b:InteractiveObject):int { var aa:int = a.tabIndex; var bb:int = b.tabIndex; if (aa == -1) aa = int.MAX_VALUE; if (bb == -1) bb = int.MAX_VALUE; return (aa > bb ? 1 : aa < bb ? -1 : sortByDepth(DisplayObject(a), DisplayObject(b))); } /** * @private */ private function sortFocusableObjectsTabIndex():void { // trace("FocusableObjectsTabIndex"); focusableCandidates = []; var n:int = focusableObjects.length; for (var i:int = 0; i < n; i++) { var c:IFocusManagerComponent = focusableObjects[i] as IFocusManagerComponent; if ((c && c.tabIndex && !isNaN(Number(c.tabIndex))) || focusableObjects[i] is ISWFLoader) { // if we get here, it is a candidate focusableCandidates.push(focusableObjects[i]); } } focusableCandidates.sort(sortByTabIndex); } /** * @private */ private function sortByDepth(aa:DisplayObject, bb:DisplayObject):Number { var val1:String = ""; var val2:String = ""; var index:int; var tmp:String; var tmp2:String; var zeros:String = "0000"; var a:DisplayObject = DisplayObject(aa); var b:DisplayObject = DisplayObject(bb); // TODO (egreenfi): If a component lives inside of a group, we care about not its display object index, but // its index within the group. See SDK-25144 while (a != DisplayObject(form) && a.parent) { index = getChildIndex(a.parent, a); tmp = index.toString(16); if (tmp.length < 4) { tmp2 = zeros.substring(0, 4 - tmp.length) + tmp; } val1 = tmp2 + val1; a = a.parent; } while (b != DisplayObject(form) && b.parent) { index = getChildIndex(b.parent, b); tmp = index.toString(16); if (tmp.length < 4) { tmp2 = zeros.substring(0, 4 - tmp.length) + tmp; } val2 = tmp2 + val2; b = b.parent; } return val1 > val2 ? 1 : val1 < val2 ? -1 : 0; } private function getChildIndex(parent:DisplayObjectContainer, child:DisplayObject):int { try { return parent.getChildIndex(child); } catch(e:Error) { if (parent is IRawChildrenContainer) return IRawChildrenContainer(parent).rawChildren.getChildIndex(child); throw e; } throw new Error("FocusManager.getChildIndex failed"); // shouldn't ever get here } /** * @private * Calculate what focusableObjects are valid tab candidates. */ private function sortFocusableObjects():void { // trace("FocusableObjects " + focusableObjects.length.toString()); focusableCandidates = []; var n:int = focusableObjects.length; for (var i:int = 0; i < n; i++) { var c:InteractiveObject = focusableObjects[i]; // trace(" " + c); if (c.tabIndex && !isNaN(Number(c.tabIndex)) && c.tabIndex > 0) { sortFocusableObjectsTabIndex(); return; } focusableCandidates.push(c); } focusableCandidates.sort(sortByDepth); } /** * Call this method to make the system * think the Enter key was pressed and the defaultButton was clicked * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ mx_internal function sendDefaultButtonEvent():void { // trace("FocusManager.sendDefaultButtonEvent " + defButton); defButton.dispatchEvent(new MouseEvent("click")); } /** * @private * Do a tree walk and add all children you can find. */ mx_internal function addFocusables(o:DisplayObject, skipTopLevel:Boolean = false):void { // trace(">>addFocusables " + o); if ((o is IFocusManagerComponent) && !skipTopLevel) { var addToFocusables:Boolean = false; if (o is IFocusManagerComponent) { var focusable:IFocusManagerComponent = IFocusManagerComponent(o); if (focusable.focusEnabled) { if (focusable.tabFocusEnabled && isTabVisible(o)) { addToFocusables = true; } } } if (addToFocusables) { if (focusableObjects.indexOf(o) == -1) { focusableObjects.push(o); calculateCandidates = true; // trace("FM added " + o); } } o.addEventListener("tabFocusEnabledChange", tabFocusEnabledChangeHandler); o.addEventListener("tabIndexChange", tabIndexChangeHandler); } if (o is DisplayObjectContainer) { var doc:DisplayObjectContainer = DisplayObjectContainer(o); // Even if they aren't focusable now, // listen in case they become later. var checkChildren:Boolean; if (o is IFocusManagerComponent) { o.addEventListener("hasFocusableChildrenChange", hasFocusableChildrenChangeHandler); checkChildren = IFocusManagerComponent(o).hasFocusableChildren; } else { o.addEventListener("tabChildrenChange", tabChildrenChangeHandler); checkChildren = doc.tabChildren; } if (checkChildren) { if (o is IRawChildrenContainer) { // trace("using view rawChildren"); var rawChildren:IChildList = IRawChildrenContainer(o).rawChildren; // recursively visit and add children of components // we don't do this for containers because we get individual // adds for the individual children var i:int; for (i = 0; i < rawChildren.numChildren; i++) { try { addFocusables(rawChildren.getChildAt(i)); } catch(error:SecurityError) { // Ignore this child if we can't access it // trace("addFocusables: ignoring security error getting child from rawChildren: " + error); } } } else { // trace("using container's children"); // recursively visit and add children of components // we don't do this for containers because we get individual // adds for the individual children for (i = 0; i < doc.numChildren; i++) { try { addFocusables(doc.getChildAt(i)); } catch(error:SecurityError) { // Ignore this child if we can't access it // trace("addFocusables: ignoring security error getting child at document." + error); } } } } } // trace("<= i) wrapped = true; } else if (j <= i) wrapped = true; var focusInfo:FocusInfo = new FocusInfo(); focusInfo.displayObject = findFocusManagerComponent2(focusableCandidates[j]); focusInfo.wrapped = wrapped; return focusInfo; } /** * @private */ private function getTopLevelFocusTarget(o:InteractiveObject):InteractiveObject { while (o != InteractiveObject(form)) { if (o is IFocusManagerComponent && IFocusManagerComponent(o).focusEnabled && IFocusManagerComponent(o).mouseFocusEnabled && (o is IUIComponent ? IUIComponent(o).enabled : true)) return o; if (hasEventListener("getTopLevelFocusTarget")) if (!dispatchEvent(new FocusEvent("getTopLevelFocusTarget", false, true, o.parent))) return null; o = o.parent; if (o == null) break; } return null; } /** * Returns a String representation of the component hosting the FocusManager object, * with the String ".focusManager" appended to the end of the String. * * @return Returns a String representation of the component hosting the FocusManager object, * with the String ".focusManager" appended to the end of the String. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function toString():String { return Object(form).toString() + ".focusManager"; } /** * @private * * Clear the browser focus component and undo any tab index we may have set. */ private function clearBrowserFocusComponent():void { if (browserFocusComponent) { if (browserFocusComponent.tabIndex == LARGE_TAB_INDEX) browserFocusComponent.tabIndex = -1; browserFocusComponent = null; } } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private * Listen for children being added * and see if they are focus candidates. */ private function addedHandler(event:Event):void { var target:DisplayObject = DisplayObject(event.target); // trace("FM: addedHandler: got added for " + target); // if it is truly parented, add it, otherwise it will get added when the top of the tree // gets parented. if (target.stage) { // trace("FM: addedHandler: adding focusables"); addFocusables(DisplayObject(event.target)); } } /** * @private * Listen for children being removed. */ private function removedHandler(event:Event):void { var i:int; var o:DisplayObject = DisplayObject(event.target); var focusPaneParent:DisplayObject = focusPane ? focusPane.parent : null; // Remove the focusPane to allow the focusOwner to be garbage collected. // Avoid recursion by not processing the removal of the focusPane itself. if (focusPaneParent && o != focusPane) { if (o is DisplayObjectContainer && isParent(DisplayObjectContainer(o), focusPane)) { if (focusPaneParent is ISystemManager) ISystemManager(focusPaneParent).focusPane = null; else IUIComponent(focusPaneParent).focusPane = null; } } // trace("FM got added for " + event.target); if (o is IFocusManagerComponent) { for (i = 0; i < focusableObjects.length; i++) { if (o == focusableObjects[i]) { if (o == _lastFocus) { _lastFocus.drawFocus(false); _lastFocus = null; } // trace("FM removed " + o); focusableObjects.splice(i, 1); focusableCandidates = []; calculateCandidates = true; break; } } o.removeEventListener("tabFocusEnabledChange", tabFocusEnabledChangeHandler); o.removeEventListener("tabIndexChange", tabIndexChangeHandler); } removeFocusables(o, false); } /** * @private */ private function removeFocusables(o:DisplayObject, dontRemoveTabChildrenHandler:Boolean):void { var i:int; if (o is DisplayObjectContainer) { if (!dontRemoveTabChildrenHandler) { o.removeEventListener("tabChildrenChange", tabChildrenChangeHandler); o.removeEventListener("hasFocusableChildrenChange", hasFocusableChildrenChangeHandler); } for (i = 0; i < focusableObjects.length; i++) { if (isParent(DisplayObjectContainer(o), focusableObjects[i])) { if (focusableObjects[i] == _lastFocus) { _lastFocus.drawFocus(false); _lastFocus = null; } // trace("FM removed " + focusableObjects[i]); focusableObjects[i].removeEventListener( "tabFocusEnabledChange", tabFocusEnabledChangeHandler); focusableObjects[i].removeEventListener( "tabIndexChange", tabIndexChangeHandler); focusableObjects.splice(i, 1); i = i - 1; // because increment would skip one focusableCandidates = []; calculateCandidates = true; } } } } /** * @private */ private function showHandler(event:Event):void { var awm:IActiveWindowManager = IActiveWindowManager(form.systemManager.getImplementation("mx.managers::IActiveWindowManager")); if (awm) awm.activate(form); // build a message that does the equal } /** * @private */ private function hideHandler(event:Event):void { var awm:IActiveWindowManager = IActiveWindowManager(form.systemManager.getImplementation("mx.managers::IActiveWindowManager")); if (awm) awm.deactivate(form); // build a message that does the equal } /** * @private */ private function childHideHandler(event:Event):void { var target:DisplayObject = DisplayObject(event.target); // trace("FocusManager focusInHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FM " + this + " focusInHandler " + target); if (lastFocus && !isEnabledAndVisible(DisplayObject(lastFocus)) && DisplayObject(form).stage) { DisplayObject(form).stage.focus = null; lastFocus = null; } } /** * @private */ private function viewHideHandler(event:Event):void { // Target is the active view that is about to be hidden var target:DisplayObjectContainer = event.target as DisplayObjectContainer; var lastFocusDO:DisplayObject = lastFocus as DisplayObject; // If the lastFocus is in the view about to be hidden, clear focus if (target && lastFocusDO && target.contains(lastFocusDO)) lastFocus = null; } /** * @private */ private function creationCompleteHandler(event:FlexEvent):void { var o:DisplayObject = DisplayObject(form); if (o.parent && o.visible && !activated) { var awm:IActiveWindowManager = IActiveWindowManager(form.systemManager.getImplementation("mx.managers::IActiveWindowManager")); if (awm) awm.activate(form); // build a message that does the equal } } /** * @private * Add or remove if tabbing properties change. */ private function tabIndexChangeHandler(event:Event):void { calculateCandidates = true; } /** * @private * Add or remove if tabbing properties change. */ private function tabFocusEnabledChangeHandler(event:Event):void { calculateCandidates = true; var o:IFocusManagerComponent = IFocusManagerComponent(event.target); var n:int = focusableObjects.length; for (var i:int = 0; i < n; i++) { if (focusableObjects[i] == o) break; } if (o.tabFocusEnabled) { if (i == n && isTabVisible(DisplayObject(o))) { // trace("FM tpc added " + o); // add it if were not already if (focusableObjects.indexOf(o) == -1) focusableObjects.push(o); } } else { // remove it if (i < n) { // trace("FM tpc removed " + o); focusableObjects.splice(i, 1); } } } /** * @private * Add or remove if tabbing properties change. */ private function tabChildrenChangeHandler(event:Event):void { if (event.target != event.currentTarget) return; calculateCandidates = true; var o:DisplayObjectContainer = DisplayObjectContainer(event.target); if (o.tabChildren) { addFocusables(o, true); } else { removeFocusables(o, true); } } /** * @private * Add or remove if tabbing properties change. */ private function hasFocusableChildrenChangeHandler(event:Event):void { if (event.target != event.currentTarget) return; calculateCandidates = true; var o:IFocusManagerComponent = IFocusManagerComponent(event.target); if (o.hasFocusableChildren) { addFocusables(DisplayObject(o), true); } else { removeFocusables(DisplayObject(o), true); } } /** * @private * This gets called when mouse clicks on a focusable object. * We block player behavior */ private function mouseFocusChangeHandler(event:FocusEvent):void { // trace("FocusManager: mouseFocusChangeHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FocusManager: mouseFocusChangeHandler " + event); if (event.isDefaultPrevented()) return; // If relatedObject is null because we don't have access to the // object getting focus then allow the Player to set focus // to the object. The isRelatedObjectInaccessible property is // Player 10 only so we have to test if it is available. We // will only see isRelatedObjectInaccessible if we are a version "10" swf // (-target-player=10). Version "9" swfs will not see the property // even if running in Player 10. if (event.relatedObject == null && "isRelatedObjectInaccessible" in event && event["isRelatedObjectInaccessible"] == true) { // lost focus to a control in different sandbox. return; } if (event.relatedObject is TextField) { var tf:TextField = event.relatedObject as TextField; if (tf.type == "input" || tf.selectable) { return; // pass it on } } event.preventDefault(); } /** * @private * This gets called when the tab key is hit. */ mx_internal function keyFocusChangeHandler(event:FocusEvent):void { // trace("keyFocusChangeHandler handled by " + this); // trace("keyFocusChangeHandler event = " + event); var sm:ISystemManager = form.systemManager; if (hasEventListener("keyFocusChange")) if (!dispatchEvent(new FocusEvent("keyFocusChange", false, true, InteractiveObject(event.target)))) return; showFocusIndicator = true; focusChanged = false; var haveBrowserFocusComponent:Boolean = (browserFocusComponent != null); if (browserFocusComponent) clearBrowserFocusComponent(); // see if we got here from a tab. We also need to check for // keyCode == 0 because in IE sometimes the first time you tab // in to the flash player, you get keyCode == 0 instead of TAB. // Flash Player bug #2295688. if ((event.keyCode == Keyboard.TAB || (browserMode && event.keyCode == 0)) && !event.isDefaultPrevented()) { if (haveBrowserFocusComponent) { if (hasEventListener("browserFocusComponent")) dispatchEvent(new FocusEvent("browserFocusComponent", false, false, InteractiveObject(event.target))); return; } // trace("tabHandled by " + this); setFocusToNextObject(event); // if we changed focus or if we're the main app // eat the event if (focusChanged || sm == sm.getTopLevelRoot()) event.preventDefault(); } } /** * @private * Watch for TAB keys. */ mx_internal function keyDownHandler(event:KeyboardEvent):void { // trace("onKeyDown handled by " + this); // trace("onKeyDown event = " + event); // if the target is in a bridged application, let it handle the click. var sm:ISystemManager = form.systemManager; if (hasEventListener("keyDownFM")) if (!dispatchEvent(new FocusEvent("keyDownFM", false, true, InteractiveObject(event.target)))) return; if (sm is SystemManager) SystemManager(sm).idleCounter = 0; if (event.keyCode == Keyboard.TAB) { lastAction = "KEY"; // I think we'll have time to do this here instead of at creation time // this makes and orders the focusableCandidates array if (calculateCandidates) { sortFocusableObjects(); calculateCandidates = false; } } if (browserMode) { if (browserFocusComponent) clearBrowserFocusComponent(); if (event.keyCode == Keyboard.TAB && focusableCandidates.length > 0) { // get the object that has the focus var o:DisplayObject = fauxFocus; if (!o) { o = form.systemManager.stage.focus; } // trace("focus was at " + o); // trace("focusableObjects " + focusableObjects.length); o = DisplayObject(findFocusManagerComponent2(InteractiveObject(o))); var g:String = ""; if (o is IFocusManagerGroup) { var tg:IFocusManagerGroup = IFocusManagerGroup(o); g = tg.groupName; } var i:int = getIndexOfFocusedObject(o); var j:int = getIndexOfNextObject(i, event.shiftKey, false, g); if (event.shiftKey) { if (j >= i) { // we wrapped so let browser have it browserFocusComponent = getBrowserFocusComponent(event.shiftKey); if (browserFocusComponent.tabIndex == -1) browserFocusComponent.tabIndex = 0; } } else { if (j <= i) { // we wrapped so let browser have it browserFocusComponent = getBrowserFocusComponent(event.shiftKey); if (browserFocusComponent.tabIndex == -1) browserFocusComponent.tabIndex = LARGE_TAB_INDEX; } } } } } /** * @private * Watch for ENTER key. */ private function defaultButtonKeyHandler(event:KeyboardEvent):void { var sm:ISystemManager = form.systemManager; if (hasEventListener("defaultButtonKeyHandler")) if (!dispatchEvent(new FocusEvent("defaultButtonKeyHandler", false, true))) return; if (defaultButtonEnabled && event.keyCode == Keyboard.ENTER && defaultButton && defButton.enabled) { sendDefaultButtonEvent(); } } /** * @private * This gets called when the focus changes due to a mouse click. * * Note: If the focus is changing to a TextField, we don't call * setFocus() on it because the player handles it; * calling setFocus() on a TextField which has scrollable text * causes the text to autoscroll to the end, making the * mouse click set the insertion point in the wrong place. */ private function mouseDownCaptureHandler(event:MouseEvent):void { // trace("FocusManager mouseDownCaptureHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FocusManager mouseDownCaptureHandler target " + event.target); showFocusIndicator = false; } /** * @private * This gets called when the focus changes due to a mouse click. * * Note: If the focus is changing to a TextField, we don't call * setFocus() on it because the player handles it; * calling setFocus() on a TextField which has scrollable text * causes the text to autoscroll to the end, making the * mouse click set the insertion point in the wrong place. */ private function mouseDownHandler(event:MouseEvent):void { // trace("FocusManager mouseDownHandler in = " + this._form.systemManager.loaderInfo.url); // trace("FocusManager mouseDownHandler target " + event.target); // if the target is in a bridged application, let it handle the click. var sm:ISystemManager = form.systemManager; var o:DisplayObject = getTopLevelFocusTarget( InteractiveObject(event.target)); if (!o) return; // trace("FocusManager mouseDownHandler on " + o); // Make sure the containing component gets notified. // As the note above says, we don't set focus to a TextField ever // because the player already did and took care of where // the insertion point is, and we also don't call setfocus // on a component that last the last focused object unless // the last action was just to activate the player and didn't // involve tabbing or clicking on a component if ((o != _lastFocus || lastAction == "ACTIVATE") && !(o is TextField)) setFocus(IFocusManagerComponent(o)); else if (_lastFocus) { // trace("FM: skipped setting focus to " + _lastFocus); } if (hasEventListener("mouseDownFM")) dispatchEvent(new FocusEvent("mouseDownFM", false, false, InteractiveObject(o))); lastAction = "MOUSEDOWN"; } private function getBrowserFocusComponent(shiftKey:Boolean):InteractiveObject { var focusComponent:InteractiveObject = form.systemManager.stage.focus; // if the focus is null it means focus is in an application we // don't have access to. Use either the last object or the first // object in this focus manager's list. if (!focusComponent) { var index:int = shiftKey ? 0 : focusableCandidates.length - 1; focusComponent = focusableCandidates[index]; } return focusComponent; } } } import flash.display.DisplayObject; /** * @private * * Plain old class to return multiple items of info about the potential * change in focus. */ class FocusInfo { public var displayObject:DisplayObject; // object to get focus public var wrapped:Boolean; // true if focus wrapped around }