/******************************************************************************
                                    dtt
      - Adds tooltips to any element in a simple and unobtrusive manner -
*******************************************************************************
    Version:       1.5
    Author:        John Ha
    Author URI:    http://ink.bur.st/
    Script URI:    http://ink.bur.st/javascript/

    Description:   Adds tooltips to web pages in an unobtrusive manner using
                   the element's TITLE attribute.
*******************************************************************************

	Copyright (C) 2005-2007  John Ha

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

******************************************************************************/

if (!khanh) var khanh = {};
if (!khanh.dtt) {
	khanh.dtt = {
		/*
		** Specfiy the tags to scan for. '*' means all tags. If a textarea has
		** title="true" then it will be ignored. This is because tinyMCE uses this
		** as a flag.
		*/
		defaultTags: ['a', 'acronym', 'div', 'li', 'input', 'img', 'th', 'td', 'p'],
		/*
		** This specifies the maximum width for the tooltip in pixels.
		*/
		maxWidth: 200,
		/*
		** Offset in pixels that the tooltip should be from the mouse cursor. So, with
		** offsetX: 15 and offsetY: 20, the tooltip will be 15 pixels to the right and
		** 20 pixels down from the mouse cursor position.
		*/
		offsetX: 15,
		offsetY: 20,
		
		pollOn: 1,
		pollInterval: 5000,

		fadeOn: 0,
		opacMin: 0,
		opacMax: 1,
		// Fade duration time (ms)
		fadeTime: 500,

		boingOn: 0,
		// These two values determine "boinginess".
		// Higher values means less "boing".
		boingX: 50,
		boingY: 50,
		// Time (ms) in between boing refresh rate
		boingTime: 0,

		/***********************************************************************
		** DO NOT edit below here, unless you REALLY know what you are doing! **
		***********************************************************************/

		init: function(tags) {
			if (!document.getElementById) return;

			this.scanTags(tags);

			if (this.isLoading) return;
			else if (!this.initBegun) {
				this.initBegun = 1;
				this.isLoading = 1;
				this.loadIncludes();
				return;
			}

			var that = this;

			if (!document.getElementById(this.id)) {
				this.tip = document.createElement ('div');
				this.tip.id = this.id;
				this.tip.style.cssText = this.tipCSS;

				document.body.appendChild(this.tip);

		//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
				if (this.fadeOn) {
					var options = {
						duration: this.fadeTime,
						onComplete: function() { that.effect.complete = 1; },
						transition: fx.sinoidal
					};
					this.effect = new fx.Opacity(this.tip, options);
					this.effect.hide();
				}

				if (this.boingOn) {
					this.boing = this.boingLater();
					this.boing();
				}
		//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

				ieTrueBody =
					document.compatMode
					&& document.compatMode != 'BackCompat' ?
						document.documentElement
					:	document.body;
			}

			if (!isDefined(this.keydownHandler)) {
				this.keydownHandler = function(evt) { that.handleKeydown(evt); };
				khanh.event.add(document, 'keydown', this.keydownHandler);
			}

			if (!isDefined(this.keyupHandler)) {
				this.keyupHandler = function(evt) { that.handleKeyup(evt); };
				khanh.event.add(document, 'keyup', this.keyupHandler);
			}

			this.initDone = 1;
			this.scanTags();
			if (this.pollOn)
				setInterval('khanh.dtt.scanTags()', this.pollInterval);
		},

		scanTags: function(tags) {
			if (!this.initDone) {
				if (isDefined(tags)) this.addTags(tags, this.tags);
				else this.addTags(this.defaultTags, this.tags);
				return;
			}

			if (isDefined(tags))
				tags = this.addTags(tags);
			else tags = this.tags;

			for (var tag in tags) {
				var elms = document.getElementsByTagName(tag);
				for (var i = 0; i < elms.length; i++)
					if (elms[i].title)
						this.addtip(elms[i], elms[i].title.replace(/'/g, "\'").replace(/"/g, '\"'));
			}
		},

		addTags: function(tags, obj) {
			if (!isDefined(obj)) var obj = {};
			for (var i = 0; i < tags.length; i++)
				obj[tags[i]] = tags[i];
			return obj;
		},

		loadIncludes: function() {
			var that = this;
			this.loader = new khanh.loader();
			this.loader.onload = function() { that.isLoading = 0; that.init(); };
			this.loader.add({type: 'css', files: khanh.coreModules.includesDir + 'dtt.css'});
			this.loader.add({type: 'js', files: khanh.coreModules.includesDir + 'prototype.lite.js'});
			this.loader.then({type: 'js', files: khanh.coreModules.includesDir + 'moo.fx.js'});
			this.loader.load(this.filename);
		},

		handleKeydown: function(e) {
			if(!e) var e = window.event;
			if (e.keyCode) code = e.keyCode;
			else if (e.which) code = e.which;
			if (code == 17 && !this.ctrlPressed)
				this.ctrlPressed = 1;
		},

		handleKeyup: function(e) {
			if(!e) var e = window.event;
			if (e.keyCode) code = e.keyCode;
			else if (e.which) code = e.which;
			if (code == 17 && this.ctrlPressed) {
				this.ctrlPressed = 0;
				this.resetTip();
			}
		},

		addtip: function(obj, tipText) {
			if (obj.title && isString(obj.title)) {

				obj.setAttribute('dttTitle', obj.title);
				obj.title = '';

				tipText = this.format(tipText);
				var that = this;
				if (!isDefined(obj.mouseoverHandler)) {
					obj.mouseoverHandler = function(evt) { that.enable(obj); };
					khanh.event.add(obj, 'mouseover', obj.mouseoverHandler);
				}

				if (!isDefined(obj.mouseoutHandler)) {
					obj.mouseoutHandler = function(evt) { that.hide(); };
					khanh.event.add(obj, 'mouseout', obj.mouseoutHandler);
				}

				// Can add event handler to document: (this.fadeOn ? document : obj)
				// but this will slow down position updates and interfere with other events
				if (!isDefined(obj.mousemoveHandler)) {
					obj.mousemoveHandler = function(evt) { that.move(evt); };
					khanh.event.add(obj, 'mousemove', obj.mousemoveHandler);
				}
			}
		},

		getTipText: function(obj) {
			return obj.getAttribute('dttTitle');
		},

		// Used by KCA WP plugin
		changeTip: function (obj, tipText) {
			if (!this.tip) return;
			obj.setAttribute('dttTitle', tipText);

			this.enable(obj);
		},

		format: function(s) {
			s = s.replace(/\r?\n/g, '<br />');
			return s;
		},

		move: function(evt) {
			this.getMousePos(evt);
			if (!this.boingOn) {
				this.setXY(evt);

				if ((this.enabled
				|| isDefined(this.effect)
				&& !this.effect.complete)
				&& !this.ctrlPressed)
					this.drawTip();
			}
		},

		setXY: function(evt) {
			/*
				It seems Firefox has a bug where if Ctrl is pressed, then mouse
				is moved, then Ctrl is released - either both, or one of the
				coord/scroll values are reset to zero. So, if either are zero
				then we use last known values to workaround this problem.
			*/
			if (!this.mouseX || !this.mouseY) {
				this.x = this.oldX;
				this.y = this.oldY;
				this.pX = this.oldPageX;
				this.pY = this.oldPageY;
			} else {
				this.x = this.mouseX;
				this.y = this.mouseY;
				this.pX = this.pageX;
				this.pY = this.pageY;
			}

			/*
				Save tip position when ctrl pressed,
				restore position when ctrl released.
			*/
			if (this.ctrlPressed) {
				this.mousePos = evt;
				this.oldX = this.mouseX;
				this.oldY = this.mouseY;
				this.oldPageX = this.pageX;
				this.oldPageY = this.pageY;
			}
		},

		drawTip: function() {
			//Find out how close the mouse is to the corner of the window
			var rightedge = (
				isIE && !isOpera ?
					ieTrueBody.clientWidth
				:	window.innerWidth - 18
			) - this.x - this.offsetX;
			var bottomedge = (
				isIE && !isOpera ?
					ieTrueBody.clientHeight
				:	window.innerHeight
			) - this.y - this.offsetY;
			var leftedge =
				this.offsetX < 0 ?
					this.offsetX * (-1)
				:	-1000;

			//if the horizontal distance isn't enough to accomodate the width of the tooltip
			if (rightedge < this.tip.offsetWidth) {
				//move the horizontal position of the tooltip to the left by it's width
				this.tip.style.left = parseInt((
					isIE ?
						ieTrueBody.scrollLeft
					:	window.pageXOffset
				) + this.x - this.tip.offsetWidth - 10) + 'px';
			} else if (this.pX < leftedge) {
				this.tip.style.left = '5px';
			} else {
				//horizontal position of the tooltip
				this.tip.style.left = parseInt(this.pX + this.offsetX) + 'px';
			}

			//same concept with the vertical position
			if (bottomedge < this.tip.offsetHeight) {
				this.tip.style.top = parseInt((
					isIE ?
						ieTrueBody.scrollTop
					:	window.pageYOffset
				) + this.y - this.tip.offsetHeight) + 'px';
			} else {
				this.tip.style.top = parseInt(this.pY + this.offsetY) + 'px';
			}
			this.tip.style.visibility = 'visible';
		},

		getMousePos: function(e) {
			var d = document.documentElement;
			this.scrollX =
				window.scrollX ?
					window.scrollX
				:	d.scrollLeft ?
						d.scrollLeft
					:	document.body.scrollLeft;
			this.scrollY =
				window.scrollY ?
					window.scrollY
				:	d.scrollTop ?
						d.scrollTop
					:	document.body.scrollTop;
			this.windowW =
				window.innerWidth ?
					window.innerWidth
				:	d.offsetWidth;
			this.windowH =
				window.innerHeight ?
					window.innerHeight
				:	d.offsetHeight;
			this.windowX =
				e.clientX - (
					isSafari ?
						this.scrollX
					:	0
				);
			this.windowY =
				e.clientY - (
					isSafari ?
						this.scrollY
					:	0
				);
			this.pageX =
				e.clientX + (
					!isSafari ?
						this.scrollX
					:	0
				);
			this.pageY =
				e.clientY + (
					!isSafari ?
						this.scrollY
					:	0
				);
			this.mouseX = e.clientX;
			this.mouseY = e.clientY;
		},

		resetTip: function(e) {
			var enabled = this.enabled;
			if (this.fadeOn) this.effect.hide();
			else if (!enabled) this.hide();
			this.move(this.mousePos);
			if (enabled) this.enable();
		},

		enable: function(obj) {
			this.enabled = 1;

			if (!this.tip) return;

			if (isDefined(obj)) {
				this.tipHTML = obj.getAttribute('dttTitle');

				this.tipHTML +=
				(/a href\=/g.test(this.tipHTML) ?
					this.ctrlText
				:	'');

				if (this.ctrlPressed) return;
			}

			this.tip.innerHTML = this.tipHTML;
			this.tip.style.width = '';
			if (this.tip.offsetWidth > this.maxWidth)
				this.tip.style.width = this.maxWidth + 'px';
		//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
			this.fade(this.enabled);
		//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		},

		hide: function() {
			this.enabled = 0;

			if (!this.tip || this.ctrlPressed) return;
		//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
			this.fade(this.enabled);
		//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		},

		//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
		fade: function(state) {
			var enabled = this.enabled;

			this.enabled = 0;
			if (!this.fadeOn) this.tip.style.visibility = 'hidden';

			if (this.fadeOn) {
				this.effect.complete = 0;
				this.effect.clearTimer();
				this.effect.custom((state ? this.opacMin : this.opacMax), (state ? this.opacMax : this.opacMin));
			}

			if (state && enabled) {
		//			if (!this.fadeOn) this.tip.style.visibility = 'visible';
				this.enabled = 1;
			}
		},

		// Functional programming method for maintaining scope in setTimeout
		boingLater: function() {
			var that = this;
			return (
				function() {
					if (that.enabled && !that.ctrlPressed) {
						if ((!that.mouseX || !that.mouseY)
							&& that.oldX && that.oldY) {
							that.mouseX = that.oldX;
							that.mouseY = that.oldY;
						}
						that.x = that.mouseX;
						that.y = that.mouseY;
						Math.round(that.pX += (that.ddx += ((that.pageX - that.pX - that.ddx) * that.boingX) / 100));
						Math.round(that.pY += (that.ddy += ((that.pageY - that.pY - that.ddy) * that.boingY) / 100));
						that.drawTip();
					}
					setTimeout(that.boing, that.boingTime);
				}
			);
		},
		//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

		filename: 'dtt.js',
		id: 'dtt',
		enabled: 0,
		tipCSS: 'position: absolute;'
			+ ' left: -999px;'
			+ ' top: -999px;'
			+ ' z-index: 999;'
			+ ' display: block;'
			+ ' visibility: hidden;',
		tipHTML: '<p>TEST</p>',
		ctrlText: '<cite id="ctrl-text">Hold Ctrl and move mouse to access links.</cite>',
		ctrlPressed: 0,
		initBegun: 0,
		initDone: 0,
		isLoading: 0,
		tags: {},
		titleIDs: {},

		x: 0,
		y: 0,
		ddx: 0,
		ddy: 0,
		pX: 0,
		pY: 0,
		mouseX: 0,
		mouseY: 0,
		mousePos: '',
		oldX: 0,
		oldY: 0,
		oldPageX: 0,
		oldPageY: 0
	};
}

khanh.coreModules = {
	IDs: '',
	onload: function() {
				if (khanh.event.domLoaded) khanh.dtt.init();
				else khanh.event.add(window, 'domload', function() { khanh.dtt.init(); });
			},
	scripts: ['core', 'event', 'loader'],
	includesDir: 'includes/',
	re: /dtt\.js/,
	url: '',
	
	getURL: function() {
		var scripts = document.getElementsByTagName('script');
		var img = document.createElement('img');
		for (var i = 0; i < scripts.length; i++)
			if (this.re.test(scripts[i].src)) {
				// Convert any reative URLs to full URLs
				img.src = scripts[i].src;
				this.url = img.src;
				break;
			}
	},

	load: function() {
		this.getURL();
		
		var head = document.getElementsByTagName('head')[0];
	
		for (var i = 0; i < this.scripts.length; i++)
			this.IDs += '<' + i + '>';
			
		for (var i = 0; i < this.scripts.length; i++) {
			var tag = document.createElement('script');
			tag.type = 'text/javascript';
			tag.src = this.url.replace(this.re, this.includesDir + this.scripts[i] + '.js');
			head.appendChild(tag);
			this.checkLoadState(i);
		}
	},
	
	checkLoadState: function(i) {
		eval("var isLoaded = typeof(khanh." + khanh.coreModules.scripts[i] + ") != 'undefined';");
		if (isLoaded) {
			khanh.coreModules.IDs = khanh.coreModules.IDs.replace(new RegExp('<' + i + '>'), '');
			if (!khanh.coreModules.IDs) khanh.coreModules.onload();
			return;
		}
		setTimeout('khanh.coreModules.checkLoadState(' + i + ')', 50);
	}
};

khanh.coreModules.load();
