/*
 * Glow JavaScript Library
 * Copyright (c) 2008 British Broadcasting Corporation
 */
/*@cc_on @*//*@if (@_jscript_version > 5.1)@*/;
glow.module("glow.widgets", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events'
	],
	implementation: function() {
		var doc,
			docBody,
			env = glow.env;
		
		glow.ready(function() {
			doc = document;
			docBody = doc.body;
			
			//check if css or images are disabled, add class name "glow-basic" to body if they aren't
			var testDiv = glow.dom.create('<div class="glow-cssTest"></div>').appendTo(docBody);
			if (testDiv.css("z-index") != "1234" || testDiv.css("background-image").indexOf("ctr.png") == -1) {
				docBody.className += " glow-basic";
			}
			testDiv.remove();
			//add some IE class names to the body to help widget styling
			env.ie && (docBody.className += " glow-ie");
			//note: we apply the class "glow-ielt7" for IE7 if it's in quirks mode
			(env.ie < 7 || !env.standardsMode) && (docBody.className += " glow-ielt7");
			//some rounding issues in firefox when using opacity, so need to have a workaround
			env.gecko && (docBody.className += " glow-gecko");
			
		});
		
		
		return {
			_scrollPos: function() {
				var win = window,
					docElm = env.standardsMode ? doc.documentElement : docBody;
				
				return {
					x: docElm.scrollLeft || win.pageXOffset || 0,
					y: docElm.scrollTop || win.pageYOffset || 0
				};
			}
		};
	}
});
;glow.module("glow.widgets.Mask", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events',
		'glow.widgets'
	],
	implementation: function() {
		var dom = glow.dom,
			$ = dom.get,
			events = glow.events,
			widgets = glow.widgets,
			bodyProperties, //this is a holding place for body padding & margins
			htmlStr = '<div class="glow-noMask" style="margin:0;padding:0;position:absolute;width:100%;top:0;left:0;overflow:auto;', //reusable html string
			noScrollContainer,
			iframeSrc = '<iframe class="glow-noMask" style="margin:0;padding:0;position:absolute;top:0;left:0;filter:alpha(opacity=0);display:none"></iframe>';
		function Mask(opts) {
			this.opts = glow.lang.apply({
				color: '#000',
				opacity: 0.7,
				zIndex: 9900,
				disableScroll: false
			}, opts || {});
			var docBody = document.body,
				mask = this.maskElement = dom.create(
				htmlStr + 'z-index:' + this.opts.zIndex + ';background:' + this.opts.color + ';visibility:hidden"></div>'
			).appendTo(docBody),
				that = this;
			
			mask.css("opacity", this.opts.opacity);
			
			if (glow.env.ie < 7) {
				this._iframe = dom.create(iframeSrc).css("z-index", this.opts.zIndex - 1).appendTo(docBody);
			}
			
			//add mask node click event, route it through to a Mask event
			events.addListener(mask, "click", function() {
				events.fire(that, "click");
			});
			if (this.opts.onClick) {
				events.addListener(this, "click", opts.onClick);
			}
		}

		Mask.prototype = {
			add: function () {
				var doc = $(document),
					body = $(document.body),
					win = $(window),
					that = this;
			
				if (this.opts.disableScroll && !noScrollContainer) { //avoid blocking scrolling twice
					noScrollContainer = glow.dom.create(
						htmlStr + 'height:100%;overflow:hidden;">' + htmlStr + '"></div></div>'
					);
					
					var scrollVals = widgets._scrollPos(),
						bodyStyle = body[0].style,
						clientHeight = win.height(),
						clientWidth = win.width(),
						noScroll = noScrollContainer.get("div"),
						//get children which don't have class "glow-noMask"
						bodyChildren = body.children().filter(function() { return (' ' + this.className + ' ').indexOf("glow-noMask") == -1 });
					
					bodyProperties = {
						margin: [body.css("margin-top"), body.css("margin-right"), body.css("margin-bottom"), body.css("margin-left")],
						padding: [body.css("padding-top"), body.css("padding-right"), body.css("padding-bottom"), body.css("padding-left")],
						height: body.css("height")
					};
					
					bodyStyle.margin = bodyStyle.padding = 0;
					
					bodyStyle.height = "100%";
					noScroll[0].style.zIndex = this.opts.zIndex - 1;
					
					noScrollContainer.appendTo(body);
					
					noScroll.css("margin", bodyProperties.margin.join(" ")).
							 css("padding", bodyProperties.padding.join(" ")).
							 css("top", -scrollVals.y - parseFloat(bodyProperties.margin[0]) + "px").
							 css("left", -scrollVals.x + "px").
							 append(bodyChildren);
				}
				
				function resizeMask() {
					var bodyHeight = body.height();
					for (var i = 0; i < 2; i++) {
						that.maskElement.css("width", "100%").
								  css("height", (that.opts.disableScroll ? noScrollContainer.height() : Math.max(bodyHeight, win.height())) + "px");
					}
					if (glow.env.ie < 7) {
						var maskStyle = that.maskElement[0].style;
						that._iframe.css("width", maskStyle.width).css("height", maskStyle.height);
					}
				}
				this.maskElement.css("visibility", "visible").css("display", "block");
				if (glow.env.ie < 7) {
					this._iframe.css("display", "block");
				}
				resizeMask();
				this._resizeListener = events.addListener(window, "resize", resizeMask);
			},
			remove : function () {
				this.maskElement.css("visibility", "hidden").css("display", "none");
				if (glow.env.ie < 7) {
					this._iframe.css("display", "none");
				}
				events.removeListener(this._resizeListener);
				
				if (this.opts.disableScroll) {
					var body = $(document.body),
						noScroll = noScrollContainer.children();
					
					noScroll.children().appendTo(body);
					window.scroll(-parseInt(noScroll.css("left")), -parseInt(noScroll.css("top")));
					noScrollContainer.remove();
					body.css("margin", bodyProperties.margin.join(" ")).
						 css("padding", bodyProperties.padding.join(" ")).
						 css("height", bodyProperties.height);
						 
					delete noScrollContainer;
					noScrollContainer = undefined;
				}
				
			}
		};

		return Mask;
	}
});
;glow.module("glow.widgets.Overlay", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events',
		'glow.anim',
		'glow.widgets',
		'glow.widgets.Mask'
	],
	implementation: function() {
		var dom = glow.dom,
			$ = dom.get,
			events = glow.events,
			widgets = glow.widgets,
			env = glow.env,
			anim = glow.anim,
			tweens = glow.tweens,
			overlayHtml = '<div class="glow-overlay glow-noMask"></div>',
			//this iframe code is duplicated in mask... shall we sort that out?
			iframeSrc = '<iframe class="glow-noMask" style="display:none;margin:0;padding:0;position:absolute;filter:alpha(opacity=0)"></iframe>',
			hiddenFlash = [],
			flashUrlTest = /.swf($|\?)/i,
			wmodeTest = /<param\s+(?:[^>]*(?:name=["'?]\bwmode["'?][\s\/>]|\bvalue=["'?](?:opaque|transparent)["'?][\s\/>])[^>]*){2}/i;
	
		function hideWindowedFlash(overlay) {
			//return if they've already been hidden, saves time
			if (hiddenFlash.length) { return; }
			var i = 0;
			
			$("object, embed").each(function() {
				var that = this, wmode;
				//we need to use getAttribute here because Opera & Safari don't copy the data to properties
				if (
					(that.getAttribute("type") == "application/x-shockwave-flash" ||
					flashUrlTest.test(that.getAttribute("data") || that.getAttribute("src") || "") ||
					(that.getAttribute("classid") || "").toLowerCase() == "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000") &&
					!$(that).isWithin(overlay.content)
				) {
					wmode = that.getAttribute("wmode");
					if (
						(that.nodeName == "OBJECT" && !wmodeTest.test(that.innerHTML)) ||
						(wmode != "transparent" && wmode != "opaque")
					) {
						hiddenFlash[i++] = [that, that.style.visibility];
						that.style.visibility = "hidden";
					}
					
				}
			});
			
		}
	
		function revertWindowedFlash() {
			for (var i = 0, len = hiddenFlash.length; i < len; i++) {
				hiddenFlash[i][0].style.visibility = hiddenFlash[i][1];
			}
			hiddenFlash = [];
		}
		function generatePresetAnimation(overlay, show) {
			var channels = [],
				channel = [],
				chanLen = 0,
				chansLen = 0,
				preset = overlay.opts.anim,
				mask = overlay.opts.mask,
				container = overlay.container,
				maskOpacity,
				finalHeight = 0;
			
			if (preset == "fade") {
				container.css("opacity", (show ? 0 : 1));
				channels[chansLen++] = [
					anim.css(container, 0.3, {
							opacity: {
								from: (show ? 0 : 1),
								to: (show ? 1 : 0)
							}
						}
					)
				];
				if (show) {
					channels[chansLen - 1][1] = function() { container.css("opacity", "") };
				}
				channels[chansLen++] = [generateMaskAnimation(overlay, show)];
			} else if (preset == "roll") {
				if (show) {
					container.css("height", "");
					finalHeight = container.height();
					container.css("height", "0");
				}
				
				channels[chansLen++] = [
					function() {
						if (env.webkit < 522 && show) {
							container.css("display", "none");
							setTimeout(function() {
								container.css("overflow", "hidden").css("display", "block");
							}, 0);
						} else {
							container.css("overflow", "hidden");
						}
					},
					anim.css(container, 0.3, {
						height: {to: finalHeight}
					}, {tween: show ? tweens.easeOut() : tweens.easeIn() }),
					function() {
						if (!show) {
							container.css("visibility", "hidden");
						}
						container.css("height", "");
						container.css("overflow", "");
					}
				];
				channels[chansLen++] = [generateMaskAnimation(overlay, show)];
			}
			return new anim.Timeline(channels);
		}
		function generateMaskAnimation(overlay, show) {
			if (! overlay.opts.modal) { return 0; }
		
			var mask = overlay.opts.mask,
				maskOpacity = mask.opts.opacity,
				maskElement = mask.maskElement;
			
			maskElement.css("opacity", (show ? 0 : maskOpacity));
			return anim.css(maskElement, 0.1, {
					opacity: {
						from: (show ? 0 : maskOpacity),
						to: (show ? maskOpacity : 0)
					}
				}
			)
		}
		function closeOverlay(overlay) {
			revertWindowedFlash();
			overlay.container.css("visibility", "").css("display", "");
			if (overlay.opts.modal) {
				overlay.opts.mask.remove();
			} else if (glow.env.ie < 7) {
				overlay._iframe.css("display", "none");
			}
			events.removeListener(overlay._scrollEvt);
			events.removeListener(overlay._resizeEvt);
		}
		function Overlay(content, opts) {
			//assume modal if mask provided
			if (opts && opts.mask) { opts.modal = true; }
			
			this.opts = glow.lang.apply({
				modal: false,
				mask: new glow.widgets.Mask(opts.zIndex ? {zIndex: opts.zIndex-1} : {}),
				closeOnMaskClick: true,
				zIndex: 9990,
				autoPosition: true,
				x: "50%",
				y: "50%"
			}, opts || {});
			
			var contentNode = this.content = $(content),
				that = this,
	
				overlayNode = this.container = dom.create(overlayHtml).css("z-index", this.opts.zIndex),
				docBody = document.body;
			this.autoPosition = this.opts.autoPosition;
			this.isShown = false;
			
			//this is used to prevent show / hide commands while animations are underway
			this._blockActions = false;
			
			//add the content to the page
			overlayNode.appendTo(docBody).append(contentNode);
			
			//add close event to mask if needed
			if (this.opts.closeOnMaskClick) {
				events.addListener(this.opts.mask, "click", function() {
					that.hide();
				});
			}
			
			//add IE iframe hack if needed
			if (glow.env.ie < 7 && !this.opts.modal) {
				this._iframe = dom.create(iframeSrc).css("z-index", this.opts.zIndex - 1).appendTo(docBody);
			}
		}
		
		Overlay.prototype = {
			setPosition: function(x, y) {
				var container = this.container;
				//don't use set position if autoPosition is false
				if (this.autoPosition) {
					//if values have been provided, set them. Make sure we're not being passed an event object!
					if (x !== undefined && !(x.source)) {
						this.opts.x = x;
						this.opts.y = y;
					}
					var win = $(window),
						x = this.opts.x,
						y = this.opts.y,
						//fixed positioning isn't supported in IE6 (or quirks mode). Also, Safari 2 does some mental stuff with position:fixed so best to just avoid it
						useFixed = (!env.ie && !(env.webkit < 522)) || (env.ie > 6 && env.standardsMode),
						xVal = parseFloat(this.opts.x),
						yVal = parseFloat(this.opts.y),
						extraOffset = (this.opts.mask.opts.disableScroll || useFixed) ? {x:0,y:0} : widgets._scrollPos();
					
					useFixed && container.css("position", "fixed");
					
					if (typeof x == "string" && x.indexOf("%") != -1) {
						container.css("left", Math.max(((win.width() - container[0].offsetWidth) * (xVal/100)) + extraOffset.x, extraOffset.x) + "px");
					} else {
						container.css("left", xVal + extraOffset.x + "px");
					}
					
					if (typeof y == "string" && y.indexOf("%") != -1) {
						container.css("top", Math.max(((win.height() - container[0].offsetHeight) * (yVal/100)) + extraOffset.y, extraOffset.y) + "px");
					} else {
						container.css("top", yVal + extraOffset.y + "px");
					}
				}

				if (glow.env.ie < 7 && !this.opts.modal) {
					var overlayStyle = container[0].style;
					this._iframe.css("top", overlayStyle.top).
								 css("left", overlayStyle.left).
								 css("width", container[0].offsetWidth + "px").
								 css("height", container[0].offsetHeight + "px");
				}
				return this;
			},
			show: function() {
				var that = this,
					showAnim,
					animOpt = that.opts.anim;
				
				if (that._blockActions || that.isShown) { return that; }
				
				if (events.fire(that, "show").defaultPrevented()) {
					return that;
				}
				hideWindowedFlash(that);
				that.container.css("display", "block");
				if (that.opts.modal) {
					that.opts.mask.add();
				} else if (glow.env.ie < 7) {
					that._iframe.css("display", "block");
				}
				that._scrollEvt = events.addListener(window, "scroll", that.setPosition, that);
				that._resizeEvt = events.addListener(window, "resize", that.setPosition, that);
				
				that.setPosition();
				
				//run the appropiate animation
				if (typeof animOpt == "string") {
					showAnim = generatePresetAnimation(that, true);
				} else if (typeof animOpt == "function") {
					showAnim = animOpt(that, true);
				} else if (animOpt) {
					showAnim = animOpt.show;
				}
				if (showAnim) {
					if (! showAnim._overlayEvtAttached) {
						events.addListener(showAnim, "complete", function() {
							that._blockActions = false;
							that.isShown = true;
							events.fire(that, "afterShow");
						});
						showAnim._overlayEvtAttached = true;
					}
					that._blockActions = true;
					showAnim.start();
					that.container.css("visibility", "visible");
				} else {
					that.container.css("visibility", "visible");
					that.isShown = true;
					events.fire(that, "afterShow");
				}
				
				
				return that;
			},
			hide: function() {			
				var that = this,
					hideAnim,
					animOpt = that.opts.anim;
				
				if (this._blockActions || !that.isShown) { return that; }
				
				if (events.fire(that, "hide").defaultPrevented()) {
					return that;
				}
				
				//run the appropiate animation
				if (typeof animOpt == "string") {
					hideAnim = generatePresetAnimation(that, false);
				} else if (typeof animOpt == "function") {
					hideAnim = animOpt(that, false);
				} else if (animOpt) {
					hideAnim = animOpt.hide;
				}
				if (hideAnim) {
					if (! hideAnim._overlayEvtAttached) {
						events.addListener(hideAnim, "complete", function() {
							closeOverlay(that);
							that._blockActions = false;
							that.isShown = false;
							events.fire(that, "afterHide");
						});
						hideAnim._overlayEvtAttached = true;
					}
					that._blockActions = true;
					hideAnim.start();
				} else {
					closeOverlay(that);
					that.isShown = false;
					events.fire(that, "afterHide");
				}
				return that;
			}

		};
		
		return Overlay;
	}
});
;glow.module("glow.widgets.Panel", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events',
		'glow.widgets.Overlay'
	],
	implementation: function() {
		var dom = glow.dom,
			$ = dom.get,
			events = glow.events,
			widgets = glow.widgets,
			Overlay = widgets.Overlay,
			lang = glow.lang,
			env = glow.env,
			defaultTemplate,
			//a hash of themes, true if their images have been preloaded
			themesPreloaded = {};
		function generateDivString(nest) {
			var insideDiv = nest ? '<div></div>' : '';
			for (var i = 1, len = arguments.length, r = []; i < len; i++) {
				r[i-1] = '<div class="' + arguments[i] + '">' + insideDiv + '</div>';
			}
			return r.join("");
		}
		defaultTemplate = function() {
			var r = [], rLen = 0;
			r[rLen++] = '<div class="glow-panel">';
				r[rLen++] = '<div class="glow-defaultSkin">';
					r[rLen++] = generateDivString(false, "glow-infoPanel-pointerT", "glow-infoPanel-pointerL", "glow-infoPanel-pointerR");
					r[rLen++] = '<div class="pc">';
						r[rLen++] = generateDivString(false, "tr", "tl");
						r[rLen++] = generateDivString(true, "tb");
						r[rLen++] = '<div class="tc">';
							r[rLen++] = generateDivString(false, "bars");
							r[rLen++] = '<div class="c">';
								r[rLen++] = '<a class="glow-panel-close" href="#" title="close">X</a>';
								r[rLen++] = generateDivString(false, "glow-panel-hd", "glow-panel-bd", "glow-panel-ft");
							r[rLen++] = '</div>';
						r[rLen++] = '</div>';
						r[rLen++] = generateDivString(false, "br", "bl");
						r[rLen++] = generateDivString(true, "bb");
					r[rLen++] = '</div>';
					r[rLen++] = generateDivString(false, "glow-infoPanel-pointerB");
				r[rLen++] = '</div>';
			r[rLen++] = '</div>';
			return r.join("");
		}();
		function Panel(content, opts) {
			content = $(content);
			opts = opts || {};
			
			if (typeof opts.width == "number") {
				opts.width += 'px';
			}
			
			if (opts.template) {
				var customTemplate = true;
			}
			
			opts = glow.lang.apply({
				template: defaultTemplate,
				width: "400px",
				modal: true,
				theme: "dark"
			}, opts);
			
			//dress content in template
			var fullContent = dom.create(opts.template),
				headContent = content.get("> .hd"),
				footerContent = content.get("> .ft"),
				docBody = document.body,
				that = this,
				fullContentClone;
				
			if (!customTemplate) {
				fullContent.addClass("glow-panel-" + opts.theme);
				//preload the images of the theme
				if (!themesPreloaded[opts.theme] && docBody.className.indexOf("glow-basic") == -1) {
					fullContentClone = fullContent.clone().addClass("glow-panel-preload").appendTo(docBody);
					themesPreloaded[opts.theme] = true;
				}
			}
			if (content.length > 1) {
				content.each(function() {
					var elm = $(this);
					if (elm.hasClass("hd")) {
						headContent = elm;
					} else if (elm.hasClass("ft")) {
						footerContent = elm;
					}
				});
			}
			this.header = fullContent.get(".glow-panel-hd");
			this.footer = fullContent.get(".glow-panel-ft");
			this.body = fullContent.get(".glow-panel-bd");
			
			if (content.isWithin(docBody)) {
				fullContent.insertBefore(content);
			} else {
				fullContent.appendTo(docBody);
			}
			this.body.append(content);
			if (headContent.length) {
				this.header.append(headContent);
			} else if (!customTemplate) {
				fullContent.addClass("glow-panel-noHeader");
			}
			if (footerContent.length) { this.footer.append(footerContent); }
			
			events.addListener(fullContent.get(".glow-panel-close"), "click", function() {
				that.hide();
				return false;
			})
			
			Overlay.call(this, fullContent, opts);
			
			this.container.css("width", opts.width);
		}
		lang.extend(Panel, Overlay);
		
		return Panel;
	}
});
;
glow.module("glow.widgets.Sortable", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events',
		'glow.dragdrop'
	],
	implementation: function() {

		var $ = glow.dom.get,
			events = glow.events,
			fire = events.fire,
			addListener = events.addListener;
		// TODO - this is a copy of the one in dragdrop, which should be moved to dom, removing the need for this
		function offsetTop (element) {
			var res = 0, el = element[0], pos;
			if (glow.env.ie) {
				do {
					res += el.offsetTop;
					el = el.offsetParent;
					if (el) pos = $(el).css('position');
				} while (el && ! (pos == 'absolute' || pos == 'fixed' || pos == 'relative'));
			}
			else {
				res = el.offsetTop;
			}
			return res;
		}
		var Sortable = function (containers, opts) {
			this._opts = opts = glow.lang.apply({
				dropIndicatorClass : 'glow-sortable-dropindicator',
				equaliseColumns    : true,
				draggableOptions   : {}
			}, opts || {});

			this.constrainDragTo = opts.constrainDragTo;
			this.axis = opts.axis;
			this.draggables = [];

			var containers = this.containers = $(containers),
				dropTargets = this.dropTargets = [];

			if (opts.onSort)
				addListener(this, "sort", opts.onSort);

		    // drop targets
			containers.each(function (i) {
				dropTargets[i] = new glow.dragdrop.DropTarget(this, {
					tolerance          : 'intersect',
					dropIndicator      : 'spacer',
					dropIndicatorClass : opts.dropIndicatorClass
				});
			});

			// draggables
			this.addItems(containers.get("> *"));
		};
		function equaliseColumns () {
		    var offsets = [], maxBottom = 0, bottom, dropTargets = this.dropTargets;
			this.containers.each(function (i) {
				var el = $(this);
				offsets[i] = offsetTop(el);
				bottom = offsets[i] + el[0].offsetHeight;
				if (glow.env.khtml) bottom -= el.css('margin-top') + el.css('margin-bottom');
				if (bottom > maxBottom) maxBottom = bottom;
			});
			for (var i = 0, l = this.dropTargets.length; i < l; i++)
				this.dropTargets[i].setLogicalBottom(maxBottom);
		}
		function handleDrop (e) {
			var draggable = e.attachedTo,
				el = draggable.element,
				target = draggable.activeTarget;
		    this._previous = el.prev();
			this._parent = el.parent();
			if (target)	target.moveToPosition(draggable);
	    }
		function handleAfterDrop (e) {
			var draggable = e.attachedTo,
				el = draggable.element;
			if (! el.prev().eq(this._previous || []) || ! el.parent().eq(this._parent))
				fire(this, "sort");
			delete this._prev;
			delete this._parent;
	    }
		Sortable.prototype = {
			addItems : function (elements) {
				var this_ = this, opts = this._opts.draggableOptions;
				$(elements).each(function () {
					var draggable = new glow.dragdrop.Draggable(
						this, glow.lang.apply({
							placeholder       : 'none',
							axis              : this_.axis,
							container         : this_.constrainDragTo,
							dropTargets       : this_.dropTargets,
							acceptDropOutside : (this_.containers.length == 1)
						}, opts)
					);

					if (this_._opts.equaliseColumns)
					    addListener(draggable, 'drag', equaliseColumns, this_);

					addListener(draggable, 'drop', handleDrop, this_);
					addListener(draggable, 'afterDrop', handleAfterDrop, this_);

					this_.draggables.push(draggable);
				});
			}

		};

		return Sortable;
	}
});

