//////////////////////////////////////////////////////////////////////////////// // // 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 Iftrue
, 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 * theactivate()
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("<".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
}