////////////////////////////////////////////////////////////////////////////////
//
// 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.events.ContextMenuEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.geom.Point;
import flash.system.ApplicationDomain;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.ui.Mouse;
import mx.core.EventPriority;
import mx.core.FlexGlobals;
import mx.core.FlexSprite;
import mx.core.mx_internal;
import mx.core.ISystemCursorClient;
import mx.core.IUIComponent;
import mx.events.Request;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
import mx.core.IFlexModuleFactory;
use namespace mx_internal;
[ExcludeClass]
/**
* @private
*/
public class CursorManagerImpl extends EventDispatcher implements ICursorManager
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static var instance:ICursorManager;
/**
* @private
*
* Place to hook in additional classes
*/
public static var mixins:Array;
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
public static function getInstance():ICursorManager
{
if (!instance)
instance = new CursorManagerImpl();
return instance;
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function CursorManagerImpl(systemManager:ISystemManager = null)
{
super();
if (instance && !systemManager)
throw new Error("Instance already exists.");
if (systemManager)
this.systemManager = systemManager as ISystemManager;
else
this.systemManager = SystemManagerGlobals.topLevelSystemManagers[0] as ISystemManager;
if (mixins)
{
var n:int = mixins.length;
for (var i:int = 0; i < n; i++)
{
new mixins[i](this);
}
}
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var nextCursorID:int = 1;
/**
* @private
*/
private var cursorList:Array = [];
/**
* @private
*/
private var busyCursorList:Array = [];
/**
* @private
*/
mx_internal var initialized:Boolean = false;
/**
* @private
*/
mx_internal var cursorHolder:Sprite;
/**
* @private
*/
private var currentCursor:DisplayObject;
/**
* @private
*/
private var listenForContextMenu:Boolean = false;
/*******************************************************************
* Regarding overTextField, showSystemCursor, and showCustomCursor:
* Don't modify or read these variables unless you are certain
* you will not create race conditions. E.g. you may get the
* wrong (or no) cursor, and get stuck in an inconsistent state.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
/**
* @private
*/
private var overTextField:Boolean = false;
/**
* @private
*/
private var overLink:Boolean = false;
/**
* @private
*/
private var showSystemCursor:Boolean = false;
/**
* @private
*/
private var showCustomCursor:Boolean = false;
/**
* @private
*
* State variable -- set when there is a custom cursor and the
* mouse has left the stage. Upon return, mouseMoveHandler will
* restore the custom cursor and remove the system cursor.
*/
private var customCursorLeftStage:Boolean = false;
/*******************************************************************/
/**
* @private
*/
mx_internal var systemManager:ISystemManager = null;
/**
* @private
*/
mx_internal var sandboxRoot:IEventDispatcher = null;
/**
* @private
*/
private var sourceArray:Array = [];
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// currentCursorID
//----------------------------------
/**
* @private
*/
mx_internal var _currentCursorID:int = 0 /* CursorManager.NO_CURSOR */;
/**
* ID of the current custom cursor,
* or CursorManager.NO_CURSOR if the system cursor is showing.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get currentCursorID():int
{
return _currentCursorID;
}
/**
* @private
*/
public function set currentCursorID(value:int):void
{
_currentCursorID = value;
if (hasEventListener("currentCursorID"))
dispatchEvent(new Event("currentCursorID"));
}
//----------------------------------
// currentCursorXOffset
//----------------------------------
/**
* @private
*/
mx_internal var _currentCursorXOffset:Number = 0;
/**
* The x offset of the custom cursor, in pixels,
* relative to the mouse pointer.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get currentCursorXOffset():Number
{
return _currentCursorXOffset;
}
/**
* @private
*/
public function set currentCursorXOffset(value:Number):void
{
_currentCursorXOffset = value;
if (hasEventListener("currentCursorXOffset"))
dispatchEvent(new Event("currentCursorXOffset"));
}
//----------------------------------
// currentCursorYOffset
//----------------------------------
/**
* @private
*/
mx_internal var _currentCursorYOffset:Number = 0;
/**
* The y offset of the custom cursor, in pixels,
* relative to the mouse pointer.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get currentCursorYOffset():Number
{
return _currentCursorYOffset;
}
/**
* @private
*/
public function set currentCursorYOffset(value:Number):void
{
_currentCursorYOffset = value;
if (hasEventListener("currentCursorYOffset"))
dispatchEvent(new Event("currentCursorYOffset"));
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Makes the cursor visible.
* Cursor visibility is not reference-counted.
* A single call to the showCursor()
method
* always shows the cursor regardless of how many calls
* to the hideCursor()
method were made.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function showCursor():void
{
if (cursorHolder)
cursorHolder.visible = true;
if (hasEventListener("showCursor"))
dispatchEvent(new Event("showCursor"));
}
/**
* Makes the cursor invisible.
* Cursor visibility is not reference-counted.
* A single call to the hideCursor()
method
* always hides the cursor regardless of how many calls
* to the showCursor()
method were made.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function hideCursor():void
{
if (cursorHolder)
cursorHolder.visible = false;
if (hasEventListener("hideCursor"))
dispatchEvent(new Event("hideCursor"));
}
/**
* Creates a new cursor and sets an optional priority for the cursor.
* Adds the new cursor to the cursor list.
*
* @param cursorClass Class of the cursor to display.
*
* @param priority Integer that specifies
* the priority level of the cursor.
* Possible values are CursorManagerPriority.HIGH
,
* CursorManagerPriority.MEDIUM
, and CursorManagerPriority.LOW
.
*
* @param xOffset Number that specifies the x offset
* of the cursor, in pixels, relative to the mouse pointer.
*
* @param yOffset Number that specifies the y offset
* of the cursor, in pixels, relative to the mouse pointer.
*
* @param setter The IUIComponent that set the cursor. Necessary (in multi-window environments)
* to know which window needs to display the cursor.
*
* @return The ID of the cursor.
*
* @see mx.managers.CursorManagerPriority
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setCursor(cursorClass:Class, priority:int = 2,
xOffset:Number = 0,
yOffset:Number = 0):int
{
if (hasEventListener("setCursor"))
{
var event:Request = new Request("setCursor", false, true);
event.value = [ cursorClass, priority, xOffset, yOffset ];
if (!dispatchEvent(event))
{
return event.value as int;
}
}
var cursorID:int = nextCursorID++;
// Create a new CursorQueueItem.
var item:CursorQueueItem = new CursorQueueItem();
item.cursorID = cursorID;
item.cursorClass = cursorClass;
item.priority = priority;
item.x = xOffset;
item.y = yOffset;
if (systemManager)
item.systemManager = systemManager;
else
item.systemManager = FlexGlobals.topLevelApplication.systemManager;
// Push it onto the cursor list.
cursorList.push(item);
// Re-sort the cursor list based on priority level.
cursorList.sort(priorityCompare);
// Determine which cursor to display
showCurrentCursor();
return cursorID;
}
/**
* @private
*/
private function priorityCompare(a:CursorQueueItem, b:CursorQueueItem):int
{
if (a.priority < b.priority)
return -1;
else if (a.priority == b.priority)
return 0;
return 1;
}
/**
* Removes a cursor from the cursor list.
* If the cursor being removed is the currently displayed cursor,
* the CursorManager displays the next cursor in the list, if one exists.
* If the list becomes empty, the CursorManager displays
* the default system cursor.
*
* @param cursorID ID of cursor to remove.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeCursor(cursorID:int):void
{
if (hasEventListener("removeCursor"))
if (!dispatchEvent(new Request("removeCursor", false, true, cursorID)))
return;
for (var i:Object in cursorList)
{
var item:CursorQueueItem = cursorList[i];
if (item.cursorID == cursorID)
{
// Remove the element from the array.
cursorList.splice(i, 1);
// Determine which cursor to display.
showCurrentCursor();
break;
}
}
}
/**
* Removes all of the cursors from the cursor list
* and restores the system cursor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeAllCursors():void
{
if (hasEventListener("removeAllCursors"))
if (!dispatchEvent(new Event("removeAllCursors", false, true)))
return;
cursorList.splice(0);
showCurrentCursor();
}
/**
* Displays the busy cursor.
* The busy cursor has a priority of CursorManagerPriority.LOW.
* Therefore, if the cursor list contains a cursor
* with a higher priority, the busy cursor is not displayed
* until you remove the higher priority cursor.
* To create a busy cursor at a higher priority level,
* use the setCursor()
method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setBusyCursor():void
{
if (hasEventListener("setBusyCursor"))
if (!dispatchEvent(new Event("setBusyCursor", false, true)))
return;
var cursorManagerStyleDeclaration:CSSStyleDeclaration =
StyleManager.getStyleManager(systemManager as IFlexModuleFactory).
getMergedStyleDeclaration("mx.managers.CursorManager");
var busyCursorClass:Class =
cursorManagerStyleDeclaration.getStyle("busyCursor");
busyCursorList.push(setCursor(busyCursorClass, CursorManagerPriority.LOW));
}
/**
* Removes the busy cursor from the cursor list.
* If other busy cursor requests are still active in the cursor list,
* which means you called the setBusyCursor()
method more than once,
* a busy cursor does not disappear until you remove
* all busy cursors from the list.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeBusyCursor():void
{
if (hasEventListener("removeBusyCursor"))
if (!dispatchEvent(new Event("removeBusyCursor", false, true)))
return;
if (busyCursorList.length > 0)
removeCursor(int(busyCursorList.pop()));
}
/**
* @private
* Decides what cursor to display.
*/
private function showCurrentCursor():void
{
// if there are custom cursors...
if (cursorList.length > 0)
{
if (!initialized)
{
initialized = true;
var e:Event;
if (hasEventListener("initialize"))
{
e = new Event("initialize", false, true);
}
if (!e || dispatchEvent(e))
{
// The first time a cursor is requested of the CursorManager,
// create a Sprite to hold the cursor symbol
cursorHolder = new FlexSprite();
cursorHolder.name = "cursorHolder";
cursorHolder.mouseEnabled = false;
cursorHolder.mouseChildren = false;
systemManager.cursorChildren.addChild(cursorHolder);
}
}
// Get the top most cursor.
var item:CursorQueueItem = cursorList[0];
// If the system cursor was being displayed, hide it.
if (currentCursorID == CursorManager.NO_CURSOR)
Mouse.hide();
// If the current cursor has changed...
if (item.cursorID != currentCursorID)
{
if (cursorHolder.numChildren > 0)
cursorHolder.removeChildAt(0);
currentCursor = new item.cursorClass();
if (currentCursor)
{
if (currentCursor is InteractiveObject)
InteractiveObject(currentCursor).mouseEnabled = false;
if (currentCursor is DisplayObjectContainer)
DisplayObjectContainer(currentCursor).mouseChildren = false;
cursorHolder.addChild(currentCursor);
addContextMenuHandlers();
var pt:Point;
// make sure systemManager is not other implementation of ISystemManager
if (systemManager is SystemManager)
{
pt = new Point(SystemManager(systemManager).mouseX + item.x, SystemManager(systemManager).mouseY + item.y);
pt = SystemManager(systemManager).localToGlobal(pt);
pt = cursorHolder.parent.globalToLocal(pt);
cursorHolder.x = pt.x;
cursorHolder.y = pt.y;
}
// WindowedSystemManager
else if (systemManager is DisplayObject)
{
pt = new Point(DisplayObject(systemManager).mouseX + item.x, DisplayObject(systemManager).mouseY + item.y);
pt = DisplayObject(systemManager).localToGlobal(pt);
pt = cursorHolder.parent.globalToLocal(pt);
cursorHolder.x = DisplayObject(systemManager).mouseX + item.x;
cursorHolder.y = DisplayObject(systemManager).mouseY + item.y;
}
// otherwise
else
{
cursorHolder.x = item.x;
cursorHolder.y = item.y;
}
var e2:Event;
if (hasEventListener("addMouseMoveListener"))
e2 = new Event("addMouseMoveListener", false, true);
if (!e2 || dispatchEvent(e2))
systemManager.stage.addEventListener(MouseEvent.MOUSE_MOVE,
mouseMoveHandler,true,EventPriority.CURSOR_MANAGEMENT);
var e3:Event;
if (hasEventListener("addMouseOutListener"))
e3 = new Event("addMouseOutListener", false, true);
if (!e3 || dispatchEvent(e3))
systemManager.stage.addEventListener(MouseEvent.MOUSE_OUT,
mouseOutHandler,true,EventPriority.CURSOR_MANAGEMENT);
}
currentCursorID = item.cursorID;
currentCursorXOffset = item.x;
currentCursorYOffset = item.y;
}
}
else
{
showCustomCursor = false;
if (currentCursorID != CursorManager.NO_CURSOR)
{
// There is no cursor in the cursor list to display,
// so cleanup and restore the system cursor.
currentCursorID = CursorManager.NO_CURSOR;
currentCursorXOffset = 0;
currentCursorYOffset = 0;
var e4:Event;
if (hasEventListener("removeMouseMoveListener"))
e4 = new Event("removeMouseMoveListener", false, true)
if (!e4 || dispatchEvent(e4))
{
systemManager.stage.removeEventListener(MouseEvent.MOUSE_MOVE,
mouseMoveHandler,true);
}
var e5:Event;
if (hasEventListener("removeMouseMoveListener"))
e5 = new Event("removeMouseOutListener", false, true)
if (!e5 || dispatchEvent(e5))
{
systemManager.stage.removeEventListener(MouseEvent.MOUSE_OUT,
mouseOutHandler,true);
}
cursorHolder.removeChild(currentCursor);
removeContextMenuHandlers();
}
Mouse.show();
}
}
/**
* @private
*/
private function addContextMenuHandlers():void
{
if (!listenForContextMenu)
{
const app:InteractiveObject = systemManager.document as InteractiveObject;
const sm:InteractiveObject = systemManager as InteractiveObject;
if (app && app.contextMenu)
{
app.contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_menuSelectHandler,
true, EventPriority.CURSOR_MANAGEMENT);
listenForContextMenu = true;
}
if (sm && sm.contextMenu)
{
sm.contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_menuSelectHandler,
true, EventPriority.CURSOR_MANAGEMENT);
listenForContextMenu = true;
}
}
}
/**
* @private
*/
private function removeContextMenuHandlers():void
{
if (listenForContextMenu)
{
const app:InteractiveObject = systemManager.document as InteractiveObject;
const sm:InteractiveObject = systemManager as InteractiveObject;
if (app && app.contextMenu)
app.contextMenu.removeEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_menuSelectHandler, true);
if (sm && sm.contextMenu)
sm.contextMenu.removeEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_menuSelectHandler, true);
listenForContextMenu = false;
}
}
/**
* @private
* Called by other components if they want to display
* the busy cursor during progress events.
*/
public function registerToUseBusyCursor(source:Object):void
{
if (hasEventListener("registerToUseBusyCursor"))
if (!dispatchEvent(new Request("registerToUseBusyCursor", false, true, source)))
return;
if (source && source is EventDispatcher)
{
source.addEventListener(ProgressEvent.PROGRESS, progressHandler);
source.addEventListener(Event.COMPLETE, completeHandler);
source.addEventListener(IOErrorEvent.IO_ERROR, completeHandler);
}
}
/**
* @private
* Called by other components to unregister
* a busy cursor from the progress events.
*/
public function unRegisterToUseBusyCursor(source:Object):void
{
if (hasEventListener("unRegisterToUseBusyCursor"))
if (!dispatchEvent(new Request("unRegisterToUseBusyCursor", false, true, source)))
return;
if (source && source is EventDispatcher)
{
source.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
source.removeEventListener(Event.COMPLETE, completeHandler);
source.removeEventListener(IOErrorEvent.IO_ERROR, completeHandler);
}
}
/**
* @private
* Called when contextMenu is opened
*/
private function contextMenu_menuSelectHandler(event:ContextMenuEvent):void
{
showCustomCursor = true; // Restore the custom cursor
// Standalone player doesn't initially send mouseMove when the contextMenu is closed,
// so we need to listen for mouseOver as well.
sandboxRoot.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
}
/**
* @private
*/
private function mouseOverHandler(event:MouseEvent):void
{
sandboxRoot.removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
mouseMoveHandler(event);
}
/**
* @private
*/
private function findSource(target:Object):int
{
var n:int = sourceArray.length;
for (var i:int = 0; i < n; i++)
{
if (sourceArray[i] === target)
return i;
}
return -1;
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*
* Handles the mouse leaving the stage; hides the custom cursor and restores the system cursor.
*/
mx_internal function mouseOutHandler(event:MouseEvent):void
{
// relatedObject==null implies the mouse left the stage.
// this also fires when you are returning from a context menu click.
//
// it sometimes fires after you drag off the stage, and back to the stage quickly,
// and let go of the button -- this seems like a player bug
if ((event.relatedObject == null) && (cursorList.length > 0))
{
//trace("mouseOutHandler", event);
// this will get unset in mouseMoveHandler (since that fires when
// the mouse returns/glides over the stage)
customCursorLeftStage = true;
hideCursor();
Mouse.show();
}
}
/**
* @private
*/
mx_internal function mouseMoveHandler(event:MouseEvent):void
{
var pt:Point = new Point(event.stageX, event.stageY);
pt = cursorHolder.parent.globalToLocal(pt);
pt.x += currentCursorXOffset;
pt.y += currentCursorYOffset;
cursorHolder.x = pt.x;
cursorHolder.y = pt.y;
var target:Object = event.target;
var isInputTextField:Boolean =
(target is TextField && target.type == TextFieldType.INPUT) ||
(target is ISystemCursorClient &&
ISystemCursorClient(target).showSystemCursor);
// Do target test.
if (!overTextField && isInputTextField)
{
overTextField = true;
showSystemCursor = true;
}
else if (overTextField && !isInputTextField)
{
overTextField = false;
showCustomCursor = true;
}
else
{
showCustomCursor = true
}
// Handle switching between system and custom cursor.
if (showSystemCursor)
{
showSystemCursor = false;
cursorHolder.visible = false;
Mouse.show();
}
if (showCustomCursor)
{
showCustomCursor = false;
cursorHolder.visible = true;
Mouse.hide();
if (hasEventListener("showCustomCursor"))
dispatchEvent(new Event("showCustomCursor"));
}
}
/**
* @private
* Displays the busy cursor if a component is in a busy state.
*/
private function progressHandler(event:ProgressEvent):void
{
// Only pay attention to the first progress call. Ignore all others.
var sourceIndex:int = findSource(event.target);
if (sourceIndex == -1)
{
// Add the target to the list of objects we are listening for.
sourceArray.push(event.target);
setBusyCursor();
}
}
/**
* @private
*/
private function completeHandler(event:Event):void
{
var sourceIndex:int = findSource(event.target);
if (sourceIndex != -1)
{
// Remove from the list of targets we are listening to.
sourceArray.splice(sourceIndex, 1);
removeBusyCursor();
}
}
}
}
import mx.managers.CursorManager;
import mx.managers.CursorManagerPriority;
import mx.managers.ISystemManager;
////////////////////////////////////////////////////////////////////////////////
//
// Helper class: CursorQueueItem
//
////////////////////////////////////////////////////////////////////////////////
/**
* @private
*/
class CursorQueueItem
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function CursorQueueItem()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* @private
*/
public var cursorID:int = CursorManager.NO_CURSOR;
/**
* @private
*/
public var cursorClass:Class = null;
/**
* @private
*/
public var priority:int = CursorManagerPriority.MEDIUM;
/**
* @private
*/
public var systemManager:ISystemManager;
/**
* @private
*/
public var x:Number;
/**
* @private
*/
public var y:Number;
}