﻿////----------------------------------------------------------
//// Copyright (C) ESRI. All rights reserved.
////----------------------------------------------------------
// TRADE SECRETS: ESRI PROPRIETARY AND CONFIDENTIAL
// Unpublished material - all rights reserved under the 
// Copyright Laws of the United States.
//
// For additional information, contact:
// Environmental Systems Research Institute, Inc.
// Attn: Contracts Dept
// 380 New York Street
// Redlands, California, USA 92373
//
// email: contracts@esri.com

/// <reference assembly="ESRI.ArcGIS.ADF.Web.UI.WebControls" name="ESRI.ArcGIS.ADF.Web.UI.WebControls.Runtime.JavaScript.ESRI.ADF.UI.Map.js"/>

Type.registerNamespace('ESRI.ADF.UI');

ESRI.ADF.UI.AnchorPoint = function() {
	/// <summary>
	/// The AnchorPoint enumeration determines the corner on which to anchor a component.
	/// </summary>
	/// <field name="TopLeft" type="Number" integer="true" />
	/// <field name="TopRight" type="Number" integer="true" />
	/// <field name="BottomLeft" type="Number" integer="true" />
	/// <field name="BottomRight" type="Number" integer="true" />
	throw Error.invalidOperation();
};
ESRI.ADF.UI.AnchorPoint.prototype = {
	TopLeft : 0,
	TopRight : 1,
	BottomLeft : 2,
	BottomRight : 3
};
ESRI.ADF.UI.AnchorPoint.registerEnum("ESRI.ADF.UI.AnchorPoint", false);

