/*
 * Glow JavaScript Library
 * Copyright (c) 2008 British Broadcasting Corporation
 */
/*@cc_on @*//*@if (@_jscript_version > 5.1)@*/;

glow.module("glow.tweens", "0.4.1", {
	require: [],
	implementation: function() {

		function _reverse(tween) {
			return function(t) {
				return 1 - tween(1 - t);
			}
		}
		
		return {

			linear: function() {
				return function(t) { return t; };
			},

			easeIn: function(strength) {
				strength = strength || 2;
				return function(t) {
					return Math.pow(1, strength - 1) * Math.pow(t, strength);
				}
			},

			easeOut: function(strength) {
				return _reverse(this.easeIn(strength));
			},

			easeBoth: function(strength) {
				return this.combine(this.easeIn(strength), this.easeOut(strength));
			},

			overshootIn: function(amount) {
				return _reverse(this.overshootOut(amount));
			},

			overshootOut: function(amount) {
				amount = amount || 1.70158;
				return function(t) {
					if (t == 0 || t == 1) { return t; }
					return ((t -= 1)* t * ((amount + 1) * t + amount) + 1);
				}
			},

			overshootBoth: function(amount) {
				return this.combine(this.overshootIn(amount), this.overshootOut(amount));
			},

			bounceIn: function() {
				return _reverse(this.bounceOut());
			},

			bounceOut: function() {
				return function(t) {
					if (t < (1 / 2.75)) {
						return 7.5625 * t * t;
					} else if (t < (2 / 2.75)) {
						return (7.5625 * (t -= (1.5 / 2.75)) * t + .75);
					} else if (t < (2.5 / 2.75)) {
						return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375);
					} else {
						return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375);
					}
				};
			},

			bounceBoth: function() {
				return this.combine(this.bounceIn(), this.bounceOut());
			},

			elasticIn: function(a, p) {
				return _reverse(this.elasticOut(a, p));
			},

			elasticOut: function(a, p) {
				return function (t) {
					if (t == 0 || t == 1) {
						return t;
					}
					if (!p) {
						p = 0.3;
					}
					if (!a || a < 1) {
						a = 1;
						var s = p / 4;
					} else {
						var s = p / (2 * Math.PI) * Math.asin(1 / a);
					}
					return a * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / p) + 1;
				}
			},

			elasticBoth: function(a, p) {
				p = p || 0.45;				
				return this.combine(this.elasticIn(a, p), this.elasticOut(a, p));
			},

			combine: function(tweenIn, tweenOut) {
				return function (t) {
					if (t < 0.5) {
						return tweenIn(t * 2) / 2;
					} else {
						return tweenOut((t - 0.5) * 2) / 2 + 0.5;
					}
				}
			}
		};
	}
});
;

