////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2006-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.managers
{

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Timer;

import mx.controls.ToolTip;
import mx.core.FlexGlobals;
import mx.core.FlexVersion;
import mx.core.IFlexModule;
import mx.core.IInvalidating;
import mx.core.ILayoutDirectionElement;
import mx.core.IToolTip;
import mx.core.IUIComponent;
import mx.core.LayoutDirection;
import mx.core.mx_internal;
import mx.effects.EffectManager;
import mx.effects.IAbstractEffect;
import mx.events.DynamicEvent;
import mx.events.EffectEvent;
import mx.events.ToolTipEvent;
import mx.managers.IToolTipManagerClient;
import mx.styles.IStyleClient;
import mx.validators.IValidatorListener;

use namespace mx_internal;

[ExcludeClass]

/**
 *  @private
 *  The ToolTipManager lets you set basic ToolTip and error tip functionality,
 *  such as display delay and the disabling of ToolTips.
 *
 *  @see mx.controls.ToolTip
 *  @see mx.validators.Validator
 */
public class ToolTipManagerImpl extends EventDispatcher
             implements IToolTipManager2
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private static var instance:IToolTipManager2;

	/**
	 * @private
	 * 
	 * Place to hook in additional classes
	 */
	public static var mixins:Array;

    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private
     */
    public static function getInstance():IToolTipManager2
    {
        if (!instance)
            instance = new ToolTipManagerImpl();

        return instance;
    }

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public function ToolTipManagerImpl()
    {
        super();
        
        if (instance)
            throw new Error("Instance already exists.");

		if (mixins)
		{
			var n:int = mixins.length;
			for (var i:int = 0; i < n; i++)
			{
				new mixins[i](this);
			}
		}

        if (hasEventListener("initialize"))
    		dispatchEvent(new Event("initialize"));

    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  A flag that keeps track of whether this class's initialize()
     *  method has been executed.
     */
    mx_internal var initialized:Boolean = false;

    /**
     *  @private
     *  This timer is used to delay the appearance of a normal ToolTip
     *  after the mouse moves over a target; an error tip has no such delay.
     *
     *  <p>This timer, which is lazily created, is started when the mouse
     *  moves over an object with a ToolTip, with a duration specified
     *  by showDelay.
     *  If the mouse moves out of this object before the timer fires,
     *  the ToolTip is never created.
     *  If the mouse stays over the object until the timer fires,
     *  the ToolTip is created and its showEffect is started.
     */
    mx_internal var showTimer:Timer;

    /**
     *  @private
     *  This timer is used to make the tooltip "time out" and hide itself
     *  if the mouse stays over a target.
     *
     *  <p>This timer, which is lazily created, is started
     *  when the showEffect ends.
     *  When it fires, the hideEffect is started.</p>
     */
    mx_internal var hideTimer:Timer;

    /**
     *  @private
     *  This timer is used to implement mousing quickly over multiple targets
     *  with ToolTip...
     *
     *  <p>This timer, which is lazily created, is started
     *  when ...</p>
     */
    mx_internal var scrubTimer:Timer;

    /**
     *  @private
     */
    mx_internal var currentText:String;

    /**
     *  @private
     */
    mx_internal var isError:Boolean;

    /**
     *  The UIComponent with the ToolTip assigned to it
     *  that was most recently under the mouse.
     *  During much of the tool tip life cycle this property
     *  has the same value as the <code>currentTarget</code> property.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var previousTarget:DisplayObject;

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  currentTarget
    //----------------------------------

    /**
     *  @private
     */
    private var _currentTarget:DisplayObject;

    /**
     *  The UIComponent that is currently displaying a ToolTip,
     *  or <code>null</code> if none is.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get currentTarget():DisplayObject
    {
        return _currentTarget;
    }
    
    /**
     *  @private
     */
    public function set currentTarget(value:DisplayObject):void
    {
        _currentTarget = value;
    }

    //----------------------------------
    //  currentToolTip
    //----------------------------------

    /**
     *  @private
     */
    mx_internal var _currentToolTip:DisplayObject;

    /**
     *  The ToolTip object that is currently visible,
     *  or <code>null</code> if none is shown.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get currentToolTip():IToolTip
    {
        return _currentToolTip as IToolTip;
    }
    
    /**
     *  @private
     */
    public function set currentToolTip(value:IToolTip):void
    {
        _currentToolTip = value as DisplayObject;

        if (hasEventListener("currentToolTip"))
    		dispatchEvent(new Event("currentToolTip"));
    }

    //----------------------------------
    //  enabled
    //----------------------------------

    /**
     *  @private
     */
    private var _enabled:Boolean = true;

    /**
     *  If <code>true</code>, the ToolTipManager will automatically show
     *  ToolTips when the user moves the mouse pointer over components.
     *  If <code>false</code>, no ToolTips will be shown.
     *
     *  @default true
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get enabled():Boolean 
    {
        return _enabled;
    }
    
    /**
     *  @private
     */
    public function set enabled(value:Boolean):void
    {
        _enabled = value;
    }

    //----------------------------------
    //  hideDelay
    //----------------------------------

    /**
     *  @private
     */
    private var _hideDelay:Number = 10000; // milliseconds

    /**
     *  The amount of time, in milliseconds, that Flex waits
     *  to hide the ToolTip after it appears.
     *  Once Flex hides a ToolTip, the user must move the mouse
     *  off the component and then back onto it to see the ToolTip again.
     *  If you set <code>hideDelay</code> to <code>Infinity</code>,
     *  Flex does not hide the ToolTip until the user triggers an event,
     *  such as moving the mouse off of the component.
     *
     *  @default 10000
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get hideDelay():Number 
    {
        return _hideDelay;
    }
    
    /**
     *  @private
     */
    public function set hideDelay(value:Number):void
    {
        _hideDelay = value;
    }

    //----------------------------------
    //  hideEffect
    //----------------------------------

    /**
     *  @private
     */
    private var _hideEffect:IAbstractEffect;

    /**
     *  The effect that plays when a ToolTip is hidden,
     *  or <code>null</code> if the ToolTip should disappear with no effect.
	 *
	 *  <p>Effects are not marshaled across applicationDomains in a sandbox
	 *  as they may not be supportable in different versions</p>
     *
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get hideEffect():IAbstractEffect
    {
        return _hideEffect;
    }
    
    /**
     *  @private
     */
    public function set hideEffect(value:IAbstractEffect):void
    {
        _hideEffect = value as IAbstractEffect;
    }

    //----------------------------------
    //  scrubDelay
    //----------------------------------

    /**
     *  @private
     */
    private var _scrubDelay:Number = 100; // milliseconds

    /**
     *  The amount of time, in milliseconds, that a user can take
     *  when moving the mouse between controls before Flex again waits
     *  for the duration of <code>showDelay</code> to display a ToolTip.
     *
     *  <p>This setting is useful if the user moves quickly from one control
     *  to another; after displaying the first ToolTip, Flex will display
     *  the others immediately rather than waiting.
     *  The shorter the setting for <code>scrubDelay</code>, the more
     *  likely that the user must wait for an amount of time specified
     *  by <code>showDelay</code> in order to see the next ToolTip.
     *  A good use of this property is if you have several buttons on a
     *  toolbar, and the user will quickly scan across them to see brief
     *  descriptions of their functionality.</p>
     *
     *  @default 100
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get scrubDelay():Number 
    {
        return _scrubDelay;
    }
    
    /**
     *  @private
     */
    public function set scrubDelay(value:Number):void
    {
        _scrubDelay = value;
    }

    //----------------------------------
    //  showDelay
    //----------------------------------

    /**
     *  @private
     */
    private var _showDelay:Number = 500; // milliseconds

    /**
     *  The amount of time, in milliseconds, that Flex waits
     *  before displaying the ToolTip box once a user
     *  moves the mouse over a component that has a ToolTip.
     *  To make the ToolTip appear instantly, set <code>showDelay</code> to 0.
     *
     *  @default 500
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get showDelay():Number 
    {
        return _showDelay;
    }
    
    /**
     *  @private
     */
    public function set showDelay(value:Number):void
    {
        _showDelay = value;
    }

    //----------------------------------
    //  showEffect
    //----------------------------------

    /**
     *  @private
     */
    private var _showEffect:IAbstractEffect;

    /**
     *  The effect that plays when a ToolTip is shown,
     *  or <code>null</code> if the ToolTip should appear with no effect.
	 *
	 *  <p>Effects are not marshaled across applicationDomains in a sandbox
	 *  as they may not be supportable in different versions</p>
     *
     *  @default null
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get showEffect():IAbstractEffect
    {
        return _showEffect;
    }
    
    /**
     *  @private
     */
    public function set showEffect(value:IAbstractEffect):void
    {
        _showEffect = value as IAbstractEffect;
    }

    //----------------------------------
    //  toolTipClass
    //----------------------------------

    /**
     *  @private
     */
    private var _toolTipClass:Class = ToolTip;

    /**
     *  The class to use for creating ToolTips.
     *  
	 *  <p>The ToolTipClass is not marshaled across applicationDomains in a sandbox
	 *  as they may not be supportable in different versions.  Child
	 *  applications should only be interested in setting the tooltip
	 *  for objects within themselves</p>
     *
     *  @default mx.controls.ToolTip
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get toolTipClass():Class 
    {
        return _toolTipClass;
    }
    
    /**
     *  @private
     */
    public function set toolTipClass(value:Class):void
    {
        _toolTipClass = value;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Initializes the class.
     *
     *  <p>This method sets up three Timer objects that ToolTipManager
     *  starts and stops while tracking the mouse.
     *  The repeatCount is set to 1 so that they fire only once.
     *  Their duration is set later, just before they are started.
     *  The timers are never destroyed once they are created here.</p>
     *
     *  <p>This method is called by targetChanged(); Flex waits to initialize
     *  the class until mouse-tracking happens in order to optimize
     *  startup time.</p>
     */
    mx_internal function initialize():void
    {
        if (!showTimer)
        {
            showTimer = new Timer(0, 1);
            showTimer.addEventListener(TimerEvent.TIMER,
                                       showTimer_timerHandler);
        }

        if (!hideTimer)
        {
            hideTimer = new Timer(0, 1);
            hideTimer.addEventListener(TimerEvent.TIMER,
                                       hideTimer_timerHandler);
        }

        if (!scrubTimer)
            scrubTimer = new Timer(0, 1);

        initialized = true;
    }

    /**
     *  Registers a target UIComponent or UITextField, and the text
     *  for its ToolTip, with the ToolTipManager.
     *  This causes the ToolTipManager to display a ToolTip
     *  when the mouse hovers over the target.
     *
     *  <p>This method is called by the setter
     *  for the toolTip property in UIComponent and UITextField.</p>
     *
     *  @param target The UIComponent or UITextField that owns the ToolTip.
     *
     *  @param oldToolTip The old text that was displayed
     *  in the ToolTip.
     * 
     *  @param newToolTip The new text to display in the ToolTip.
     *  If null, no ToolTip will be displayed when the mouse hovers
     *  over the target.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function registerToolTip(target:DisplayObject,
                                    oldToolTip:String,
									newToolTip:String):void
    {
        if (!oldToolTip && newToolTip)
        {
            target.addEventListener(MouseEvent.MOUSE_OVER,
                                    toolTipMouseOverHandler);
            target.addEventListener(MouseEvent.MOUSE_OUT,
                                    toolTipMouseOutHandler);
                                    
            // If the mouse is already over the object
            // that's getting a toolTip, show the tip.
            if (mouseIsOver(target))
            	showImmediately(target);
        }
        else if (oldToolTip && !newToolTip)
        {
            target.removeEventListener(MouseEvent.MOUSE_OVER,
                                       toolTipMouseOverHandler);
            target.removeEventListener(MouseEvent.MOUSE_OUT,
                                       toolTipMouseOutHandler);
            
            // If the mouse is over the object whose toolTip
            // is being removed, hide the tip.
            if (mouseIsOver(target))
            	hideImmediately(target);
        }
    }

    /**
     *  Registers a target UIComponent, and the text
     *  for its error tip, with the ToolTipManager.
     *  This causes the ToolTipManager to display an error tip
     *  when the mouse hovers over the target.
     *
     *  <p>This method is called by the setter
     *  for the errorString property in UIComponent.</p>
     *
     *  @param target The UIComponent or UITextField that owns the ToolTip.
     * 
     *  @param oldErrorString The old text that was displayed
     *  in the error tip.
     *
     *  @param newErrorString The new text to display in the error tip.
     *  If null, no error tip will be displayed when the mouse hovers
     *  over the target.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function registerErrorString(target:DisplayObject,
                                        oldErrorString:String,
										newErrorString:String):void
    {
        if (!oldErrorString && newErrorString)
        {
            target.addEventListener(MouseEvent.MOUSE_OVER,
                                    errorTipMouseOverHandler);
            target.addEventListener(MouseEvent.MOUSE_OUT,
                                    errorTipMouseOutHandler);
                                    
            // If the mouse is already over the object
            // that's getting an errorTip, show the tip.
            if (mouseIsOver(target))
            	showImmediately(target);
        }
        else if (oldErrorString && !newErrorString)
        {
            target.removeEventListener(MouseEvent.MOUSE_OVER,
                                       errorTipMouseOverHandler);
            target.removeEventListener(MouseEvent.MOUSE_OUT,
                                       errorTipMouseOutHandler);
            
            // If the mouse is over the object whose toolTip
            // is being removed, hide the tip.
            if (mouseIsOver(target))
            	hideImmediately(target);
        }
    }
    
    /**
     *  @private
     *  Returns true if the mouse is over the specified target.
     */
    private function mouseIsOver(target:DisplayObject):Boolean
    {
    	if (!target || !target.stage)
    		return false;
    		
    	//SDK:13465 - If we pass through the above if block, then
    	//we have a target component and its been added to the 
    	//display list. If the mouse coordinates are (0,0), there 
    	//is a chance the component has not been positioned yet 
    	//and we'll end up mistakenly showing tooltips since the 
    	//target hitTest will return true. 
    	if ((target.stage.mouseX == 0)	 && (target.stage.mouseY == 0))
    		return false;
        
        if (target is ILayoutManagerClient && !ILayoutManagerClient(target).initialized)
            return false;
    		
    	return target.hitTestPoint(target.stage.mouseX,
    							   target.stage.mouseY, true);
    }
    
    /**
     *  @private
     *  Shows the tip immediately when the toolTip or errorTip property 
     *  becomes non-null and the mouse is over the target.
     */
    private function showImmediately(target:DisplayObject):void
    {
    	var oldShowDelay:Number = ToolTipManager.showDelay;
    	ToolTipManager.showDelay = 0;
    	checkIfTargetChanged(target);
    	ToolTipManager.showDelay = oldShowDelay;
    }

    /**
     *  @private
     *  Hides the tip immediately when the toolTip or errorTip property 
     *  becomes null and the mouse is over the target.
     */
    private function hideImmediately(target:DisplayObject):void
    {
    	checkIfTargetChanged(null);
    }

    /**
     *  Replaces the ToolTip, if necessary.
     *
     *  <p>Determines whether the UIComponent or UITextField object
     *  with the ToolTip assigned to it that is currently under the mouse
     *  pointer is the most recent such object.
     *  If not, it removes the old ToolTip and displays the new one.</p>
     *
     *  @param displayObject The UIComponent or UITextField that is currently under the mouse.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function checkIfTargetChanged(displayObject:DisplayObject):void
    {
        if (!enabled)
            return;

        findTarget(displayObject);
		
        if (currentTarget != previousTarget)
        {
            targetChanged();
            previousTarget = currentTarget;
        }
    }

    /**
     *  Searches from the <code>displayObject</code> object up the chain
     *  of parent objects until it finds a UIComponent or UITextField object
     *  with a <code>toolTip</code> or <code>errorString</code> property.
     *  Treats an empty string as a valid <code>toolTip</code> property.
     *  Sets the <code>currentTarget</code> property.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function findTarget(displayObject:DisplayObject):void
    {
        // Walk up the DisplayObject parent chain looking for a UIComponent
        // with a toolTip or errorString property. Note that we stop
        // even if we find a tooltip which is an empty string. Although
        // we don't display empty tooltips, we have to track when we
        // are over a movieclip with an empty tooltip so that we can
        // hide any previous tooltip. This allows a child to set
        // toolTip="" to "cancel" its parent's toolTip.
        while (displayObject)
        {
            if (displayObject is IValidatorListener)
            {
                currentText = IValidatorListener(displayObject).errorString;
                var showErrorTip:Boolean;
                if (displayObject is IStyleClient)
                    showErrorTip = FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_0 ||
                                   IStyleClient(displayObject).getStyle("showErrorTip");
                                   ;
                if (currentText != null && currentText != "" && showErrorTip)
                {
                    currentTarget = displayObject;
                    isError = true;
                    return;
                }
            }

            if (displayObject is IToolTipManagerClient)
            {
                currentText = IToolTipManagerClient(displayObject).toolTip;
                if (currentText != null)
                {
                    currentTarget = displayObject;
                    isError = false;
                    return;
                }
            }

            displayObject = displayObject.parent;
        }

        currentText = null;
        currentTarget = null;
    }

    /**
     *  Removes any ToolTip that is currently displayed and displays
     *  the ToolTip for the UIComponent that is currently under the mouse
     *  pointer, as determined by the <code>currentTarget</code> property.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function targetChanged():void
    {
        // Do lazy creation of the Timer objects this class uses.
        if (!initialized)
            initialize()

        var event:ToolTipEvent;
        
        if (previousTarget && currentToolTip)
        {
			if (currentToolTip is IToolTip)
			{
				event = new ToolTipEvent(ToolTipEvent.TOOL_TIP_HIDE);
				event.toolTip = currentToolTip;
				previousTarget.dispatchEvent(event);
			}
			else
			{
                if (hasEventListener(ToolTipEvent.TOOL_TIP_HIDE))
		    		dispatchEvent(new Event(ToolTipEvent.TOOL_TIP_HIDE));
			}
        }   
            
        reset();

        if (currentTarget)
        {
            // Don't display empty tooltips.
            if (currentText == "")
                return;

            // Dispatch a "startToolTip" event
            // from the object displaying the tooltip.
            event = new ToolTipEvent(ToolTipEvent.TOOL_TIP_START);
            currentTarget.dispatchEvent(event);

            if (showDelay == 0 || scrubTimer.running)
            {
                // Create the tooltip and start its showEffect.
                createTip();
                initializeTip();
                positionTip();
                showTip();
            }
            else
            {
                showTimer.delay = showDelay;
                showTimer.start();
                // After the delay, showTimer_timerHandler()
                // will create the tooltip and start its showEffect.
            }
        }
    }

    /**
     *  Creates an invisible new ToolTip.
     *
     *  <p>If the ToolTipManager's <code>enabled</code> property is
     *  <code>true</code> this method is automatically called
     *  when the user moves the mouse over an object that has
     *  the <code>toolTip</code> property set,
     *  The ToolTipManager makes subsequent calls to
     *  <code>initializeTip()</code>, <code>positionTip()</code>,
     *  and <code>showTip()</code> to complete the display
     *  of the ToolTip.</p>
     *
     *  <p>The type of ToolTip that is created is determined by the
     *  <code>toolTipClass</code> property.
     *  By default, this is the ToolTip class.
     *  This class can be styled to appear as either a normal ToolTip
     *  (which has a yellow background by default) or as an error tip
     *  for validation errors (which is red by default).</p>
     *
     *  <p>After creating the ToolTip with the <code>new</code>
     *  operator, this method stores a reference to it in the
     *  <code>currentToolTip</code> property.
     *  It then uses addChild() to add this ToolTip to the
     *  SystemManager's toolTips layer.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function createTip():void
    {
        // Dispatch a "createToolTip" event
        // from the object displaying the tooltip.
        var event:ToolTipEvent =
            new ToolTipEvent(ToolTipEvent.TOOL_TIP_CREATE);
        currentTarget.dispatchEvent(event);

        if (event.toolTip)
            currentToolTip = event.toolTip;
        else
            currentToolTip = new toolTipClass();

        currentToolTip.visible = false;

        // Set the tooltip to be in the same module factory as the target to the
        // correct style manager is used. Don't overwrite an existing module factory.
        if (currentToolTip is IFlexModule && IFlexModule(currentToolTip).moduleFactory == null && 
            currentTarget is IFlexModule)
            IFlexModule(currentToolTip).moduleFactory = IFlexModule(currentTarget).moduleFactory;
        
        if (hasEventListener("createTip"))
    		if (!dispatchEvent(new Event("createTip", false, true)))
	    		return;

        var sm:ISystemManager = getSystemManager(currentTarget) as ISystemManager;
       	sm.topLevelSystemManager.toolTipChildren.addChild(currentToolTip as DisplayObject);
    }

    /**
     *  Initializes a newly created ToolTip with the appropriate text,
     *  based on the object under the mouse.
     *
     *  <p>If the ToolTipManager's <code>enabled</code> property is
     *  <code>true</code> this method is automatically called
     *  when the user moves the mouse over an object that has
     *  the <code>toolTip</code> property set.
     *  The ToolTipManager calls <code>createTip()</code> before
     *  this method, and <code>positionTip()</code> and
     *  <code>showTip()</code> after.</p>
     *
     *  <p>If a normal ToolTip is being displayed, this method
     *  sets its text as specified by the <code>toolTip</code>
     *  property of the object under the mouse.
     *  If an error tip is being displayed, the text is as
     *  specified by the <code>errorString</code> property
     *  of the object under the mouse.</p>
     *
     *  <p>This method also makes the ToolTip the appropriate
     *  size for the text that it needs to display.</p>
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function initializeTip():void
    {
        // Set the text of the tooltip.
        if (currentToolTip is IToolTip)
            IToolTip(currentToolTip).text = currentText;

        if (isError && currentToolTip is IStyleClient)
            IStyleClient(currentToolTip).setStyle("styleName", "errorTip");
        
        sizeTip(currentToolTip);

        if (currentToolTip is IStyleClient)
        {
            // Set up its "show" and "hide" effects.
            if (showEffect)
                IStyleClient(currentToolTip).setStyle("showEffect", showEffect);
            if (hideEffect)
                IStyleClient(currentToolTip).setStyle("hideEffect", hideEffect);
        }

        if (showEffect || hideEffect)
        {
            currentToolTip.addEventListener(EffectEvent.EFFECT_END,
                                            effectEndHandler);
        }
    }

    /**
     *  @private
     *  Objects added to the SystemManager's ToolTip layer don't get
     *  automatically measured or sized, so ToolTipManager has to
     *  measure it and set its size.
     */
    public function sizeTip(toolTip:IToolTip):void
    {
        // Force measure() to be called on the tooltip.
        // Otherwise, its measured size will be 0.
        if (toolTip is IInvalidating)
            IInvalidating(toolTip).validateNow();
        
        toolTip.setActualSize(
            toolTip.getExplicitOrMeasuredWidth(),
            toolTip.getExplicitOrMeasuredHeight());
    }

    /**
     *  Positions a newly created and initialized ToolTip on the stage.
     *
     *  <p>If the ToolTipManager's <code>enabled</code> property is
     *  <code>true</code> this method is automatically called
     *  when the user moves the mouse over an object that has
     *  the <code>toolTip</code> property set.
     *  The ToolTipManager calls <code>createTip()</code> and
     *  <code>initializeTip()</code> before this method,
     *  and <code>showTip()</code> after.</p>
     *
     *  <p>If a normal ToolTip is being displayed, this method positions
     *  its upper-left (upper-right) corner near the lower-right (lower-left)
     *  of the arrow cursor.  This method ensures that the ToolTip is 
     *  completely in view.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function positionTip():void
    {
        // Determine layoutDirection of the target component.
        var layoutDirection:String;
        if (currentTarget is ILayoutDirectionElement)
            layoutDirection = ILayoutDirectionElement(currentTarget).layoutDirection;
        else
            layoutDirection = LayoutDirection.LTR;
        
        const mirror:Boolean = (layoutDirection == LayoutDirection.RTL);
        
        var x:Number;
        var y:Number;
        
        var screenWidth:Number = currentToolTip.screen.width;
        var screenHeight:Number = currentToolTip.screen.height;
        
        if (isError)
        {
            // Tooltips are laid out in the same direction as the target 
            // component.
            var tipElt:ILayoutDirectionElement = 
                currentToolTip as ILayoutDirectionElement;
                        
            if (tipElt && 
                tipElt.layoutDirection != layoutDirection)
            {
                tipElt.layoutDirection = layoutDirection;
                // sizeTip below will call validateNow()
                tipElt.invalidateLayoutDirection();
            }
            
            var targetGlobalBounds:Rectangle = 
                getGlobalBounds(currentTarget, currentToolTip.root, mirror);
            
            x = mirror ?
                    targetGlobalBounds.left - 4 :
                    targetGlobalBounds.right + 4;
            y = targetGlobalBounds.top - 1;
            
            // If there's no room to the right (left) of the control, put it 
            // above or below, with the left (right) edge of the error tip 
            // aligned with the left (right) edge of the target.
            var noRoom:Boolean = mirror ?
                                 x < currentToolTip.width :    
                                 x + currentToolTip.width > screenWidth;            
            if (noRoom)
            {
                var newWidth:Number = NaN;
                var oldWidth:Number = NaN;
                
                x = mirror ?
                        targetGlobalBounds.right + 2 - currentToolTip.width :
                        targetGlobalBounds.left - 2;
                
                // If the error tip would be too wide for the stage,
                // reduce the maximum width to fit onstage. Note that
                // we have to reassign the text in order to get the tip
                // to relayout after changing the border style and maxWidth.
                if (mirror)
                {
                    if (x < currentToolTip.width + 4) 
                    {
                        // -4 on the left, +2 on the right = -2
                        x = 4;
                        newWidth = targetGlobalBounds.right - 2;                        
                    }
                }
                else
                {
                    if (x + currentToolTip.width + 4 > screenWidth)
                        newWidth = screenWidth - x - 4;                            
                }
                
                if (!isNaN(newWidth))
                {
                    oldWidth = Object(toolTipClass).maxWidth;
                    Object(toolTipClass).maxWidth = newWidth;
                    if (currentToolTip is IStyleClient)
                        IStyleClient(currentToolTip).setStyle("borderStyle", "errorTipAbove");
                    currentToolTip["text"] = currentToolTip["text"];
                } 

                    // Even if the error tip will fit onstage, we still need to
                    // change the border style and get the error tip to relayout.
                else
                {
                    if (currentToolTip is IStyleClient)
                        IStyleClient(currentToolTip).setStyle("borderStyle", "errorTipAbove");
                    currentToolTip["text"] = currentToolTip["text"];
                }
                
                if (currentToolTip.height + 2 < targetGlobalBounds.top)
                {
                    // There's room to put it above the control.
                    y = targetGlobalBounds.top - (currentToolTip.height + 2);
                }
                else
                {
                    // No room above, put it below the control.
                    y = targetGlobalBounds.bottom + 2;
                    
                    if (!isNaN(newWidth))
                        Object(toolTipClass).maxWidth = newWidth;
                    if (currentToolTip is IStyleClient)
                        IStyleClient(currentToolTip).setStyle("borderStyle", "errorTipBelow");
                    currentToolTip["text"] = currentToolTip["text"];
                }
            }
            
            // Since the border style of the error tip may have changed,
            // we have to force a remeasurement and change its size.
            // This is because objects in the toolTips layer
            // don't undergo normal measurement and layout.
            sizeTip(currentToolTip)
            
            // If we changed the tooltip max size, we change it back.
            // Otherwise, if RTL, and x wasn't set for maxWidth, reposition 
            // because the width may have changed during the remeasure.
            if (!isNaN(oldWidth))
                Object(toolTipClass).maxWidth = oldWidth;
            else if (mirror)
                x = targetGlobalBounds.right + 2 - currentToolTip.width;
        }
        else
        {
            var sm:ISystemManager = getSystemManager(currentTarget);
            // Position the upper-left (upper-right) of the tooltip
            // at the lower-right (lower-left) of the arrow cursor.
            x = DisplayObject(sm).mouseX + 11; 
            if (mirror)
                x -= currentToolTip.width;
            y = DisplayObject(sm).mouseY + 22;
            
            // If the tooltip is too wide to fit onstage, move it left (right).
            var toolTipWidth:Number = currentToolTip.width;
            if (mirror)
            {
                if (x < 2)
                    x = 2;
            }
            else if (x + toolTipWidth > screenWidth)
            {
                x = screenWidth - toolTipWidth;
            }
            
            // If the tooltip is too tall to fit onstage, move it up.
            var toolTipHeight:Number = currentToolTip.height;
            if (y + toolTipHeight > screenHeight)
                y = screenHeight - toolTipHeight;
            
            var pos:Point = new Point(x, y);
            pos = DisplayObject(sm).localToGlobal(pos);
            pos = DisplayObject(sm.getSandboxRoot()).globalToLocal(pos);
            x = pos.x;
            y = pos.y;
        }
     
        currentToolTip.move(x, y);
    }

    /**
     *  Shows a newly created, initialized, and positioned ToolTip.
     *
     *  <p>If the ToolTipManager's <code>enabled</code> property is
     *  <code>true</code> this method is automatically called
     *  when the user moves the mouse over an object that has
     *  the <code>toolTip</code> property set.
     *  The ToolTipManager calls <code>createTip()</code>,
     *  <code>initializeTip()</code>, and <code>positionTip()</code>
     *  before this method.</p>
     *
     *  <p>This method first dispatches a <code>"showToolTip"</code>
     *  event from the object under the mouse.
     *  This gives you a chance to do special processing on a
     *  particular object's ToolTip just before it becomes visible.
     *  It then makes the ToolTip visible, which triggers
     *  the ToolTipManager's <code>showEffect</code> if one is specified.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function showTip():void
    {
        // Dispatch a "showToolTip" event
        // from the object displaying the tooltip.
        var event:ToolTipEvent =
            new ToolTipEvent(ToolTipEvent.TOOL_TIP_SHOW);
        event.toolTip = currentToolTip;
        currentTarget.dispatchEvent(event);

        if (isError)
        {
            // Listen for a change event so we know when to hide the tip
            currentTarget.addEventListener(Event.CHANGE, changeHandler);
        }
        else
        {
            var sm:ISystemManager = getSystemManager(currentTarget);
            sm.addEventListener(MouseEvent.MOUSE_DOWN,
                                systemManager_mouseDownHandler);
        }

        // Make the tooltip visible.
        // If showEffect exists, this effect will play.
        // When the effect ends, effectEndHandler()
        // will start the hideTimer.
        currentToolTip.visible = true;

        if (!showEffect)
            showEffectEnded();
    }

    /**
     *  Hides the current ToolTip.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function hideTip():void
    {
        // Dispatch a "hideToolTip" event
        // from the object that was displaying the tooltip.
        if (previousTarget)
        {
            var event:ToolTipEvent =
                new ToolTipEvent(ToolTipEvent.TOOL_TIP_HIDE);
            event.toolTip = currentToolTip;
            previousTarget.dispatchEvent(event);
        }

        // Make the tooltip invisible.
        // If hideEffect exists, this effect will play.
        // When the effect ends, effectEndHandler()
        // will reset the ToolTipManager to a no-tip state.
        if (currentToolTip)
            currentToolTip.visible = false;

        // When to do this?
        if (isError)
        {
            if (currentTarget)
                currentTarget.removeEventListener(Event.CHANGE, changeHandler);
        }
        else
        {
            if (previousTarget)
            {
                var sm:ISystemManager = getSystemManager(previousTarget);
                sm.removeEventListener(MouseEvent.MOUSE_DOWN,
                                       systemManager_mouseDownHandler);
            }
        }

        if (!hideEffect)
            hideEffectEnded();
    }

    /**
     *  Removes any currently visible ToolTip.
     *  If the ToolTip is starting to show or hide, this method
     *  removes the ToolTip immediately without completing the effect.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal function reset():void
    {
        // Reset the three timers, in case any are running.
        showTimer.reset();
        hideTimer.reset();

        // If there is a current tooltip...
        if (currentToolTip)
        {
            // Remove the event handlers for the effectEnd of the showEffect
            // and hideEffect, so that calling endEffectsForTarget() doesn't
            // trigger effectEndHandler().
            if (showEffect || hideEffect)
            {
                currentToolTip.removeEventListener(EffectEvent.EFFECT_END,
                                                   effectEndHandler);
            }

            // End any show or hide effects that might be playing on it.
            EffectManager.endEffectsForTarget(currentToolTip);

            var e:DynamicEvent;
            if (hasEventListener("removeChild"))
            {
                e = new DynamicEvent("removeChild", false, true);
                e.sm = currentToolTip.systemManager;
                e.toolTip = currentToolTip;
            }
			if (!e || dispatchEvent(e))
			{
				// Remove it.
				var sm:ISystemManager = currentToolTip.systemManager as ISystemManager;
           		sm.topLevelSystemManager.toolTipChildren.removeChild(currentToolTip as DisplayObject);
			}
            currentToolTip = null;

            scrubTimer.delay = scrubDelay;
            scrubTimer.reset();
            if (scrubDelay > 0)
			{
                scrubTimer.delay = scrubDelay;
                scrubTimer.start();
			}
        }
    }

    /**
     *  Creates an instance of the ToolTip class with the specified text
     *  and displays it at the specified location in stage coordinates.
     *
     *  <p>ToolTips appear in their own layer, on top of everything
     *  except cursors.</p>
     *
     *  <p>The standard way of using ToolTips is to let the ToolTipManager
     *  automatically show and hide them as the user moves the mouse over
     *  the objects that have the <code>toolTip</code> property set.
     *  You can turn off this automatic ToolTip management by setting
     *  the ToolTipManager's <code>enabled</code> property to
     *  <code>false</code>.</p>
     *
     *  <p>By contrast, this method&#x2014;along with <code>hideToolTip()</code>&#x2014;gives 
     *  you programmatic control over ToolTips.
     *  You can show them when and where you choose,
     *  and you can even show more than one at once if you need to.
     *  (The ToolTipManager never does this, because it is generally
     *  confusing to the user).</p>
     *
     *  <p>This method first creates a new instance of ToolTip and calls the 
     *  <code>addChild()</code> method to put it into the SystemManager's
     *  toolTips layer.
     *  If you are showing an error tip, it sets the appropriate styles.
     *  Then it sets the text for the ToolTip, sizes the ToolTip based on
     *  its text, and positions it where you specified.</p>
     *
     *  <p>You must save the reference to the ToolTip that this method
     *  returns so that you can pass it to the <code>hideToolTip()</code> method.</p>
     *
     *  @param text The text to display in the ToolTip instance.
     *
     *  @param x The horizontal coordinate of the ToolTip in stage coordinates.
     *  In case of multiple stages, the relevant stage is determined
     *  from the <code>context</code> argument.
     *
     *  @param y The vertical coordinate of the ToolTip in stage coordinates.
     *  In case of multiple stages, the relevant stage is determined
     *  from the <code>context</code> argument.
     *
     *  @param errorTipBorderStyle The border style of an error tip. This method 
     *  argument can be null, "errorTipRight", "errorTipAbove", or "errorTipBelow". 
     *  If it is null, then the <code>createToolTip()</code> method creates a normal ToolTip. If it is 
     *  "errorTipRight", "errorTipAbove", or "errorTipBelow", then the <code>createToolTip()</code> 
     *  method creates an error tip, and this parameter determines where the arrow 
     *  of the error tip points to (the error's target). For example, if you pass "errorTipRight", Flex 
     *  positions the error tip (via the x and y arguments) to the 
     *  right of the error target; the arrow is on the left edge of the error tip.
     *
     *  @param context This property is not currently used.
     *
     *  @return The newly created ToolTip.
     *
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function createToolTip(text:String, x:Number, y:Number,
                                         errorTipBorderStyle:String = null,
                                         context:IUIComponent = null):IToolTip
    {
        var toolTip:ToolTip = new ToolTip();

        var sm:ISystemManager = context ?
                                          context.systemManager as ISystemManager:
                                          FlexGlobals.topLevelApplication.systemManager as ISystemManager;

        if (context is IFlexModule)
            toolTip.moduleFactory = IFlexModule(context).moduleFactory;
        else
            toolTip.moduleFactory = sm;
        
        var e:DynamicEvent;
        if (hasEventListener("addChild"))
        {
            e = new DynamicEvent("addChild", false, true);
            e.sm = sm;
            e.toolTip = toolTip;
        }
		if (!e || dispatchEvent(e))
		{
		    sm.topLevelSystemManager.toolTipChildren.addChild(toolTip as DisplayObject);
		}

        if (errorTipBorderStyle)
        {
            toolTip.setStyle("styleName", "errorTip");
            toolTip.setStyle("borderStyle", errorTipBorderStyle);
        }

        toolTip.text = text;

        sizeTip(toolTip);

        toolTip.move(x, y);
        // Ensure that tip is on screen?
        // Should x and y for error tip be tip of pointy border?

        // show effect?

        return toolTip as IToolTip;
    }

    /**
     *  Destroys a specified ToolTip that was created by the <code>createToolTip()</code> method.
     *
     *  <p>This method calls the <code>removeChild()</code> method to remove the specified
     *  ToolTip from the SystemManager's ToolTips layer.
     *  It will then be garbage-collected unless you keep a
     *  reference to it.</p>
     *
     *  <p>You should not call this method on the ToolTipManager's
     *  <code>currentToolTip</code>.</p>
     *
     *  @param toolTip The ToolTip instance to destroy.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function destroyToolTip(toolTip:IToolTip):void
    {
        var e:DynamicEvent;
        if (hasEventListener("removeChild"))
        {
            e = new DynamicEvent("removeChild", false, true);
            e.sm = toolTip.systemManager;
            e.toolTip = toolTip;
        }
		if (!e || dispatchEvent(e))
		{
			// Remove it.
			var sm:ISystemManager = toolTip.systemManager as ISystemManager;
           	sm.topLevelSystemManager.toolTipChildren.removeChild(toolTip as DisplayObject);
		}

        // hide effect?
    }

    /**
     *  @private
     */
    mx_internal function showEffectEnded():void
    {
        if (hideDelay == 0)
        {
            hideTip();
        }
        else if (hideDelay < Infinity)
        {
            hideTimer.delay = hideDelay;
            hideTimer.start();
        }
        if (currentTarget)
        {
            // Dispatch a "toolTipShown" event
            // from the object displaying the tooltip.
            var event:ToolTipEvent =
                new ToolTipEvent(ToolTipEvent.TOOL_TIP_SHOWN);
            event.toolTip = currentToolTip;
            currentTarget.dispatchEvent(event);
        }
    }

    /**
     *  @private
     */
    mx_internal function hideEffectEnded():void
    {
        reset();
        // Dispatch a "toolTipEnd" event
        // from the object that was displaying the tooltip.
        if (previousTarget)
        {
            var event:ToolTipEvent =
                new ToolTipEvent(ToolTipEvent.TOOL_TIP_END);
            event.toolTip = currentToolTip;
            previousTarget.dispatchEvent(event);
        }
    }

    /**
     *  @private
     */
     mx_internal function getSystemManager(
                                    target:DisplayObject):ISystemManager
     {
        return target is IUIComponent ?
               IUIComponent(target).systemManager :
               null;
     }

    /**
     *  @private
     */
    private function getGlobalBounds(obj:DisplayObject, 
                                     parent:DisplayObject, 
                                     mirror:Boolean):Rectangle
    {
        var upperLeft:Point = new Point(0, 0);
        
        upperLeft = obj.localToGlobal(upperLeft);
        
        // If the layout has been mirrored, then the 0,0 is the uppper
        // right corner; compensate here.
        if (mirror)
            upperLeft.x -= obj.width;

        upperLeft = parent.globalToLocal(upperLeft);
        
        return new Rectangle(upperLeft.x, upperLeft.y, obj.width, obj.height);
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  This handler is called when the mouse moves over an object
     *  with a toolTip.
     */
    mx_internal function toolTipMouseOverHandler(event:MouseEvent):void
    {
        checkIfTargetChanged(DisplayObject(event.target));
    }

    /**
     *  @private
     *  This handler is called when the mouse moves out of an object
     *  with a toolTip.
     */
    mx_internal function toolTipMouseOutHandler(event:MouseEvent):void
    {
        checkIfTargetChanged(event.relatedObject);
    }

    /**
     *  @private
     *  This handler is called when the mouse moves over an object
     *  with an errorString.
     */
    mx_internal function errorTipMouseOverHandler(event:MouseEvent):void
    {
        checkIfTargetChanged(DisplayObject(event.target));
    }

    /**
     *  @private
     *  This handler is called when the mouse moves out of an object
     *  with an errorString.
     */
    mx_internal function errorTipMouseOutHandler(event:MouseEvent):void
    {
        checkIfTargetChanged(event.relatedObject);
    }

    /**
     *  @private
     *  This handler is called when the showTimer fires.
     *  It creates the tooltip and starts its showEffect.
     */
    mx_internal function showTimer_timerHandler(event:TimerEvent):void
    {
        // Make sure we still have a currentTarget when the timer fires.
        if (currentTarget)
        {
            createTip();
            initializeTip();
            positionTip();
            showTip();
        }
    }

    /**
     *  @private
     *  This handler is called when the hideTimer fires.
     *  It starts the hideEffect.
     */
    mx_internal function hideTimer_timerHandler(event:TimerEvent):void
    {
        hideTip();
    }

    /**
     *  @private
     *  This handler is called when the showEffect or hideEffect ends.
     *  When the showEffect ends, it starts the hideTimer,
     *  which will automatically start hiding the tooltip when it fires,
     *  even if the mouse is still over the target.
     *  When the hideEffect ends, the tooltip is removed.
     */
    mx_internal function effectEndHandler(event:EffectEvent):void
    {
        if (event.effectInstance.effect == showEffect)
            showEffectEnded();
        else if (event.effectInstance.effect == hideEffect)
            hideEffectEnded();
    }

    /**
     *  @private
     *  This handler is called when the user clicks the mouse
     *  while a normal tooltip is displayed.
     *  It immediately hides the tooltip.
     */
    mx_internal function systemManager_mouseDownHandler(event:MouseEvent):void
    {
        reset();
    }

    /**
     *  @private
     */
    mx_internal function changeHandler(event:Event):void
    {
        reset();
    }

}

}