ESRI.ADF.UI.Callout = function() { 
	/// <summary>
	/// The Callout control displays a dynamic window with HTML content.  The window has an arrow to a target element.    
	/// </summary>
	ESRI.ADF.UI.Callout.initializeBase(this);
	this._parent = document.body; //parent container defaults to the document body
	this._templateLoaded = false;
	this._cssLoaded = true;
	this._css=null;
	this._template = '<div style="background-color:#ffa;border: solid 1px #eee;padding: 0px; margin:0;" ><div style="padding:0;">{@content}</div></div>';
	
	this._arrowSize = [18,30]; //the width and height of the arrow
	this._offset = [18,6]; //Arrow offset
	this._calloutBuffer = 5;
	this._templateUrl = null;
	this._arrowImageUrl = ESRI.ADF.UI.calloutArrowUrl;
	this._isHidden = true;
	this._animate = true;
	this._autoHide = true;
	this._verticalOffset = 0;
	this._clickHandler = Function.createDelegate(this, this._onClick);
	this._mouseoverHandler = Function.createDelegate(this,function(e) { this.stopHideTimer(); this._raiseEvent('mouseOver',e);});
	this._mouseoutHandler = Function.createDelegate(this,function(e) { this.startHideTimer(); this._raiseEvent('mouseOut',e);});
	this._anchorPoint = ESRI.ADF.UI.AnchorPoint.TopLeft;
	this._hideDelayTime=500;
};
ESRI.ADF.UI.Callout.prototype = {
	initialize : function() {
		ESRI.ADF.UI.Callout.callBaseMethod(this, 'initialize');
		if(this._templateUrl) {
			var wRequest1 = new Sys.Net.WebRequest();
			wRequest1.set_url(this._templateUrl);
			wRequest1.add_completed(Function.createDelegate(this,this._onTemplateLoaded));
			wRequest1.invoke(); 
		}
		else {
			this._templateLoaded = true;
			this._createCallout();
		}
	},
	_onTemplateLoaded : function(executor,eventArgs){
		if(executor.get_responseAvailable()) {
			this._template = executor.get_responseData();
			this._templateLoaded = true;
			this._createCallout();
		}
	},	
	_createCallout : function() {
		this._calloutMain = document.createElement('div');
		this._arrowDiv = document.createElement('div');
		this._contentDiv = document.createElement('div');
		this._calloutMain.style.position = 'absolute';	
		this._arrowDiv.style.position = 'absolute';
		this._arrowDiv.style.overflow = 'hidden';
		this._arrowDiv.style.width = this._arrowSize[0]+'px';
		this._arrowDiv.style.height = this._arrowSize[1]+'px';
		this._arrowDiv.style.backgroundImage = 'url('+this._arrowImageUrl+')';		
		this._contentDiv.style.position = 'absolute';
		this._contentDiv.style.cursor = 'default';
		this._calloutMain.appendChild(this._arrowDiv);
		this._calloutMain.appendChild(this._contentDiv);
		this._calloutMain.style.zIndex = ESRI.ADF.UI.Callout._zIndex;		
		
		this._animation1 = new AjaxControlToolkit.Animation.FadeAnimation(this._contentDiv, 0.25, 25, AjaxControlToolkit.Animation.FadeEffect.FadeIn, 0, 1,false);
		this._animation2 = new AjaxControlToolkit.Animation.FadeAnimation(this._contentDiv, 0.25, 25, AjaxControlToolkit.Animation.FadeEffect.FadeOut, 0, 1,false);
		this._animation1.add_ended(Function.createDelegate(this,function() { this._arrowDiv.style.display=''; }));
		this._animation2.add_ended(Function.createDelegate(this,function() { this._calloutMain.style.display='none'; }));

		if(this._isHidden) {
			this._calloutMain.style.display='none';
			this._arrowDiv.style.display='none';
		}
		this._parent.appendChild(this._calloutMain);
		ESRI.ADF.UI.Callout.callBaseMethod(this, 'initialize');		
		this.setContent(this._content);
		this._reorient();
		this.setPosition(0,0);
		$addHandler(this._contentDiv, "click", this._clickHandler);
		$addHandler(this._contentDiv,'mouseover',this._mouseoverHandler);
		$addHandler(this._contentDiv,'mouseout',this._mouseoutHandler);
		$addHandler(this._contentDiv,'mousedown',function(e) { e.stopPropagation(); });		
	},	
	dispose : function() {
		if(!this._calloutMain) { return; }
		this.hide();
		$clearHandlers(this._contentDiv);
		this._calloutMain.parentNode.removeChild(this._calloutMain);
		this._calloutMain = null;
		this._parent = null;
		ESRI.ADF.UI.Callout.callBaseMethod(this, 'dispose');
	},
	setContent : function(value) {
		/// <summary>Sets the content of the callout relative to its parent.</summary>		
		/// <param name="value" type="Objects">Key value pair object with names to bind to template.</param>
		/// <remarks>Key names matching {@keyname} tags in the template will be replaced by its value.</remarks>
		if(this._content != value) {
			this._content = value;
			this._refreshContent();
			this.raisePropertyChanged("content");
		}
	},
	_refreshContent : function() {
		if(!this.get_isInitialized()) { return; }
		this._contentDiv.innerHTML = ESRI.ADF.System.templateBinder(this._content,this._template);
		this._reorient();
	},
	setPosition : function(x,y) {
		/// <summary>Sets the position of the callout relative to its parent.</summary>
		/// <param name="x" type="Number">X offset in pixels.</param>	
		/// <param name="y" type="Number">Y offset in pixels.</param>	
		/// <remarks>The position will be offset according to the buffer property and alignment.</remarks>
		if(!this.get_isInitialized()) { return; }
		this._calloutMain.style.left = x+'px';
		this._calloutMain.style.top = y+'px';
	},
	getPosition : function() {
    	/// <summary>Returns the current screen position of the callout</summary>
		if(!this._calloutMain) { return null; }
		return [parseInt(this._calloutMain.style.left,10),parseInt(this._calloutMain.style.top,10)];
	},
	checkPosition : function() {
		/// <summary>Checks the position of the callout if the contents has changed.</summary>
		this._reorient();
	},
	_reorient : function() {	
		if(!this._contentDiv || this._contentDiv.style.display==='none' || !this._contentDiv.parentNode) { return; }
		var bounds = Sys.UI.DomElement.getBounds(this._contentDiv);
		if(bounds.width==0 || bounds.height==0) { return; }
		var x=0; var y=0;
		if(this._anchorPoint==ESRI.ADF.UI.AnchorPoint.BottomRight) { x = y = -1; }
		else if(this._anchorPoint===ESRI.ADF.UI.AnchorPoint.TopRight) { x = -1; }
		else if(this._anchorPoint===ESRI.ADF.UI.AnchorPoint.BottomLeft) { y = -1; }
		var x2 = (x===0?1:-1);
		var y2 = (y===0?1:-1);
		this._arrowDiv.style.left = (this._arrowSize[0]*x + this._calloutBuffer*x2)+'px';
		this._arrowDiv.style.top = (this._arrowSize[1]*y + this._calloutBuffer*y2)+'px';
		this._arrowDiv.style.backgroundPosition = (this._arrowSize[0]*x)+'px ' +  (this._arrowSize[1]*y)+'px';
		this._contentDiv.style.left = ((bounds.width*x + this._arrowSize[0]*x2) + this._calloutBuffer*x2)+'px';
		this._contentDiv.style.top = ((bounds.height*y + this._offset[1]*y2) + this._calloutBuffer*y2 - this._verticalOffset*y2)+'px';
	},
	startHideTimer : function() {
		/// <summary>Starts a timer that will hide the callout with a slight delay</summary>
		/// <remarks>This function is used for hover events to prevent hiding the callout because the mouse briefly left the trigger object.  Define delay time using get/set_hideDelayTime property.</remarks>
		this.stopHideTimer();
		if(this.get_autoHide()) {
			this._timer = window.setTimeout(Function.createDelegate(this,this.hide),this._hideDelayTime);			
		}
	},
	stopHideTimer : function() {
		/// <summary>Stops a timer that will hide the callout with a slight delay</summary>
		/// <remarks>This function is used for hover events to prevent hiding the callout because the mouse briefly left the trigger object</remarks>
		if(this._timer) {
			window.clearTimeout(this._timer);
			this._timer=null;
		}
	},
	show : function() {
		/// <summary>Shows the callout</summary>
		ESRI.ADF.UI.Callout._zIndex++;
		this._calloutMain.style.zIndex = ESRI.ADF.UI.Callout._zIndex;
		this._isHidden = false;
		this._calloutMain.style.visibility='hidden'; //Ensure layout but stay hidden
		this._calloutMain.style.display='';
		this._raiseEvent('show');
		this._reorient();
		if(this._animate) { this._animation1.play(); }
		else { this._arrowDiv.style.display = ''; }
		this._calloutMain.style.visibility=''; //Unhide
	},
	hide : function() {
		/// <summary>Hides the callout</summary>
		if(this._isHidden) { return; }
		if(this._animate) { this._animation2.play(); }
		else { this._calloutMain.style.display='none'; }
		this._arrowDiv.style.display='none'; 
		this._isHidden = true;
		this._raiseEvent('hide');
	},	
	_onClick : function(e) {
		e.stopPropagation();
		this._raiseEvent('click');
	},
	//
	// Properties
	//
	/// <value type="Boolean">Get the display state (visible, hidden) of the callout.</value>
	get_isOpen : function() {
		return !this._isHidden;
	},
	get_content : function() {
		/// <value type="String">Gets or sets the content of the callout.  The content can be any valid HTML.</value>
		return this._content;
	},
	get_autoHide : function() {
		/// <value type="Boolean">Gets or sets whether the callout should hide itself
		/// shortly after the mouse has left the callout</value>
		return this._autoHide;
	},
	set_autoHide : function(value) {
		if (this._autoHide != value) {
			this._autoHide = value;
			this.raisePropertyChanged("autoHide");
		}
	},
	get_anchorPoint : function() {
		/// <value type="ESRI.ADF.UI.AnchorPoint">Gets or sets the relative position of the callout.</value>
		return this._anchorPoint;
	},
	set_anchorPoint : function(value) {
		if(this._anchorPoint!=value) {
			this._anchorPoint = value;
			this._reorient();
		}
	},
	get_animate : function() {
		/// <value type="Boolean">Gets or sets whether the callout fades in and out.</value>
		return this._animate;
	},
	set_animate : function(value) {if(this._animate!=value) {this._animate = value;}
	},
	get_parent : function() {
		/// <value type="Sys.UI.DomElement" domElement="true">Gets or sets the parent container of this element.
		/// Positioning will be relative to this element.</value>
		return this._parent;
	},
	set_parent : function(value) {if(this._parent!=value) {this._parent = value;}},
	get_hideDelayTime : function() {
		/// <value type="Number" integer="true">
		/// Gets or sets the number of milliseconds before a callout hides itself after the mouse leaves it.
		/// </value>
		/// <seealso "autoHide" />
		return this._hideDelayTime;
	},
	set_hideDelayTime : function(value) { if(this._hideDelayTime!=value) { this._hideDelayTime = value; }},	
	get_template : function() {
		/// <value type="Sys.UI.String" >Gets or sets the callout template.</value>
		return this._template;
	},
	set_template : function(value) {this._template = value; this._refreshContent(); },
	get_templateUrl : function() {
		/// <value type="String">Gets or sets the url to the template.</value>
		/// <remarks>The templateUrl should be set prior to initialize and will override the template property.</remarks>
		return this._templateUrl;
	},
	set_templateUrl : function(value) { this._templateUrl = value; },
	get_arrowImageUrl : function() {
		/// <value type="Sys.UI.String" >Gets or sets the url for the arrow sprite.</value>
		/// <remarks>The image should contain four arrows pointing towards each their corner.</remarks>
		return this._arrowImageUrl;
	},
	set_arrowImageUrl : function(value) { this._arrowImageUrl = value; },
	get_arrowSize : function() {
		/// <value type="Array" elementType="Number" elementInteger="true">Gets or sets the width and height of the arrow.</value>
		return this._arrowSize;
	},
	set_arrowSize : function(value) { this._arrowSize = value; },
	get_arrowOffset : function() {
		/// <value type="Array" elementType="Number" elementInteger="true">Gets or sets the offset of the arrow.</value>
		/// <remarks>The offset is the distance from the peak of the arrow to the point where it
		/// should connect to the corner of the callout.</remarks>
		return this._offset;
	},
	set_arrowOffset : function(value) { this._offset = value; },
	get_calloutBuffer : function() {
		/// <value type="Number" integer="true">Gets or sets the distance between a callout position and the arrow.</value>
		return this._calloutBuffer;
	},
	set_calloutBuffer : function(value) { this._calloutBuffer = value; },
	get_verticalOffset : function() {
		/// <value type="Number" integer="true">Gets or sets the vertical offset between the arrow and the content box.</value>
		/// <remarks>This can be used to slide the contents down or up to make better room for the contents.
		/// A subsequent call to checkPosition will apply this change at once.</remarks>
		return this._verticalOffset;
	},
	set_verticalOffset : function(value) { this._verticalOffset = value; },
	//
	// Events
	//
	_raiseEvent : function(name,e) {
		var handler = this.get_events().getHandler(name);
		if (handler) { if(!e) { e = Sys.EventArgs.Empty; } handler(this, e); }
	},
	add_closed : function(handler) {
		/// <summary>Raised when the callout was closed.</summary>
		this.get_events().addHandler('closed', handler);
	},
	remove_closed : function(handler) { this.get_events().removeHandler('closed', handler); },
	add_click : function(handler) {
		/// <summary>Raised when the callout was click.</summary>
		this.get_events().addHandler('click', handler);
	},
	remove_click : function(handler) { this.get_events().removeHandler('click', handler); },
	add_show : function(handler) {
		/// <summary>Raised when the callout is shown.</summary>
		this.get_events().addHandler('show', handler);
	},
	remove_show : function(handler) { this.get_events().removeHandler('show', handler); },
	add_hide : function(handler) {
		/// <summary>Raised when the callout is hidden.</summary>
		this.get_events().addHandler('hide', handler);
	},
	remove_hide : function(handler) { this.get_events().removeHandler('hide', handler); },
	add_mouseOut : function(handler) {
		/// <summary>Raised when the mouse exits the callout.</summary>
		this.get_events().addHandler('mouseOut', handler);
	},
	remove_mouseOut : function(handler) { this.get_events().removeHandler('mouseOut', handler); },
	add_mouseOver : function(handler) {
		/// <summary>Raised when the mouse enters the callout.</summary>
		this.get_events().addHandler('mouseOver', handler);
	},
	remove_mouseOver : function(handler) { this.get_events().removeHandler('mouseOver', handler); }
};
ESRI.ADF.UI.Callout.registerClass('ESRI.ADF.UI.Callout', Sys.Component);
ESRI.ADF.UI.Callout._zIndex = 1000;
//
// Callout implementation on GraphicsLayer
//
ESRI.ADF.UI.MapTips = function() {
	/// <summary>
	/// The MapTips control enables the display of dynamic graphic symbology and attribute information in a browser.  
	/// The control initiates an action based on a mouse event with a graphic feature.  
	/// The action may include changing the symbol of the graphic feature ("highlighting"), or displaying a Callout.    
	/// </summary>
	ESRI.ADF.UI.MapTips.initializeBase(this);
	this._clickHandler = Function.createDelegate(this,this._onCalloutClick);
	this._mouseOverHandler = Function.createDelegate(this,this._onMouseOverCallout);
	this._mouseOutHandler = Function.createDelegate(this,this._onMouseOutCallout);
	this._onMouseGfxOverHandler = Function.createDelegate(this,this._onMouseOverGfx);
	this._onMouseGfxOutHandler = Function.createDelegate(this,this._onMouseOutGfx);
	this._onMouseGfxMoveHandler = Function.createDelegate(this,this._onMouseMoveGfx);
	this._onCalloutShowHandler = Function.createDelegate(this,this._onCalloutShow);
	this._onCalloutHideHandler = Function.createDelegate(this,this._onCalloutHide);
	this._onZoomStartHandler = Function.createDelegate(this, function() { if(this._callout) { this._callout.hide(); } window.clearTimeout(this._showDelayedTimer); this._showDelayedTimer=null; });
	this._onGridOriginChanged = Function.createDelegate(this, function() { this._currentElement=null; });
	this._onMapTipEventHandler = Function.createDelegate(this, this._onMapTipEvent);
	this._onCalloutContentChanged = Function.createDelegate(this, this._contentChanged);
	this._currentElement = null;
	this._hoverTemplate = '';
	this._contentTemplate = '';
	this._map = null;
	this._hoverLayers = [];
	this._animate=true;
	this._maxheight=150;
	this._width=250;
	this._isExpanded = false;
	this._maptipID = ESRI.ADF.UI.MapTips._maptipCount; //unique MapTip ID
	this._isDisposed = false;
	this._autoHide = true;
	this._expandOnClick = true;
	this._showOnHover = true;
	ESRI.ADF.UI.MapTips._maptipCount++;
};
ESRI.ADF.UI.MapTips.prototype = {
	createTemplate : function() {
		/// <summary>Creates and returns the maptip template. Override this method to change the layout.</summary>
		/// <remarks>Get the title and content IDs for the template using the 
		/// get_maptipContentID() and get_maptipTitleID() properties.</remarks>		
		//Create template
		//Container
		var template = '<div style="margin: 0; padding: 0; background-color:White;border: solid 1px #999;">';
		//Title
		template +='<table cellpadding="0" cellspacing="0"><tr style="background-color:#eee;"><td style="padding:5px 2px;margin:0;font-weight: bold;color: Black" id="'+ this.get_maptipTitleID() +'">{@title}</td>';
		template +='</tr>';
		//Content - Height set programmatically later
		template +='<tr><td><div id="'+this.get_maptipContentID()+'" style="'+
			'border-top: 1px solid #aaa;overflow:auto; overflow-x: auto; '+
			(this._width?'width:'+(this._width)+'px;':'')+' padding: 1px 3px 5px 3px; background-color: White; display: block; color: Black">{@content}</div>';
		template +='</td></tr>';
		return template;
	},
	_onMapTipEvent : function(s,e) {
		if(e.maptip===this || this._isDisposed) { return; }
		this._callout.set_verticalOffset(0);
		if(e.action==='show' && this._autoHide) {
			this.collapse();
			this._callout.hide();
			window.clearTimeout(this._showDelayedTimer);
			this._showDelayedTimer = null;
		}
	},
	initialize : function() {
		ESRI.ADF.UI.MapTips.callBaseMethod(this, 'initialize');
		if(!this._map) { this._delaySetup = true; }
		else { this._setupCallout(); }
	},
	collapse : function() {
		/// <summary>Collapses the callout.</summary>
		if(this._isExpanded) {
			this._isExpanded = false;
			this._setExpandCollapseState();
			var handler = this.get_events().getHandler('collapsed');
			if (handler) { handler(this, this._currentElement); }
			this._raiseMapTipEvent("collapse");
		}
	},
	expand : function() {
		/// <summary>Expands the callout.</summary>
		if(!this._isExpanded) {
			this._isExpanded = true;
			this._setExpandCollapseState();
			var handler = this.get_events().getHandler('expanded');
			if (handler) { handler(this, this._currentElement); }
			this._raiseMapTipEvent("expand");
		}
	},
	_setExpandCollapseState : function() {
		var content = $get(this.get_maptipContentID());
		if(!content) { return; }
		if(this._isExpanded)  { content.style.display=''; }
		else { content.style.display='none'; }
		this.adjustHeight();
		this._callout.checkPosition();
	},
	_contentChanged : function(s,e) {
		var prop = e.get_propertyName();
		if(prop!=='content') { return; }
		var tip = $get(this.get_maptipContentID());
		if(tip) {
			if(tip.style.display==='none' && this._isExpanded) { tip.style.display = ''; }
			else if(tip.style.display!=='none' && !this._isExpanded) { tip.style.display = 'none'; }
			this.adjustHeight();
		}
	},
	_setupCallout : function() {
		if(this._callout) { return; }
		
		this._callout = $create(ESRI.ADF.UI.Callout,
				{	"parent":this._map._containerDiv,"animate":this._animate,
					"template":this.createTemplate(), "autoHide":this._autoHide }, 
				{	"click":this._clickHandler, "propertyChanged":this._onCalloutContentChanged,
					"hide":this._onCalloutHideHandler, "show":this._onCalloutShowHandler,
					"mouseOver":this._mouseOverHandler, "mouseOut":this._mouseOutHandler}
		);		
		this._map.add_zoomStart(this._onZoomStartHandler);
		this._map.add_gridOriginChanged(this._onGridOriginChanged);
		this._map.__add_mapTipEvent(this._onMapTipEventHandler);
	},
	addGraphics : function(gfxLayer) {
		/// <summary>Associates a maptip with the given graphic feature.</summary>
		/// <param name="gfxLayer" type="ESRI.ADF.Graphics.GraphicFeatureBase">
		/// Feature to apply maptips to.</param>
		if(!this._callout && !this._map) {
			if(gfxLayer._map) {
				this._map = gfxLayer._map;
				this._setupCallout();
			}
		}
		gfxLayer.add_mouseOver(this._onMouseGfxOverHandler);
		gfxLayer.add_mouseOut(this._onMouseGfxOutHandler);
		gfxLayer.add_mouseMove(this._onMouseGfxMoveHandler);
		gfxLayer.add_click(this._clickHandler);
		Array.add(this._hoverLayers,gfxLayer);
	},
	removeGraphics : function(gfxLayer) {
		/// <summary>Removes the maptip from the given graphic feature.</summary>
		/// <param name="gfxLayer" type="ESRI.ADF.Graphics.GraphicFeatureBase">
		/// Feature to apply maptips to.</param>
		gfxLayer.remove_mouseOver(this._onMouseGfxOverHandler);
		gfxLayer.remove_mouseOut(this._onMouseGfxOutHandler);
		gfxLayer.remove_mouseMove(this._onMouseGfxMoveHandler);
		gfxLayer.remove_click(this._clickHandler);
		Array.remove(this._hoverLayers,gfxLayer);
	},
	dispose : function() {
		/// <summary>Disposes the maptips.</summary>
		for(var idx in this._hoverLayers) {
			this.removeGraphics(this._hoverLayers[idx]);
		}
		if(this._map) {
			this._map.remove_zoomStart(this._onMapTipEventHandler);
			this._map.remove_gridOriginChanged(this._onGridOriginChanged);
			this._map.__remove_mapTipEvent(this._onZoomStartHandler );
		}
		if(this._callout) { this._callout.dispose(); }
		this._callout = null;
		this._currentElement = null;
		this._isDisposed = true;
		ESRI.ADF.UI.MapTips.callBaseMethod(this, 'dispose');
	},
	_onMouseOverGfx : function(sender, e) {
		if(!this._showOnHover) { return; };
		if(this._currentElement != e.element) {
			this._callout.set_verticalOffset(0);
			this.collapse();
			this._callout.hide();
			this._setContentByElement(e.element);
			if(ESRI.ADF.Geometries.Point.isInstanceOfType(e.element.get_geometry())) {
				this.setPosition(e.element.get_geometry());	//Always place at center of point geometries			
			}
			else {
				var evt = ESRI.ADF.System._makeMouseEventRelativeToElement(e,this._callout.get_parent());
				this._callout.setPosition(evt.offsetX, evt.offsetY); //Set position relative to parent
			}
		}
		this._callout.stopHideTimer();
		this._showDelayedTimer = window.setTimeout(Function.createDelegate(this._callout,this._callout.show),500);
	},
	_onMouseMoveGfx : function(sender,evt) {
		if(this._showDelayedTimer && !this._callout.get_isOpen() && this._showOnHover &&
			this._currentElement === evt.element && !ESRI.ADF.Geometries.Point.isInstanceOfType(evt.element.get_geometry())) {
			//Callout follows mouse as long as its not open yet. Callout for points are set in _onMouseOverGfx.
			var e = ESRI.ADF.System._makeMouseEventRelativeToElement(evt,this._callout.get_parent());
			this._callout.setPosition(e.offsetX, e.offsetY); //Set position relative to parent
		}
	},
	setPosition : function(point) {
		/// <summary>Sets the position of a maptip manually using map units.</summary>
		/// <param name="point" type="ESRI.ADF.Geometries.Point">Point in map units</param>
		var pos = this._map._toMapScreen(point);
		this._callout.setPosition(pos.offsetX,pos.offsetY);
	},
	adjustHeight : function() {
		///  <summary>Adjusts the height of the maptip content to fit the mapwindow and/or the maxheight.</summary>
		if(this._callout._calloutMain.style.display==='none') { return; }
		var pos = this._callout.getPosition();
		var offset = [parseInt(this._callout._parent.style.left,10),parseInt(this._callout._parent.style.top,10)];
		var position = [offset[0]+pos[0],offset[1]+pos[1]];
		var centScreen = this._map.toScreenPoint(this._map.get_extent().get_center());
		var align = 0;
		if(position[1]>this._map._mapsize[1]/2) { align+=2; }
		if(position[0]>this._map._mapsize[0]/2) { align++; }

		var height = this._map._mapsize[1];
		var offsety = this._callout._offset[1];
		var tip = $get(this.get_maptipContentID());
		if(tip && tip.style.display!='none') {
			var bottomhalf = (position[1]>centScreen.offsetY);
			var dist = (bottomhalf?position[1]:height-position[1]); //Distance to top/bottom whatever is the furthest:
			tip.style.height = ''; //reset height to measure it
			var tipHeight = parseInt(this._callout._contentDiv.offsetHeight,10);// total height of tip
			var contentHeight = parseInt(tip.offsetHeight,10);// height of content excluding title
			var titleHeight = tipHeight-contentHeight; //height of title,border,margin etc.
			var maxContentHeight = height - titleHeight;
			var adjustedHeight=null;
			if(maxContentHeight && maxContentHeight<contentHeight) { adjustedHeight = maxContentHeight; }
			if(this._maxheight && (adjustedHeight && adjustedHeight>this._maxheight)) {adjustedHeight = this._maxheight;}
			if(adjustedHeight) { tip.style.height = adjustedHeight+'px'; contentHeight = adjustedHeight; }
			var offset = contentHeight+titleHeight - dist+offsety*2;
			if(offset<0) offset = 0;
			this._callout.set_verticalOffset(offset);
		}
		else { this._callout.set_verticalOffset(0); }
		if(this._callout.get_anchorPoint()!=align) { this._callout.set_anchorPoint(align); }
		else { this._callout.checkPosition(); }

	},
	_onMouseOutGfx : function(sender, e) {
		if(!this._showOnHover) { return; }
		if(this._showDelayedTimer) { window.clearTimeout(this._showDelayedTimer); this._showDelayedTimer = null; }
		if(this._callout.get_isOpen() && this._autoHide) { this._callout.startHideTimer(); }
	},
	_setContentByElement : function(element) {
		var title = ESRI.ADF.System.templateBinder(element.get_attributes(),this._hoverTemplate);
		var content = '';
		var attr = element.get_attributes()
		if(!this._contentTemplate) {
			if(attr) { content = ESRI.ADF.System.attributeTableRenderer(attr); }
		}
		else { content = ESRI.ADF.System.templateBinder(attr,this._contentTemplate); }
		if(!title) { title = '<div style="width:10px;">&nbsp;</div>'; } //Ensure minimum size in title when empty
		this.setContent(content,title);
		this._currentElement = element;
		this._setExpandCollapseState();
	},
	_onCalloutClick : function() {
		if(this._expandOnClick) { this.expand(); }
	},
	_onCalloutShow : function() {
		this.adjustHeight();
		this._raiseMapTipEvent("show");
	},
	_onCalloutHide : function() {
		this._raiseMapTipEvent("hide");
	},
	_raiseMapTipEvent : function(action) {
		if(this._map) { this._map._raiseEvent('mapTipEvent',{"maptip":this,"element":this._currentElement,"action":action}); }
	},
	_onMouseOverCallout : function() {
		if(this._currentElement) {
			this._currentHighlight = this._currentElement;
		}
	},
	_onMouseOutCallout : function(e) {
		if(this._currentElement && this.autoHide) {
			this._callout.startHideTimer();
		}
	},
	setContent : function(content,title) {
		/// <summary>Sets the content of the maptip</summary>
		/// <param name="content" type="String">HTML content in the maptip</param>
		this._callout.setContent({"content":content,"title":title});
	},
	get_animate : function() {	
		/// <value type="Boolean">Gets or sets whether the maptip should animate on show/hide.</value>
		if(this.get_isInitialized()) { return this._callout.get_animate(); }
		else { return this._animate; }
	},
	set_animate : function(value) {
		this._animate = value;
		if(this.get_isInitialized()) { this._callout.set_animate(value); }
	},
	get_maxheight : function() {
		/// <value type="Number" integer="true">Gets or sets the expanded maximum height of the content of the maptip.</value>
		return this._maxheight;
	},
	set_maxheight : function(value) {
		this._maxheight = value;
	},
	get_width : function() {
		/// <value type="Number" integer="true">Gets or sets the width of the content of the maptip.</value>
		return this._width;
	},
	set_width : function(value) {
		this._width = value;
	},
	get_showOnHover : function() {
		return this._showOnHover;
	},
	set_showOnHover : function(value) {
		this._showOnHover = value;
		if(value===false && this._callout) { this._callout.hide(); }
	},
	__set_map : function(value) {
		if(this._callout && this._map != value) {
			throw new Error.invalidOperation('Cannot change the map on MapTips once initialized');
		}
		this._map = value;
		this._setupCallout();
	},
	get_callout : function() {
		/// <value type="ESRI.ADF.UI.Callout">Gets the callout instance used by the mapti</value>
		return this._callout;
	},
	get_contentTemplate : function() {
		/// <value type="String">Gets or sets a content template used to format an attributes collection when the maptips is expanded.</value>
		/// <remarks>The template is ignored when a contentRenderer has been set.</remarks>
		return this._contentTemplate;
	},
	set_contentTemplate : function(value) {
		this._contentTemplate = value;
	},
	get_hoverTemplate : function() {
		/// <value type="String">Gets or sets a hover template used to format an attributes collection when the mouse hovers on a GraphicFeature.</value>
		return this._hoverTemplate;
	},
	set_hoverTemplate : function(value) { this._hoverTemplate = value; },
	set_autoHide : function(value) {
		this._autoHide = value;
	},
	get_autoHide : function() {
		/// <value type="Boolean">Enables or disables the autohide functionality.</value>
		return this._autoHide;
	},
	get_expandOnClick : function() {
		/// <value type="String">Gets or sets whether the callout will expand when you click it.</value>
		return this._expandOnClick;
	},
	set_expandOnClick : function(value) { this._expandOnClick = value; },
	get_isExpanded : function() {
		/// <value type="Boolean">Gets the expanded state of the maptip.</value>
		return this._isExpanded;
	},
	get_maptipContentID : function() {
		/// <value type="String">Gets the client id of the maptip content.</value>
		return "content_"+this._maptipID;
	},	
	get_maptipTitleID : function() {
		/// <value type="String">Gets the client id of the maptip title.</value>
		return "title_"+this._maptipID;
	},	
	add_expanded : function(handler) {
		/// <summary>Raised when the maptip expands.</summary>
		this.get_events().addHandler('expanded', handler);
	},
	remove_expanded : function(handler) { this.get_events().removeHandler('expanded', handler); },
	add_collapsed : function(handler) {
		/// <summary>Raised when the maptip collapses.</summary>
		this.get_events().addHandler('collapsed', handler);
	},
	remove_collapsed : function(handler) { this.get_events().removeHandler('collapsed', handler); }
};

ESRI.ADF.UI.MapTips.registerClass('ESRI.ADF.UI.MapTips', Sys.Component);
ESRI.ADF.UI.MapTips._maptipCount = 0;

ESRI.ADF.UI.createMapTips = function(gfxLayer,map) {
	return new ESRI.ADF.UI.MapTips(gfxLayer,map);
};

if (typeof(Sys) !== "undefined") { Sys.Application.notifyScriptLoaded(); }
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();