;glow.module("glow.widgets.InfoPanel", "0.4.1", {
	require: [
		'glow.dom',
		'glow.events',
		'glow.widgets.Panel'
	],
	implementation: function() {
		var dom = glow.dom,
			$ = dom.get,
			events = glow.events,
			widgets = glow.widgets,
			lang = glow.lang,
			env = glow.env,
			win,
			positionRegex = /glow\-infoPanel\-point[TRBL]/,
			offsetInContextDefaults = {
				T: {x:"50%", y:"100%"},
				R: {x:0, y:"50%"},
				B: {x:"50%", y:0},
				L: {x:"100%", y:"50%"}
			};
			
		glow.ready(function() {
			win = $(window);
		});
		function resolveRelative(point, context) {
			var vals = [point.x, point.y],
				axis = ["x", "y"],
				sides = ["Width", "Height"],
				i = 0;
			
			//calculate for x & y
			for (; i < 2; i++) {
				if (vals[i].slice) {
					vals[i] = parseFloat(point[axis[i]]);
					if (point[axis[i]].slice(-1) == "%") {
						vals[i] = context[0]["offset" + sides[i]] * (vals[i]/100);
					}
				}
			}
			return {x: vals[0], y: vals[1]};
		}
		function calculateBestPointerSide(contextPos, contextSize) {
			//right, we need to work out where to put the box ourselves
			var scrollPos = widgets._scrollPos(),
				winSize = {x: win.width(), y:win.height()},
				//let's see how much free space there is around the context
				freeSpace = {
					T: winSize.y - contextPos.y - contextSize.y + scrollPos.y,
					R: contextPos.x - scrollPos.x,
					B: contextPos.y - scrollPos.y,
					L: winSize.x - contextPos.x - contextSize.x + scrollPos.x
				},
				preferenceOrder = ["T", "R", "B", "L"];
			
			preferenceOrder.sort(function(a, b) {
				return freeSpace[b] - freeSpace[a];
			});
			
			//could this be made clevererer? (which is why I did the preferenceOrder thing, the first may not always be best)
			return preferenceOrder[0];
		}
		function InfoPanel(content, opts) {
			opts = opts || {};
			
			if (opts.template) {
				var customTemplate = true;
			}
			
			opts = glow.lang.apply({
				modal: false,
				theme: "light",
				autoPosition: !!opts.context,
				pointerRegisters: {
					t: {x: "50%", y: 0},
					r: {x: "100%", y: "50%"},
					b: {x: "50%", y: "100%"},
					l: {x: 0, y: "50%"}
				}
			}, opts);
			
			//deal with context if it's a selector
			opts.context = opts.context && $(opts.context);
			
			widgets.Panel.call(this, content, opts);
			
			if (!customTemplate) {
				this.content.addClass("glow-infoPanel");
			}

			this.content.addClass("glow-infoPanel-point" + (opts.pointerPosition || "t").toUpperCase());
		}
		lang.extend(InfoPanel, widgets.Panel);
		
		lang.apply(InfoPanel.prototype, {
			setPosition: function(x, y) {
				//don't use set position if autoPosition is false
				var valsPassed = (x !== undefined && !(x.source));
				
				if (!(this.autoPosition || valsPassed)) {
					return this;
				} else if (valsPassed) {
					this.autoPosition = false;
				}
				
				var opts = this.opts,
					contentNode = this.content[0],
					pointerPosition = opts.pointerPosition,
					context = opts.context,
					container = this.container,
					//here's what we need to position the pointer
					pointerElm,
					//this will hold the point the user passed, or the context's offset
					contextOffset = valsPassed ? {x:x, y:y} : context.offset(),
					contextSize = valsPassed ? {x:0, y:0} : {x:context[0].offsetWidth, y:context[0].offsetHeight},
					offsetInContext,
					pointOffsetInPanel,
					pointerInnerOffset,
					panelOffset = container.offset(),
					pointerOffset,
					lastPointerPosition;
					
				if (!pointerPosition) {
					//right, we need to work out where to put the box ourselves
					pointerPosition = calculateBestPointerSide(contextOffset, contextSize);
					if (lastPointerPosition != pointerPosition) {
						lastPointerPosition = pointerPosition;
						contentNode.className = contentNode.className.replace(positionRegex, "glow-infoPanel-point" + pointerPosition);
						pointerElm = container.get(".glow-infoPanel-pointer" + pointerPosition);
					}
				} else {
					pointerPosition = pointerPosition.toUpperCase();
				}
				
				if (!pointerElm) {
					pointerElm = container.get(".glow-infoPanel-pointer" + pointerPosition);
				}
				
				//get default offset if there isn't one
				offsetInContext = valsPassed ? {x:0, y:0} : resolveRelative(opts.offsetInContext || offsetInContextDefaults[pointerPosition], context);
				pointerInnerOffset = resolveRelative(opts.pointerRegisters[pointerPosition.toLowerCase()], pointerElm);
				pointerOffset = pointerElm.offset();
				pointOffsetInPanel = {x: pointerOffset.x - panelOffset.x + pointerInnerOffset.x, y: pointerOffset.y - panelOffset.y + pointerInnerOffset.y};
				
				container.css("left", contextOffset.x + offsetInContext.x - pointOffsetInPanel.x + "px").
						  css("top", contextOffset.y + offsetInContext.y - pointOffsetInPanel.y + "px");
						  
				if (env.ie < 7 && !opts.modal) {
					var overlayStyle = container[0].style;
					this._iframe.css("top", overlayStyle.top).
								 css("left", overlayStyle.left).
								 css("width", container[0].offsetWidth + "px").
								 css("height", container[0].offsetHeight + "px");
				}
				return this;
			},
			setContext: function(context) {
				this.opts.context = $(context);
				this.autoPosition = true;
				if (this.container[0].style.display == "block") {
					this.setPosition();
				}
				return this;
			}
		});
		
		return InfoPanel;
	}
});
/*@end @*/