glow.module("glow.anim", "0.4.1", {
	require: ["glow.tweens", "glow.events", "glow.dom"],
	implementation: function() {
		//private
		var manager,
			events = glow.events,
			dom = glow.dom,
			get = dom.get,
			debug = glow.debug,
			hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/,
			noNegatives = /width|height|padding|opacity/,
			usesYAxis = /height|top/,
			getUnit = /[\d\.]+(\D+)/,
			testElement = dom.create('<div style="position:absolute;visibility:hidden"></div>');

		(function() {
			var queue = [], //running animations
				queueLen = 0,
				intervalTime = 1, //ms between intervals
				interval; //holds the number for the interval
			manager = {

				addToQueue: function(anim) {
					//add the item to the queue
					queue[queueLen++] = anim;
					anim._playing = true;
					anim._timeAnchor = anim._timeAnchor || new Date().valueOf();
					if (!interval) {
						this.startInterval();
					}
				},

				removeFromQueue: function(anim) {
					for (var i = 0; i < queueLen; i++) {
						if (queue[i] == anim) {
							queue.splice(i, 1);
							anim._timeAnchor = null;
							anim._playing = false;
							//stop the queue if there's nothing in it anymore
							if (--queueLen == 0) {
								this.stopInterval();
							}
							return;
						}
					}
				},

				startInterval: function() {
					interval = window.setInterval(this.processQueue, intervalTime);
				},

				stopInterval: function() {
					window.clearInterval(interval);
					interval = null;
				},

				processQueue: function() {
					var anim, i, now = new Date().valueOf();
					for (i = 0; i < queueLen; i++) {
						anim = queue[i];
						if (anim.position == anim.duration) {
							manager.removeFromQueue(anim);
							//need to decrement the index because we've just removed an item from the queue
							i--;
							events.fire(anim, "complete");
							continue;
						}
						if (anim.useSeconds) {
							anim.position = (now - anim._timeAnchor) / 1000;
							if (anim.position > anim.duration) {
								anim.position = anim.duration;
							}
						} else {
							anim.position++;
						}
						anim.value = anim.tween(anim.position / anim.duration);
						events.fire(anim, "frame");
					}
				}
			};
		})();

		function convertCssUnit(element, fromValue, toUnit, axis) {
			var elmStyle = testElement[0].style,
				axisProp = (axis == "x") ? "width" : "height",
				startPixelValue,
				toUnitPixelValue;
			//reset stuff that may affect the width / height
			elmStyle.margin = elmStyle.padding = elmStyle.border = "0";
			startPixelValue = testElement.css(axisProp, fromValue).insertAfter(element)[axisProp]();
			//using 10 of the unit then dividing by 10 to increase accuracy
			toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10;
			testElement.remove();
			return startPixelValue / toUnitPixelValue;
		}

		function keepWithinRange(num, start, end) {
			if (start !== undefined && num < start) {
				return start;
			}
			if (end !== undefined && num > end) {
				return end;
			}
			return num;
		}

		function buildAnimFunction(element, spec) {
			var cssProp,
				r = ["a=(function(){"],
				rLen = 1,
				fromUnit,
				unitDefault = [0,"px"],
				to,
				from,
				unit,
				a;
			
			for (cssProp in spec) {
				r[rLen++] = 'element.css("' + cssProp + '", ';
				//fill in the blanks
				to = spec[cssProp].to;
				if ((from = spec[cssProp].from) === undefined) {
					if (cssProp == "font-size" || cssProp == "background-position") {
						throw new Error("From value must be set for " + cssProp);
					}
					from = element.css(cssProp);
				}
				//TODO help some multi value things?
				if (hasUnits.test(cssProp)) {
					//normalise the units for unit-ed values
					unit = (getUnit.exec(spec[cssProp].to) || unitDefault)[1];
					fromUnit = (getUnit.exec(from) || unitDefault)[1];
					//make them numbers, we have the units seperate
					from = parseFloat(from) || 0;
					to = parseFloat(to) || 0;
					//if the units don't match, we need to have a play
					if (from && unit != fromUnit) {
						if (cssProp == "font-size") {
							throw new Error("Units must be the same for font-size");
						}
						from = convertCssUnit(element, from + fromUnit, unit, usesYAxis.test(cssProp) ? "y" : "x");
					}
					if (noNegatives.test(cssProp)) {
						r[rLen++] = 'keepWithinRange((' + (to - from) + ' * this.value) + ' + from + ', 0) + "' + unit + '"';
					} else {
						r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from + ' + "' + unit + '"';
					}
				} else if (! (isNaN(from) || isNaN(to))) { //both pure numbers
					from = Number(from);
					to = Number(to);
					r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from;
				} else if (cssProp.indexOf("color") != -1) {
					to = dom.parseCssColor(spec[cssProp].to);
					if (! glow.lang.hasOwnProperty(from, "r")) {
						from = dom.parseCssColor(from);
					}
					r[rLen++] = '"rgb(" + keepWithinRange(Math.round(' + (to.r - from.r) + ' * this.value + ' + from.r +
						'), 0, 255) + "," + keepWithinRange(Math.round(' + (to.g - from.g) + ' * this.value + ' + from.g +
						'), 0, 255) + "," + keepWithinRange(Math.round(' + (to.b - from.b) + ' * this.value + ' + from.b +
						'), 0, 255) + ")"';
				} else if (cssProp == "background-position") {
					var vals = {},
						fromTo = ["from", "to"],
						unit = (getUnit.exec(from) || unitDefault)[1];
					vals.fromOrig = from.toString().split(/\s/);
					vals.toOrig = to.toString().split(/\s/);
					
					if (vals.fromOrig[1] === undefined) {
						vals.fromOrig[1] = "50%";
					}
					if (vals.toOrig[1] === undefined) {
						vals.toOrig[1] = "50%";
					}
					
					for (var i = 0; i < 2; i++) {
						vals[fromTo[i] + "X"] = parseFloat(vals[fromTo[i] + "Orig"][0]);
						vals[fromTo[i] + "Y"] = parseFloat(vals[fromTo[i] + "Orig"][1]);
						vals[fromTo[i] + "XUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][0]) || unitDefault)[1];
						vals[fromTo[i] + "YUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][1]) || unitDefault)[1];
					}
					
					if ((vals.fromXUnit !== vals.toXUnit) || (vals.fromYUnit !== vals.toYUnit)) {
						throw new Error("Mismatched axis units cannot be used for " + cssProp);
					}
					
					r[rLen++] = '(' + (vals.toX - vals.fromX) + ' * this.value + ' + vals.fromX + ') + "' + vals.fromXUnit + ' " + (' +
								(vals.toY - vals.fromY) + ' * this.value + ' + vals.fromY + ') + "' + vals.fromYUnit + '"';
				}
				r[rLen++] = ');';
			}
			r[rLen++] = "})";
			return eval(r.join(""));
		}
		
		//public
		var r = {}; //return object
		
		r.css = function(element, duration, spec, opts) {
			element = get(element);
			
			var anim = new r.Animation(duration, opts),
				cssProp;
			
			events.addListener(anim, "frame", buildAnimFunction(element, spec));
			return anim;
		};

		r.Animation = function(duration, opts) {
			opts = glow.lang.apply({
				useSeconds: true,
				tween: glow.tweens.linear()
			}, opts);

			this._playing = false;

			this._timeAnchor = null;

			this.duration = duration;

			this.useSeconds = opts.useSeconds;

			this.tween = opts.tween;

			this.position = 0;

			this.value = 0;
	
		};
		r.Animation.prototype = {

			start: function() {
				if (this._playing) {
					this.stop();
				}
				var e = events.fire(this, "start");
				if (e.defaultPrevented()) { return this; }
				this.position = 0;
				manager.addToQueue(this);
				
				return this;
			},

			stop: function() {
				if (this._playing) {
					var e = events.fire(this, "stop");
					if (e.defaultPrevented()) { return this; }
					manager.removeFromQueue(this);
				}
				return this;
			},

			resume: function() {
				if (! this._playing) {
					var e = events.fire(this, "resume");
					if (e.defaultPrevented()) { return this; }
					//set the start time to cater for the pause
					this._timeAnchor = new Date().valueOf() - (this.position * 1000);
					manager.addToQueue(this);
				}
				return this;
			},

			isPlaying: function() {
				return this._playing;
			},

			goTo: function(pos) {
				this._timeAnchor = new Date().valueOf() - ((this.position = pos) * 1000);
				this.value = this.tween(this.duration && this.position / this.duration);
				events.fire(this, "frame");
				return this;
			}


		};

		r.Timeline = function(channels, opts) {

			//normalise channels so it's always an array of array(s)
			this._channels = (channels[0] && channels[0].push) ? channels : [channels];

			this._channelPos = [];

			this._playing = false;

			this.loop = !!(opts && opts.loop);
			
			var i, j, iLen, jLen,
				channel,
				allChannels = this._channels,
				totalDuration = 0,
				channelDuration;
			
			//process channels
			for (i = 0, iLen = allChannels.length; i < iLen; i++) {
				channel = allChannels[i];
				channelDuration = 0;
				for (j = 0, jLen = channel.length; j < jLen; j++) {
					//create a blank animation for time waiting
					if (typeof channel[j] == "number") {
						channel[j] = new r.Animation(channel[j]);
					}
					if (channel[j] instanceof r.Animation) {
						if (! channel[j].useSeconds) {
							throw new Error("Timelined animations must be timed in seconds");
						}
						channel[j]._timelineOffset = channelDuration * 1000;
						channelDuration += channel[j].duration;
						channel[j]._channelIndex = i;
					}
				}
				totalDuration = Math.max(channelDuration, totalDuration);
			}

			this._controlAnim = new r.Animation(totalDuration);
			events.addListener(this._controlAnim, "frame", this._processFrame, this);
			events.addListener(this._controlAnim, "complete", this._complete, this);
		};
		r.Timeline.prototype = {

			_advanceChannel: function(i) {
				//is there a next animation in the channel?
				var currentAnim = this._channels[i][this._channelPos[i]],
					nextAnim = this._channels[i][++this._channelPos[i]];
					
				if (currentAnim && currentAnim._playing) {
					currentAnim._playing = false;
					events.fire(currentAnim, "complete");
				}
				if ((nextAnim) !== undefined) {
					if (typeof nextAnim == "function") {
						nextAnim();
						this._advanceChannel(i);
					} else {
						nextAnim.position = 0;
						nextAnim._channelIndex = i;
						events.fire(nextAnim, "start");
						nextAnim._playing = true;
					}
				}
			},
			_complete: function() {
				if (this.loop) {
					this.start();
					return;
				}
				events.fire(this, "complete");
			},
			_processFrame: function() {
				var i, len, anim, controlAnim = this._controlAnim,
					msFromStart = (new Date().valueOf()) - controlAnim._timeAnchor;
				
				for (i = 0, len = this._channels.length; i < len; i++) {
					if (! (anim = this._channels[i][this._channelPos[i]])) { continue; }
					anim.position = (msFromStart - anim._timelineOffset) / 1000;
					if (anim.position > anim.duration) {
						anim.position = anim.duration;
					}
					anim.value = anim.tween(anim.position / anim.duration);
					events.fire(anim, "frame");
					if (anim.position == anim.duration) {
						this._advanceChannel(i);
					}
				}
			},

			start: function() {
				var e = events.fire(this, "start");
				if (e.defaultPrevented()) { return this; }
				var i, iLen, j, jLen, anim;
				this._playing = true;
				for (i = 0, iLen = this._channels.length; i < iLen; i++) {
					this._channelPos[i] = -1;
					this._advanceChannel(i);
					for (j = this._channels[i].length; j; j--) {
						anim = this._channels[i][j];
						if (anim instanceof r.Animation) {
							anim.goTo(0);
						}
					}
				}
				this._controlAnim.start();
			},

			stop: function() {
				if (this._playing) {
					var e = events.fire(this, "stop");
					if (e.defaultPrevented()) { return this; }
					this._playing = false;
					var anim;
					for (var i = 0, len = this._channels.length; i<len; i++) {
						anim = this._channels[i][this._channelPos[i]];
						if (anim instanceof r.Animation && anim._playing) {
							events.fire(anim, "stop");
							anim._playing = false;
						}
					}
					this._controlAnim.stop();
				}
			},

			resume: function() {
				if (! this._playing) {
					var e = events.fire(this, "resume");
					if (e.defaultPrevented()) { return this; }
					this._playing = true;
					var anim;
					for (var i = 0, len = this._channels.length; i<len; i++) {
						anim = this._channels[i][this._channelPos[i]];
						if (anim instanceof r.Animation && !anim._playing) {
							events.fire(anim, "resume");
							anim._playing = true;
						}
					}
					this._controlAnim.resume();
				}
			},

			isPlaying: function() {
				return this._playing;
			}


		};
		return r;
	}
});
;
glow.module("glow.dragdrop", "0.4.1", {
	require: ["glow.tweens", "glow.events", "glow.dom", "glow.anim"],
	implementation: function() {
		var events		   = glow.events,
			addListener	   = events.addListener,
			fire		   = events.fire,
			removeListener = events.removeListener,
			dom			   = glow.dom,
			$			   = dom.get,
			create		   = dom.create;

		//public
		var r = {}, 
			_zIndex = 1000,
			_ieStrict = (document.compatMode == "CSS1Compat" && glow.env.ie >= 5) ? true : false,
			_ieTrans= (document.compatMode != "CSS1Compat" && glow.env.ie >= 5) ? true : false,
			_ie = glow.env.ie >= 5;
		
		function memoize (clss, name) {
			var orig = clss.prototype[name];
			var cachedName = 'cached_' + name;
			clss.prototype[name] = function () {
				if (cachedName in this) return this[cachedName];
				return this[cachedName] = orig.apply(this, arguments);
			};
		}
		function memoizeNamed (clss, methodName) {
			var orig = clss.prototype[methodName];
			var cachedName = 'cached_' + methodName;
			clss.prototype[methodName] = function (name) {
				if (! this[cachedName]) this[cachedName] = {};
				if (name in this[cachedName]) return this[cachedName][name];
				return this[cachedName][name] = orig.apply(this, arguments);
			};
		}
		function reset (obj, names) {
			for (var i = 0, l = names.length; i < l; i++) {
				delete obj['cached_' + names[i]];
			}
		}

		var Box = function (el) {
			this.el = el;
		};

		Box.prototype = {
			val: function (style) {
				var val = parseInt(this.el.css(style));
				// TODO - fix dom so margin-left return value is always defined?
//				if (isNaN(val)) throw 'got NaN in val for ' + style + ': ' + this.el.css(style);
				return isNaN(val) ? 0 : val;
//				return val;
			},
			width: function () {
				return this.borderWidth()
					 - this.val('border-left-width')
					 - this.val('padding-left')
					 - this.val('padding-right')
					 - this.val('border-right-width');
			},
			height: function () {
				return this.borderHeight()
					 - this.val('border-top-width')
					 - this.val('padding-top')
					 - this.val('padding-bottom')
					 - this.val('border-bottom-width');
			},
			offsetParentPageTop: function () {
				var el = this.el[0], pos, top;
				while (el = el.offsetParent) {
					pos = $(el).css('position');
					if (pos == 'absolute' || pos == 'fixed' || pos == 'relative') break;
				}
				if (! el) return 0;
  				top = el.offsetTop;
				while (el = el.offsetParent) {
					top += el.offsetTop;
				}
				return top;
			},
			// TODO - should the dom module have this function?
			// TODO - this seems to miss border width of intermediary elements in ie, take account of this
			offsetTop: function () {
				var res = 0, el = this.el[0], pos, top;
				if (glow.env.ie) {
					do {
						top = el.offsetTop;
						if (! isNaN(top)) res += top;
						el = el.offsetParent;
						if (el) pos = $(el).css('position');
					} while (el && ! (pos == 'absolute' || pos == 'fixed' || pos == 'relative'));
				}
				else {
					res = el.offsetTop;
				}
				if (glow.env.opera) {
					var parentBorder = parseInt($(el.offsetParent).css('border-top-width'));
					res -= isNaN(parentBorder) ? 0 : parentBorder;
				}
				//see https://bugzilla.mozilla.org/show_bug.cgi?id=307502
				if (glow.env.gecko) {
					var doc = document,
						compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(el.offsetParent, null) || doc.defaultView.getComputedStyle),
						parentBorderVal;
					if (compStyle.position == 'relative' && compStyle.overflow == 'hidden' && (parentBorderVal = parseInt(compStyle.borderTopWidth))) {
						res += parentBorderVal;
					}
				}
				return res;
			},
			// TODO - should the dom module have this function?
			offsetLeft: function () {
				
				var res = 0, el = this.el[0], pos, left;
				if (glow.env.ie) {
					do {
						left = el.offsetLeft;
						if (! isNaN(left)) res += left;
						el = el.offsetParent;
						if (el) pos = $(el).css('position');
					} while (el && ! (pos == 'absolute' || pos == 'fixed' || pos == 'relative'));
				}
				else {
					res = el.offsetLeft;
				}
				if (glow.env.opera) {
					var parentBorder = parseInt($(el.offsetParent).css('border-left-width'));
					res -= isNaN(parentBorder) ? 0 : parentBorder;
				}
				//see https://bugzilla.mozilla.org/show_bug.cgi?id=307502
				if (glow.env.gecko) {
					var doc = document,
						compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(el.offsetParent, null) || doc.defaultView.getComputedStyle),
						parentBorderVal;
					if (compStyle.position == 'relative' && compStyle.overflow == 'hidden' && (parentBorderVal = parseInt(compStyle.borderLeftWidth))) {
						res += parentBorderVal;
					}
				}
				return res;
			},
			borderWidth: function () {
				var width = this.el[0].offsetWidth;
				if (glow.env.khtml) {
					width -= this.val('margin-left')
							+ this.val('margin-right')	
							+ this.val('border-left-width')
							+ this.val('border-right-width');
				}
				return width;
			},
			borderHeight: function () {
				if (this._logicalBottom) {
					return this._logicalBottom - this.offsetTop();
				}
				var height = this.el[0].offsetHeight;
				if (glow.env.khtml) {
					height -= this.val('margin-top')
							+ this.val('margin-bottom')	
							+ this.val('border-top-width')
							+ this.val('border-bottom-width');
				}
				return height;
			},
			outerWidth: function () {
				return this.borderWidth() + this.val('margin-left') + this.val('margin-right');
			},
			outerHeight: function () {
				return this.borderHeight() + this.val('margin-top') + this.val('margin-bottom');
			},
			innerLeftPos: function () {
				return this.offsetLeft()
					 + this.val('border-left-width')
					 + this.val('padding-left');
			},
			innerTopPos: function () {
				return this.offsetTop()
					 + this.val('border-top-width')
					 + this.val('padding-top');
			},
			surroundWidth: function () {
				return this.val('margin-left')
					 + this.val('border-left-width')
					 + this.val('padding-left')
					 + this.val('padding-right')
					 + this.val('border-right-width')
					 + this.val('margin-right');
			},
			surroundHeight: function () {
				return this.val('margin-top')
					 + this.val('border-top-width')
					 + this.val('padding-top')
					 + this.val('padding-bottom')
					 + this.val('border-bottom-width')
					 + this.val('margin-bottom');
			},
			verticalCenter: function () {
				return this.offsetTop() + (this.outerHeight() / 2);
			},
			horizontalCenter: function () {
				return this.offsetTop() + (this.outerWidth() / 2);
			}

   		};

		for (var i in Box.prototype) {
			if (i == 'val') memoizeNamed(Box, i);
			else memoize(Box, i);
		}

		glow.lang.apply(Box.prototype, {
			resetPosition: function () {
				reset(this, [
					'offsetTop',
					'offsetLeft',
					'borderTopPos',
					'borderLeftPos',
					'innerTopPos',
					'innerLeftPos',
					'verticalCenter',
					'horizontalCenter'
				]);
			},
			setLogicalBottom: function (bottom) {
				this._logicalBottom = bottom;
			},
			boundsFor: function (childBox) {
				var top, left, pos = this.el.css('position');
				if (pos == 'relative' || pos == 'absolute' || pos == 'fixed') {
				    top = left = 0;
				}
				else {
					top = this.innerTopPos();
					left = this.innerLeftPos();
				}
				return [
					top,										   // top
					left + this.width() - childBox.outerWidth(),   // right
					top  + this.height() - childBox.outerHeight(), // bottom
					left										   // left
				];
			},
			outerBounds: function () {
				var left = this.offsetLeft(),
					top = this.offsetTop();
				return [
					top,
					left + this.borderWidth(),
					top + this.borderHeight(),
					left
				];
			},
			intersectSize: function (that, touches) {
				var a = this.outerBounds(), b = that.outerBounds();
				if (touches) {
					a[1]++; b[1]++; a[2]++; b[2]++;
				}
				return (
					a[2] < b[0] ? 0 :
					b[2] < a[0] ? 0 :
					a[0] < b[0] ? (a[2] < b[2] ? a[2] - b[0] : b[2] - b[0]) :
					b[2] < a[2] ? b[2] - a[0] : a[2] - a[0]
				) * (
					a[1] < b[3] ? 0 :
					b[1] < a[3] ? 0 :
					a[3] < b[3] ? (a[1] < b[1] ? a[1] - b[3] : b[1] - b[3]) :
					b[1] < a[1] ? b[1] - a[3] : a[1] - a[3]
				);
			},
			sizePlaceholder: function (placeholder, pos, startLeft, startTop) {
				var placeholderBox = new Box(placeholder),
					el = this.el,
					position = pos || el.css('position');
				placeholder.css('display', 'none');
				
				el.after(placeholder);
				
				placeholder.css('width', (this.outerWidth() - placeholderBox.surroundWidth()) + 'px');
				placeholder.css('height', (this.outerHeight() - placeholderBox.surroundHeight()) + 'px');
				
				placeholder.remove();
				
				placeholder.css('display', 'block');
				
				if (position != 'static') {
					placeholder.css('left', startLeft + 'px');
					placeholder.css('top', startTop + 'px');
				}
				placeholder.css('position', position);
			},
			contains: function (box) {
				var bounds = this.boundsFor(box), top = box.offsetTop(), left = box.offsetLeft();
				return top  >= bounds[0]  // top
					&& left <= bounds[1]  // right
					&& top  <= bounds[2]  // bottom
					&& left >= bounds[3]; // left
			},
			containsPoint: function (offset) {
				var elementOffset = this.el.offset();
				return offset.x >= elementOffset.x
					&& offset.y >= elementOffset.y
					&& offset.x <= elementOffset.x + this.borderWidth()
					&& offset.y <= elementOffset.y  + this.borderHeight();
			},
			positionedAncestorBox: function () {
				var el = this.el.parent(), pos;
				while (el[0]) {
					pos = el.css('position') || 'static';
					if (pos == 'relative' || pos == 'absolute' || pos == 'fixed')
						return new Box(el);
					el = el.parent();
				}
				return null;
			}
		});

		function placeholderElement (el) {
			var tag = el[0].tagName.toLowerCase() == 'li' ? 'li' : 'div';
			var placeholder = create('<' + tag + '></' + tag + '>');
			if (tag == 'li') placeholder.css('list-style-type', 'none');
			return placeholder;
		}
		r.Draggable = function (el, opts) {
			this.element = $(el);
			this._opts = opts = glow.lang.apply({
				dragPrevention   : ['input', 'textarea', 'button', 'select', 'option', 'a'],
				placeholder	     : 'spacer',
				placeholderClass : 'glow-dragdrop-placeholder'
			}, opts || {});

			this._preventDrag = [];
			for (var i = 0, l = opts.dragPrevention.length; i < l; i++) {
				this._preventDrag[i] = opts.dragPrevention[i].toLowerCase();
			}

			if (opts.container) { this.container = $(opts.container); }
			this._handle = opts.handle && this.element.get(opts.handle) || this.element;

			if (opts.dropTargets) this.dropTargets = $(opts.dropTargets);

				//used for IE literal edge case bug fix
				//this._mouseUp = true;
				//bug fix to get document.body.scrollTop to return true value (not 0) if using transitional 4.01 doctype
				//get('body')[0].style.overflow = 'auto';
				//this._opts = o, this._targetCoords = [], this.isOverTarget = false;

			var listeners = this._listeners = [],
				i = 0;

			if (opts.onDrag)  listeners[i++] = addListener(this, 'drag',  this._opts.onDrag,  this);
			if (opts.onEnter) listeners[i++] = addListener(this, 'enter', this._opts.onEnter, this);
			if (opts.onLeave) listeners[i++] = addListener(this, 'leave', this._opts.onLeave, this);
			if (opts.onDrop)  listeners[i++] = addListener(this, 'drop',  this._opts.onDrop,  this);

			this._dragListener = addListener(this._handle, 'mousedown', this._startDragMouse, this);

			return;
		};
		//var applyFloatBugfix = glow.env.ie;

		r.Draggable.prototype = {
			_createPlaceholder: function () {
				var el = this.element,
					placeholder,
					box = this._box;
				
				
				if (this._opts.placeholder == 'clone') {
					placeholder = el.clone();
				}
				else { // placeholder == 'spacer'
					placeholder = placeholderElement(el);
				}
				if (this._opts.placeholderClass)
					placeholder.addClass(this._opts.placeholderClass);
				box.sizePlaceholder(placeholder, null, this._startLeft, this._startTop);
				el.after(placeholder);
				this._placeholder = placeholder;
			},
			_removePlaceholder: function () {
				this._placeholder.remove();
			},
			_resetPosition: function () {
				var origPos = this._preDragPosition,
					el = this.element,
					box = this._box,
					startOffset = this._startOffset,
					pos = el.css('position'),
					newLeft,
					newTop;
				box.resetPosition();
				var	offset = {
					x: box.offsetLeft() - box.val('margin-left'),
					y: box.offsetTop() - box.val('margin-top')
				};

				if (this._placeholder || this._dropIndicator) {
					el.remove();
				}
				if (origPos == 'static' && offset.y == startOffset.y && offset.x == startOffset.x) {
					el.css('position', 'static');
					el.css('left', '');
					el.css('top', '');
				}
				else {
					el.css('z-index', this._preDragZIndex);
					el.css('position', origPos == 'static' ? 'relative' : origPos);
					if (origPos == 'static') {
						newLeft = offset.x - startOffset.x;
						newTop = offset.y - startOffset.y;
					}
					else if (origPos == 'relative' && pos != 'relative') {
						newLeft = this._startLeft + (offset.x - startOffset.x);
						newTop = this._startTop + (offset.y - startOffset.y);
					}
					if (pos != origPos) {
						el.css('left', newLeft ? newLeft + 'px' : '');
						el.css('top', newTop ? newTop + 'px' : '');
					}
				}
				if (this._dropIndicator) {
					var parent = this._dropIndicator.parent()[0];
					if (parent)	parent.replaceChild(el[0], this._dropIndicator[0]);
					delete this._dropIndicator;
					if (this._placeholder) {
						this._placeholder.remove();
						delete this._placeholder;
					}
				}
				else if (this._placeholder) {
					var parent = this._placeholder.parent()[0];
					if (parent)	parent.replaceChild(el[0], this._placeholder[0]);
					delete this._placeholder;
				}
			},
			_startDragMouse: function (e) {
				var preventDrag = this._preventDrag,
					source = e.source,
					tag = source.tagName.toLowerCase();

				for (var i = 0, l = preventDrag.length; i < l; i++) {
					if (preventDrag[i] == tag) {
						return;
					}
				}

				if (this._dragging == 1)
					return this.endDrag();
				else if (this._dragging)
					return;

				// _dragging set to 1 during drag, 2 while ending drag and back to 0 when ready for new drag
				this._dragging = 1;

				var el = this.element,
					container = this.container,
					opts = this._opts,
					box = this._box = new Box(el);

				this._preDragPosition = el.css('position');

				if (container) {
					this._containerBox = new Box(container);
					this._bounds = this._containerBox.boundsFor(box);
				}
				else {
					delete this._bounds;
				}

				this._mouseStart = {
					x: e.pageX,
					y: e.pageY
				};
	
				var startOffset = this._startOffset = {
					x: box.offsetLeft(),
					y: box.offsetTop()
				};

				this._preDragStyle = el.attr('style');

				this._preDragZIndex = el.css('z-index');

				el.css('z-index', _zIndex++);

				this._startLeft = el[0].style.left ? parseInt(el[0].style.left) : 0;
				this._startTop = el[0].style.top ? parseInt(el[0].style.top) : 0;

				if (opts.placeholder && opts.placeholder != 'none')
					this._createPlaceholder();

				el.css('position', 'absolute');
				el.css('left', startOffset.x + 'px');
				el.css('top', startOffset.y + 'px');

				if(_ieStrict) {
					this._scrollY = document.documentElement.scrollTop;
					this._innerHeight = document.documentElement.clientHeight;
				}
				else if(_ieTrans){
					this._scrollY = document.body.scrollTop;
					this._innerHeight = document.body.clientHeight;
				}
				else {
					this._scrollY = window.scrollY;
					this._innerHeight = window.innerHeight;
				}

				var documentHeight = $(document).height();
				this._bodyHeight  = documentHeight < this._innerHeight ? this._innerHeight : documentHeight;

				//fire the drag event
				fire(this, 'drag');

				var cancelFunc = function () { return false },
					doc = document.documentElement;
				
				if (this.dropTargets) {
					var event = new events.Event();
					event.draggable = this;
					for (var i = 0, l = this.dropTargets.length; i < l; i++) {
						fire(this.dropTargets[i], 'active', event);
					}
					
					this._mousePos = {
						x: e.pageX,
						y: e.pageY
					};
					this._testForDropTargets();
				}

				this._dragListeners = [				
					addListener(doc, 'selectstart', cancelFunc),
					addListener(doc, 'dragstart',   cancelFunc),
					addListener(doc, 'mousedown',   cancelFunc),
					addListener(doc, 'mousemove',   this._dragMouse, this),
					addListener(doc, 'mouseup',	 this._releaseElement, this)
				];
				return false;
			},
			_dragMouse: function (e) {
				var element = this.element,
					newX = this._opts.axis == 'y' ?
						this._startOffset.x:
						(this._startOffset.x + e.pageX - this._mouseStart.x),
					newY = this._opts.axis == 'x' ?
						this._startOffset.y:
						(this._startOffset.y + e.pageY - this._mouseStart.y),
					bounds = this._bounds;

				// only pay for the function call if we have a container or an axis (consider coding in axis here)
				if (bounds) {
					newX = newX < bounds[3] ? bounds[3] : newX > bounds[1] ? bounds[1] : newX;
					newY = newY < bounds[0] ? bounds[0] : newY > bounds[2] ? bounds[2] : newY;
				}

				element[0].style.left = newX + 'px';
				element[0].style.top = newY + 'px';

				//if there are dragTargets check if the draggable is over the target
				if (this.dropTargets) {
					this._mousePos = { x: e.pageX, y: e.pageY };
				}
				// check for IE mouseup outside of page boundary
				if(_ie && e.nativeEvent.button == 0) {
					this._releaseElement(e);
					return false;
				};
				return false;
			},
			_testForDropTargets: function (fromTimeout) {

				if (! this._lock) this._lock = 0;
				if (fromTimeout) this._lock--;
				else if (this.lock) return;

				if (this._dragging != 1) return;

				var previousTarget = this.activeTarget,
					activeTarget,
					targets = this.dropTargets,
					target,
					targetBox,
					box = this._box,
					mousePos = this._mousePos;

				box.resetPosition();

				var maxIntersectSize = 0;
				for (var i = 0, l = targets.length; i < l; i++) {
					target = targets[i];
					targetBox = target._box;
					if (target._opts.tolerance == 'contained') {
						if (targetBox.contains(box)) {
							activeTarget = target;
							break;
						}
					}
					else if (target._opts.tolerance == 'cursor') {
						if (targetBox.containsPoint(mousePos)) {
							activeTarget = target;
							break;
						}
					}
					else {
						var intersectSize = targetBox.intersectSize(box, true);
						if (intersectSize > maxIntersectSize) {
							maxIntersectSize = intersectSize;
							activeTarget = target;
						}
					}
				}
				this.activeTarget = activeTarget;
				
				// enter events
				if (activeTarget !== previousTarget) {
					if (activeTarget) {
						// enter on the target
						var draggableEnterEvent = new events.Event();
						draggableEnterEvent.draggable = this;
						fire(activeTarget, 'enter', draggableEnterEvent);

						// enter on this (the draggable)
						var enterTargetEvent = new events.Event();
						enterTargetEvent.dropTarget = activeTarget;
						fire(this, 'enter', enterTargetEvent);
					}

					if (previousTarget) {
						// leave on target
						var draggableLeaveEvent = new events.Event();
						draggableLeaveEvent.draggable = this;
						fire(previousTarget, 'leave', draggableLeaveEvent);
						
						// leave on this (draggable)
						var leaveTargetEvent = new events.Event();
						leaveTargetEvent.dropTarget = previousTarget;
						fire(this, 'leave', leaveTargetEvent);
					}
				}
				// place the drop indicator in the drop target (not in the drop target class for speed)
				if (activeTarget && activeTarget._opts.dropIndicator != 'none') {
					var childBox,
						childBoxes = activeTarget._childBoxes,
						children = activeTarget._children;
					box.resetPosition();
					var totalHeight = activeTarget._box.innerTopPos();
					var draggablePosition = mousePos.y - box.offsetParentPageTop();
					var placed = 0;
					for (var i = 0, l = childBoxes.length; i < l; i++) {
						if (children[i] == this.element[0]) continue;
						childBox = childBoxes[i];
						totalHeight += childBox.outerHeight();
						if (draggablePosition <= totalHeight) {
							if (activeTarget._dropIndicatorAt != i) {
								$(childBox.el).before(activeTarget._dropIndicator);
								activeTarget._dropIndicatorAt = i;
							}
							placed = 1;
							break;
						}
					}
					if (! placed) {
						if (childBox) {
							$(childBox.el).after(activeTarget._dropIndicator);
							activeTarget._dropIndicatorAt = i + 1;
						}
						else {
							activeTarget.element.append(activeTarget._dropIndicator);
							activeTarget._dropIndicatorAt = 0;
						}
					}
				}

				this._lock++;
				var this_ = this;
				setTimeout(function () { this_._testForDropTargets(1) }, 100);
			},
			_releaseElement: function () {
				if (this._dragging != 1) return;
				this._dragging = 2;

				var i, l;

				//call the onInactive function on all the dropTargets for this draggable
				var dropTargets = this.dropTargets,
					activeTarget = this.activeTarget;

				if (dropTargets) {
					for (i = 0, l = dropTargets.length; i < l; i++) {
						var event = new events.Event();
						event.draggable = this;
						event.droppedOnThis = activeTarget && activeTarget == dropTargets[i];
						fire(dropTargets[i], 'inactive', event);
					}
				}

				if (activeTarget) {
					var event = new events.Event();
					event.draggable = this;
					fire(activeTarget, 'drop', event);
				}
		
				var dragListeners = this._dragListeners;
				for (i = 0, l = dragListeners.length; i < l; i++) {
					events.removeListener(dragListeners[i]);
				}
				
				var dropEvent = fire(this, "drop");
				if (! dropEvent.defaultPrevented() && this.dropTargets) {
					this.returnHome();
				}
				else {
					this.endDrag();
				}

			},
			endDrag: function(){
				if (this._dragging != 2) return;
				this._dragging = 0;

				//remove any helpers/placeholders
				if (this._reset) {
					this._reset();
					delete this._reset;
				}
			
				if (this.placeholder) {
					this.placeholder.remove();
				}
				this._resetPosition();
				delete this.activeTarget;
				fire(this, "afterDrop");
			},
			returnHome: function(tween){
				var mytween = (tween) ? tween : glow.tweens.linear(),
					el = this.element,
					distance = Math.pow(
						Math.pow(this._startOffset.x - this._box.offsetLeft(), 2)
						+ Math.pow(this._startOffset.y - this._box.offsetTop(), 2),
						0.5
					),
					duration = 0.3 + (distance / 1000);

				var channels = [[
					glow.anim.css(el, duration, {
							left: { from: this._box.offsetLeft(), to : this._startOffset.x },
							top : { from: this._box.offsetTop(), to : this._startOffset.y }
					}, { tween: mytween })
				]];

				if (this._dropIndicator) {
					channels.push([glow.anim.css(this._dropIndicator, duration - 0.1, { opacity: { to: 0 } })]);
				}

				var timeline = new glow.anim.Timeline(channels);
				addListener(timeline, 'complete', function () {
					this.endDrag();
				}, this);
				timeline.start();
				return;
			}
		};
		
		
		var dropTargetId = 0;
		r.DropTarget = function(el, opts) {
			el = this.element = $(el);
			if (! el.length) throw 'no element passed into DropTarget constuctor';
			if (el.length > 1) throw 'more than one element passed into DropTarget constructor';

			// id is for indexing drop targets in an object for getting to them quickly
			this._id = ++dropTargetId;

			this._opts = opts = glow.lang.apply({
				dropIndicator	   : 'none',
				acceptDropOutside  : false,
				dropIndicatorClass : 'glow-dragdrop-dropindicator',
				tolerance          : 'intersect'
			}, opts || {});

			if (opts.onActive)   addListener(this, 'active',   opts.onActive);
			if (opts.onInactive) addListener(this, 'inactive', opts.onInactive);
			if (opts.onEnter)	 addListener(this, 'enter',	opts.onEnter);
			if (opts.onLeave)	 addListener(this, 'leave',	opts.onLeave);
			if (opts.onDrop)	 addListener(this, 'drop',	 opts.onDrop);

			this._activeListener = addListener(this, 'active', this._onActive);
			this._activeListener = addListener(this, 'inactive', this._onInactive);

			return this;
		};
		

		r.DropTarget.prototype = {
			setLogicalBottom: function (bottom) {
				this._logicalBottom = bottom;
			},
			
			_onActive: function (e) {
				var draggable = e.draggable;

				this._box = new Box(this.element);
				if (this._logicalBottom) this._box.setLogicalBottom(this._logicalBottom);

				if (this._opts.dropIndicator == 'none') return;

				this._onEnterListener = addListener(this, 'enter', this._onEnter);
				this._onLeaveListener = addListener(this, 'leave', this._onLeave);

				this._dropIndicator = placeholderElement(draggable.element);

				if (this._opts.dropIndicatorClass) {
					this._dropIndicator.addClass(this._opts.dropIndicatorClass);
				}
				draggable._box.sizePlaceholder(this._dropIndicator, 'relative', 0, 0);
				
				
				var children = this._children = $(this.element.children()).filter(function () {
					var el = $(this);
					return (! e.draggable._placeholder || ! el.eq(e.draggable._placeholder))
						&& (! this._dropIndicator || ! el.eq(this._dropIndicator));
				});
				var childBoxes = this._childBoxes = [];
				children.each(function (i) {
					childBoxes[i] = new Box($(children[i]));
				});
			},
			_onInactive: function (e) {
				removeListener(this._onEnterListener);
				removeListener(this._onLeaveListener);

				delete this._box;

				if (this._opts.dropIndicator == 'none') return;

				if (! e.droppedOnThis && this._dropIndicator) {
					this._dropIndicator.remove();
					delete this._dropIndicator;
				}
				delete this._childBoxes;
				delete this._children;
			},
			_onEnter: function () {
				this._dropIndicatorAt = -1;
			},
			_onLeave: function () {
				this._dropIndicator.remove();
			},
			moveToPosition : function (draggable) {
				var dropIndicator = this._dropIndicator,
					box = new Box(dropIndicator);
				//dropIndicator.after(draggable.element);
				var marginLeft = parseInt(dropIndicator.css('margin-left')),
					marginTop = parseInt(dropIndicator.css('margin-top'));
				if (isNaN(marginLeft)) marginLeft = 0;
				if (isNaN(marginTop)) marginTop = 0;
				draggable._startOffset = {
					x: box.offsetLeft() - marginLeft,
					y: box.offsetTop() - marginTop
				};
				draggable._dropIndicator = dropIndicator;
				delete this._dropIndicator;
			}
		}
		return r
		
		
	}
});
/*@end @*/