blob: a9d4c1f6964f1d432d25e18e13504f45d0fb67ed [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package 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;
import mx.utils.Platform;
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.
*
* <p>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.</p>
*
* <p>All components that can be managed by the FocusManager must implement
* mx.managers.IFocusManagerComponent, whereas objects managed by player-level focus do not.</p>
*
* <p>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.</p>
*
* @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;
// flag to turn on/off some ie specific behavior
mx_internal static var ieshifttab:Boolean = true;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* <p>A FocusManager manages the focus within the children of an IFocusManagerContainer.
* It installs itself in the IFocusManagerContainer during execution
* of the constructor.</p>
*
* @param container An IFocusManagerContainer that hosts the FocusManager.
*
* @param popup If <code>true</code>, 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;
// Only <= IE8 supported focus cycling out of the SWF
browserMode = Capabilities.playerType == "ActiveX" && !popup;
desktopMode = Platform.isAir && !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 || ieshifttab))
_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 <code>activate()</code> method on that FocusManager.
* The FocusManager that was activated will have its <code>deactivate()</code> method
* called prior to the activation of another FocusManager.
*
* <p>The FocusManager adds event handlers that allow it to monitor
* focus related keyboard and mouse activity.</p>
*
* @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 <code>activate()</code> method on that FocusManager.
* The FocusManager that was activated will have its <code>deactivate()</code> method
* called prior to the activation of another FocusManager.
*
* <p>The FocusManager removes event handlers that allow it to monitor
* focus related keyboard and mouse activity.</p>
*
* @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("<<addFocusables " + o);
}
/**
* @private
* is it really tabbable?
*/
private function isTabVisible(o:DisplayObject):Boolean
{
var s:DisplayObject = DisplayObject(form.systemManager);
if (!s) return false;
var p:DisplayObjectContainer = o.parent;
while (p && p != s)
{
if (!p.tabChildren)
return false;
if (p is IFocusManagerComponent && !(IFocusManagerComponent(p).hasFocusableChildren))
return false;
p = p.parent;
}
return true;
}
private function isValidFocusCandidate(o:DisplayObject, g:String):Boolean
{
if (o is IFocusManagerComponent)
if (!IFocusManagerComponent(o).focusEnabled)
return false;
if (!isEnabledAndVisible(o))
return false;
if (o is IFocusManagerGroup)
{
// reject if it is in the same tabgroup
var tg:IFocusManagerGroup = IFocusManagerGroup(o);
if (g == tg.groupName) return false;
}
return true;
}
private function getIndexOfFocusedObject(o:DisplayObject):int
{
if (!o)
return -1;
var n:int = focusableCandidates.length;
// trace(" focusableCandidates " + n);
var i:int = 0;
for (i = 0; i < n; i++)
{
// trace(" comparing " + focusableCandidates[i]);
if (focusableCandidates[i] == o)
return i;
}
// no match? try again with a slower match for certain
// cases like DG editors
for (i = 0; i < n; i++)
{
var iui:IUIComponent = focusableCandidates[i] as IUIComponent;
if (iui && iui.owns(o))
return i;
}
return -1;
}
private function getIndexOfNextObject(i:int, shiftKey:Boolean, bSearchAll:Boolean, groupName:String):int
{
var n:int = focusableCandidates.length;
var start:int = i;
while (true)
{
if (shiftKey)
i--;
else
i++;
if (bSearchAll)
{
if (shiftKey && i < 0)
break;
if (!shiftKey && i == n)
break;
}
else
{
i = (i + n) % n;
// came around and found the original
if (start == i)
break;
// if start is -1, set start to first valid value of i
if (start == -1)
start = i;
}
// trace("testing " + focusableCandidates[i]);
if (isValidFocusCandidate(focusableCandidates[i], groupName))
{
// trace(" stopped at " + i);
var o:DisplayObject = DisplayObject(findFocusManagerComponent2(focusableCandidates[i]));
if (o is IFocusManagerGroup)
{
// when landing on an element that is part of group, try to
// advance selection to the selected group element
var j:int;
var obj:DisplayObject;
var tg1:IFocusManagerGroup = IFocusManagerGroup(o);
var tg2:IFocusManagerGroup;
// normalize the "no selected group element" case
// to the "first group element selected" case
// (respecting the tab direction)
var groupElementToFocus:IFocusManagerGroup = null;
for (j = 0; j < focusableCandidates.length; j++)
{
obj = focusableCandidates[j];
if (obj is IFocusManagerGroup)
{
tg2 = IFocusManagerGroup(obj);
if (tg2.groupName == tg1.groupName && isEnabledAndVisible(obj) &&
tg2["document"] == tg1["document"])
{
if (tg2.selected)
{
groupElementToFocus = tg2;
break;
}
if ((!shiftKey && groupElementToFocus == null) || shiftKey)
groupElementToFocus = tg2;
}
}
}
if (tg1 != groupElementToFocus)
{
var foundAnotherGroup:Boolean = false;
// cycle the entire focusable candidates array forward or backward,
// wrapping around boundaries, searching for our focus candidate
j = i;
for (var k:int = 0; k < focusableCandidates.length - 1; k++)
{
if (!shiftKey)
{
j++;
if (j == focusableCandidates.length)
j = 0;
}
else
{
j--;
if (j == -1)
j = focusableCandidates.length - 1;
}
obj = focusableCandidates[j];
if (isEnabledAndVisible(obj))
{
if (foundAnotherGroup)
{
// we're now just trying to find a selected member of this group
if (obj is IFocusManagerGroup)
{
tg2 = IFocusManagerGroup(obj);
if (tg2.groupName == tg1.groupName && tg2["document"] == tg1["document"])
{
if (tg2.selected)
{
i = j;
break;
}
}
}
}
else if (obj is IFocusManagerGroup)
{
tg2 = IFocusManagerGroup(obj);
if (tg2.groupName == tg1.groupName && tg2["document"] == tg1["document"])
{
if (tg2 == groupElementToFocus)
{
// if objects of same group have different tab index
// skip you aren't selected.
if (InteractiveObject(obj).tabIndex != InteractiveObject(o).tabIndex && !tg1.selected)
return getIndexOfNextObject(i, shiftKey, bSearchAll, groupName);
i = j;
break;
}
}
else
{
// switch to new group and hunt for selected item
tg1 = tg2;
i = j;
// element is part of another group, stop if selected
if (tg2.selected)
break;
else
foundAnotherGroup = true;
}
}
else
{
// element isn't part of any group, stop
i = j;
break;
}
}
}
}
}
return i;
}
}
return i;
}
/**
* @private
*/
private function setFocusToNextObject(event:FocusEvent):void
{
focusChanged = false;
if (focusableObjects.length == 0)
return;
var focusInfo:FocusInfo = getNextFocusManagerComponent2(event.shiftKey, fauxFocus);
// trace("winner = ", focusInfo.displayObject);
// If we are about to wrap focus around, send focus back to the parent.
if (!popup && (focusInfo.wrapped || !focusInfo.displayObject))
{
if (hasEventListener("focusWrapping"))
if (!dispatchEvent(new FocusEvent("focusWrapping", false, true, null, event.shiftKey)))
return;
}
if (!focusInfo.displayObject)
{
event.preventDefault();
return;
}
setFocusToComponent(focusInfo.displayObject, event.shiftKey);
}
private function setFocusToComponent(o:Object, shiftKey:Boolean):void
{
focusChanged = false;
if (o)
{
if (hasEventListener("setFocusToComponent"))
if (!dispatchEvent(new FocusEvent("setFocusToComponent", false, true, InteractiveObject(o), shiftKey)))
return;
if (o is IFocusManagerComplexComponent)
{
IFocusManagerComplexComponent(o).assignFocus(shiftKey ? "bottom" : "top");
focusChanged = true;
}
else if (o is IFocusManagerComponent)
{
setFocus(IFocusManagerComponent(o));
focusChanged = true;
}
}
}
/**
* @private
*/
mx_internal function setFocusToNextIndex(index:int, shiftKey:Boolean):void
{
if (focusableObjects.length == 0)
return;
// 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;
}
var focusInfo:FocusInfo = getNextFocusManagerComponent2(shiftKey, null, index);
// If we are about to wrap focus around, send focus back to the parent.
if (!popup && focusInfo.wrapped)
{
if (hasEventListener("setFocusToNextIndex"))
if (!dispatchEvent(new FocusEvent("setFocusToNextIndex", false, true, null, shiftKey)))
return;
}
setFocusToComponent(focusInfo.displayObject, shiftKey);
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function getNextFocusManagerComponent(
backward:Boolean = false):IFocusManagerComponent
{
const focusInfo:FocusInfo = getNextFocusManagerComponent2(backward, fauxFocus);
return focusInfo ? focusInfo.displayObject as IFocusManagerComponent : null;
}
/**
* Find the next object to set focus to.
*
* @param backward true if moving in the backwards in the tab order, false if moving forward.
* @param fromObject object to move focus from, if null move from the current focus.
* @param formIndex index to move focus from, if specified use fromIndex to find the
* object, not fromObject.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private function getNextFocusManagerComponent2(
backward:Boolean = false,
fromObject:DisplayObject = null,
fromIndex:int = FROM_INDEX_UNSPECIFIED):FocusInfo
{
if (focusableObjects.length == 0)
return null;
// 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;
}
// trace("focus was at " + fromObject);
// trace("focusableObjects " + focusableObjects.length);
var i:int = fromIndex;
if (fromIndex == FROM_INDEX_UNSPECIFIED)
{
// if there is no passed in object, then get the object that has the focus
var o:DisplayObject = fromObject;
if (!o)
o = form.systemManager.stage.focus;
else if (o == form.systemManager.stage)
o == null;
o = DisplayObject(findFocusManagerComponent2(InteractiveObject(o)));
var g:String = "";
if (o is IFocusManagerGroup)
{
var tg:IFocusManagerGroup = IFocusManagerGroup(o);
g = tg.groupName;
}
i = getIndexOfFocusedObject(o);
}
// trace(" starting at " + i);
var bSearchAll:Boolean = false;
var start:int = i;
if (i == -1) // we didn't find it
{
if (backward)
i = focusableCandidates.length;
bSearchAll = true;
// trace("search all " + i);
}
var j:int = getIndexOfNextObject(i, backward, bSearchAll, g);
// if we wrapped around, get if we have a parent we should pass
// focus to.
var wrapped:Boolean = false;
if (backward)
{
if (j >= 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 <code>".focusManager"</code> appended to the end of the String.
*
* @return Returns a String representation of the component hosting the FocusManager object,
* with the String <code>".focusManager"</code> 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;
}
if (ieshifttab && lastAction == "ACTIVATE")
{
// IE seems to now require that we set focus to something during activate
// but then we get this keyFocusChange event. I think we used to not
// need to set focus on activate and we still got the keyFocusChange
// and then stage.focus was null and we'd use the keyFocusChange event
// to determine which control (first or last) got focus based on
// the shift key.
// If we set focus on activate, then we get this keyFocusChange which moves
// the focus somewhere else, so we set fauxFocus to the stage as a signal
// to the setFocusToNextObject logic that it shouldn't use the stage.focus
// as the starting point.
fauxFocus = sm.stage;
}
// trace("tabHandled by " + this);
setFocusToNextObject(event);
if (ieshifttab && lastAction == "ACTIVATE")
{
fauxFocus = null;
}
// 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 &&
defButton && 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
}