
	// gc_exception class
	function gc_exception (name, message, moreInfo) {
		this.name = name;
		this.message = message;
		this.moreInfo = moreInfo;
	}

	// make the exception convert to a readable string when used as
	// a string (e.g. by the error console)
	gc_exception.prototype.toString = function () {
		var temp = new Array ();

		if (this.name != undefined)
			temp[temp.length] = 'Exception: ' + this.name;
		else
			temp[temp.length] = 'Exception: Unknown';

		if (this.message != undefined)
			temp[temp.length] = 'Message: ' + this.message;

		if (this.moreInfo != undefined)
			temp[temp.length] = 'Details: ' + this.moreInfo;

		return temp.join ("\n"); 
	}














	var gc_dhtml = {

		display_element_neatly: function (sourceElement, destElement, positionInfo, effectType, effectOptions) {

			// find the element in the dom
			destElement = $(destElement);
			if (destElement == null)
				throw new gc_exception ('invalidElementId', 'element not found', 'element "' + destElement + '" not found');

			if (effectType == undefined)
				effectType = 'Appear';

			if (effectOptions == undefined)
				effectOptions = { duration: 0.25 };

//			// allow it to be fully visible if it's not currently fully visible
//			if (effectOptions.to == undefined)
//				effectOptions.to = 1;

			if (effectOptions.queue == undefined) {
				effectOptions.queue = {	position: 'end',
										scope: destElement.id }
			}


			// make sure the element won't be hidden in the time being
			gc_timer.cancel_delayed_action (destElement.id);

			// determine the new position for the element
//			if (!destElement.visible ()) {
				var temp = gc_geometry.determine_relative_position (sourceElement, destElement, positionInfo);

				// change the element's position
				destElement.style.left = temp.x + 'px';
				destElement.style.top = temp.y + 'px';
//			}

			// make the element appear
//			if (!destElement.visible ()) {
				var queue = Effect.Queues.get (destElement.id);
				queue.each (function (e) { e.cancel() });
				eval ("new Effect." + effectType + " (destElement, effectOptions);");
//			}
//			// or make it fully visible
//			else
//				eval ("new Effect.Opacity (destElement, { to: " + effectOptions.to + " });");

			return true;
		},

		hide_element_neatly: function (destElement, delayMS, effectType, effectOptions) {

			// find the element in the dom
			destElement = $(destElement);
			if (destElement == null)
				throw new gc_exception ('invalidElementId', 'element not found', 'element "' + destElement + '" not found');

			if (delayMS == undefined)
				delayMS = 25;

			if (effectType == undefined)
				effectType = 'Fade';

			if (effectOptions == undefined)
				effectOptions = { duration: 0.25 };

			if (effectOptions.queue == undefined) {
				effectOptions.queue = {	position: 'end',
										scope: destElement.id }
			}

//alert ("new Effect." + effectType + " ($('" + destElement.id + "'), " + $H(effectOptions).toJSON() + ")");
			gc_timer.start_delayed_action (
				"new Effect." + effectType + " ($('" + destElement.id + "'), " + $H(effectOptions).toJSON() + ")",
				delayMS,
				destElement.id);

			return true;
		}
	}














	var gc_geometry = {
		// positionInfo =
		// {	xSourcePoint: left, middle, right
		//		ySourcePoint: top, middle, bottom
		//		xDestPoint: left, middle, right
		//		yDestPoint: top, middle, bottom
		//		xPixelOffset: integer
		//		yPixelOffset: integer
		//		xPercentOffset: integer
		//		yPercentOffset: integer }
		determine_relative_position: function (sourceElement, destElement, positionInfo) {

			// find the element in the dom
			sourceElement = $(sourceElement);
			if (sourceElement == null)
				throw new gc_exception ('invalidElementId', 'element not found', 'element "' + sourceElement + '" not found');

			// find the element in the dom
			destElement = $(destElement);
			if (destElement == null)
				throw new gc_exception ('invalidElementId', 'element not found', 'element "' + destElement + '" not found');

			if (positionInfo == undefined)
				positionInfo = new Object ();

			// work out the new position relative to the source element
			var temp = sourceElement.cumulativeOffset ();
			var destX = temp[0];
			var destY = temp[1];

			// adjust based on the source element's dimensions
			if (sourceElement.visible ())
				temp = sourceElement.getDimensions ();
			else {
				// force the sourceElement's width to be determined properly,
				// because prototype doesn't handle the width of divs with borders correctly when the object is hidden
				sourceElement.show ();
				temp = sourceElement.getDimensions ();
				sourceElement.hide ();
			}

			if (positionInfo.xSourcePoint == 'middle')
				destX = destX + Math.round (temp.width / 2);
			else if (positionInfo.xSourcePoint == 'right')
				destX = destX + temp.width;

			if (positionInfo.ySourcePoint == 'middle')
				destY = destY + Math.round (temp.height / 2);
			else if (positionInfo.ySourcePoint == 'bottom')
				destY = destY + temp.height;


			// adjust based on the destination element's dimensions
			if (destElement.visible ())
				destElementDimensions = destElement.getDimensions ();
			else {
				// force the destElement's width to be determined properly,
				// because prototype doesn't handle the width of divs with borders correctly when the object is hidden
				destElement.show ();
				destElementDimensions = destElement.getDimensions ();
				destElement.hide ();
			}

			if (positionInfo.xDestPoint == 'middle')
				destX = destX - Math.round (destElementDimensions.width / 2);
			else if (positionInfo.xDestPoint == 'right')
				destX = destX - destElementDimensions.width;

			if (positionInfo.yDestPoint == 'middle')
				destY = destY - Math.round (destElementDimensions.height / 2);
			else if (positionInfo.yDestPoint == 'bottom')
				destY = destY - destElementDimensions.height;


			// adjust the relative position via the given offsets
			if (positionInfo.xPixelOffset != undefined)
				destX = destX + positionInfo.xPixelOffset;
			if (positionInfo.yPixelOffset != undefined)
				destY = destY + positionInfo.yPixelOffset;

			if (positionInfo.xPercentOffset != undefined)
				destX = destX + Math.round ((positionInfo.xPercentOffset / 100) * destElementDimensions.width);
			if (positionInfo.yPercentOffset != undefined)
				destY = destY + Math.round ((positionInfo.yPercentOffset / 100) * destElementDimensions.height);



			// make sure the new coordinates are within the page
			var bodyOffset = this.find_viewable_page_offset ();
			var bodyDimensions = this.find_viewable_page_dimensions ();

//alert (destX + " " + destElementDimensions.width + "  - " + bodyOffset.x + " " + bodyDimensions.width + " -- " + destY + " " + destElementDimensions.height + " - " + bodyOffset.y  + " " +  bodyDimensions.height);
//alert ((destX + destElementDimensions.width) + " " + (bodyOffset.x + bodyDimensions.width) + " - " + (destY + destElementDimensions.height) + " " + (bodyOffset.y + bodyDimensions.height));
			if (bodyDimensions.width > 0) {
				if (destX + destElementDimensions.width > bodyOffset.x + bodyDimensions.width)
					destX = bodyOffset.x + bodyDimensions.width - destElementDimensions.width;
				if (destY + destElementDimensions.height > bodyOffset.y + bodyDimensions.height)
					destY = bodyOffset.y + bodyDimensions.height - destElementDimensions.height;
			}
/**/
			if (destX < 0)
				destX = 0;
			if (destY < 0)
				destY = 0;
//alert (destX + " " + destY);

			return {x: destX, y: destY};
		},



		// find the width and height of the viewable page
		// see for more info: http://www.quirksmode.org/js/doctypes.html
		find_viewable_page_dimensions: function () {
			var width = 0, height = 0;

			// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
			if (window.innerWidth != undefined) {
				width = window.innerWidth;
				height = window.innerHeight;
			}
			// IE6 in standards compliant mode (ie. with a valid doctype as the first line in the document)
			else if ((document.documentElement != undefined)
			&& (document.documentElement.clientWidth != undefined)
			&& (document.documentElement.clientWidth != 0)) {
				width = document.documentElement.clientWidth;
				height = document.documentElement.clientHeight;
			}
			// older versions of IE
			else {
				width = document.getElementsByTagName ('body')[0].clientWidth;
				height = document.getElementsByTagName ('body')[0].clientHeight;
			}

			return {width: parseInt (width), height: parseInt (height)};
		},



		// find the offset of the viewable page in relation to the very top and left of the document
		// see for more info: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
		find_viewable_page_offset: function () {
			var x = 0, y = 0;

			// DOM compliant
			if ((document.body) && ((document.body.scrollLeft) || (document.body.scrollTop))) {
				x = document.body.scrollLeft;
				y = document.body.scrollTop;
			}
			// netscape compliant
			else if (typeof (window.pageYOffset) == 'number') {
				x = window.pageXOffset;
				y = window.pageYOffset;
			}
			// IE6 standards compliant mode
			else if ((document.documentElement) && ((document.documentElement.scrollLeft) || (document.documentElement.scrollTop))) {
				x = document.documentElement.scrollLeft;
				y = document.documentElement.scrollTop;
			}

			return {x: parseInt (x), y: parseInt (y)};
		},



		find_total_page_dimensions: function () {
			// Firefox
			if (window.innerHeight && window.scrollMaxY) {
				width = window.innerWidth + window.scrollMaxX;
				height = window.innerHeight + window.scrollMaxY;
			}
			// all but Explorer Mac
			else if (document.body.scrollHeight > document.body.offsetHeight) {
				width = document.body.scrollWidth;
				height = document.body.scrollHeight;
			}
			// works in Explorer 6 Strict, Mozilla (not FF) and Safari
			else {
				width = document.body.offsetWidth;
				height = document.body.offsetHeight;
			}
			return {width: parseInt (width), height: parseInt (height)};
		}
	}












	var gc_popOverManager = {
		popOverInfo: new Object (),

		// positionInfo =
		// {	xSourcePoint: left, middle, right
		//		ySourcePoint: top, middle, bottom
		//		xDestPoint: left, middle, right
		//		yDestPoint: top, middle, bottom
		//		xPixelOffset: integer
		//		yPixelOffset: integer
		//		xPercentOffset: integer
		//		yPercentOffset: integer }
		define_pop_over: function (elementId, placeholderId, positionInfo, displayEffectType, displayEffectOptions, hideEffectType, hideEffectOptions, mouseOffHideDelayMS) {
			temp = new Object ();
			temp.elementId = elementId;
			temp.placeholderId = placeholderId;
			temp.positionInfo = positionInfo;
			temp.displayEffectType = displayEffectType;
			temp.displayEffectOptions = displayEffectOptions;
			temp.hideEffectType = hideEffectType;
			temp.hideEffectOptions = hideEffectOptions;
			temp.mouseOffHideDelayMS = mouseOffHideDelayMS;
			this.popOverInfo[elementId] = temp;

			return true;
		},

		display_pop_over: function (elementId) {
			if (this.popOverInfo[elementId] == undefined)
				throw new gc_exception ('invalidPopOver', 'pop over not defined', 'pop over "' + elementId + '" not defined');

			gc_dhtml.display_element_neatly (
				this.popOverInfo[elementId].placeholderId,
				this.popOverInfo[elementId].elementId,
				this.popOverInfo[elementId].positionInfo,
				this.popOverInfo[elementId].displayEffectType,
				this.popOverInfo[elementId].displayEffectOptions );
			return true;
		},

		hide_pop_over: function (elementId, useDelay) {
			if (this.popOverInfo[elementId] == undefined)
				throw new gc_exception ('invalidPopOver', 'pop over not defined', 'pop over "' + elementId + '" not defined');

			gc_dhtml.hide_element_neatly (
				this.popOverInfo[elementId].elementId,
				(useDelay ? this.popOverInfo[elementId].mouseOffHideDelayMS : 0),
				this.popOverInfo[elementId].hideEffectType,
				this.popOverInfo[elementId].hideEffectOptions );
			return true;
		}
	}



	// alert the contents of an object
	var gc_var_dump = function (object) {
		var temp = new Array ();
		for (var index in object)
			temp[temp.length] = index + ': "' + object[index] + '"';
		alert (temp.join ("\n"));
		return true;
	}
















	var gc_timer = {

		// storage place for internal info about delayed actions
		delayedActionTimers: new Array (),

		// start a delayed action
		start_delayed_action: function (actions, delayMS, timeoutName) {
			if (timeoutName != undefined) {
				this.cancel_delayed_action (timeoutName);
				if (delayMS > 0)
					this.delayedActionTimers[timeoutName] = setTimeout (actions, delayMS);
				else
					eval (actions);
			}
			else
				setTimeout (actions, delayMS);
			return true;
		},

		// cancel a delayed action
		cancel_delayed_action: function (timeoutName) {
			if (this.delayedActionTimers[timeoutName] != undefined)
				clearTimeout (this.delayedActionTimers[timeoutName]);
			return true;
		}
	};




















	var gc_ajaxManager = {
		ajaxRequesters: new Array (),	// a storage array of ajax requesters
		identifier: undefined,

		use_requester: function (identifier) {
			this.identifier = identifier;
		},

		// start an ajax request,  but store a reference to this new ajax request
		// instance so it can be replaced by a new one later if need be
		request: function (url, sendOptions) {

			if (sendOptions == undefined)
				sendOptions = new Object ();

			if (sendOptions.onSuccess == undefined) {
				if (typeof (this.identifier) == 'string')
					eval ('sendOptions.onSuccess = function (transport)'
					+ '{	gc_ajaxManager.process_ajax_response (transport, \'' + this.identifier + '\'); };');
				else
					eval ('sendOptions.onSuccess = function (transport)'
					+ '{	gc_ajaxManager.process_ajax_response (transport, ' + this.identifier + '); };');
					
			}

			// create the new request
			var newAjaxHandler = new Ajax.Request (url, sendOptions);

			// if the user has specified a name for this ajax handler then..
			if (this.identifier != undefined) {

				// cancel the original http request
				if (this.ajaxRequesters[this.identifier] != undefined)
					try { this.ajaxRequesters[this.identifier].transport.abort (); } catch (e) {};

				// store this new request
				this.ajaxRequesters[this.identifier] = newAjaxHandler;
			}

			// forget the current identifier so that other anonymous requests can be made straight away without stopping the above request
			this.identifier = undefined;

			return true;
		},


		// process an xml ajax response,  and carry out it's instructions
		process_ajax_response: function (transport, identifier) {

			// remove the ajax requester object so it can be garbage collected
			this.ajaxRequesters[identifier] = undefined;

/**
			var response = transport.responseText || "no response text";
			alert("Suceeess! \n\n" + response);	
/**/
			if (transport.responseText.length == 0)
				return true;

			var xmlDoc = transport.responseXML;
			// alert the developer of a problem with the xml
//alert (transport.responseText);
//alert (xmlDoc.childNodes[0].nodeName);
//alert (xmlDoc.childNodes.length + " " + (xmlDoc.childNodes[0].nodeName == 'parsererror'));


			if ((xmlDoc == null) || (xmlDoc.childNodes.length == 0) || (xmlDoc.childNodes[0].nodeName == 'parsererror')) {
//				document.writeln ('<strong>Warning: invalid xml ajax response:</strong><br />\n<a href="' + this.uri + '">' + this.uri + '</a><br />\n' + this.httpRequestObject.responseText);
//				document.writeln ('<strong>Warning: invalid xml ajax response:</strong><br />\n' + transport.responseText);
				alert ('Warning: invalid xml ajax response:\n' + transport.responseText);
				return false;
			}

			this.process_xml (xmlDoc);
			return true;
		},

		process_xml: function (xmlDoc) {

			// find the root node
			for (var count = 0; count < xmlDoc.childNodes.length; count++) {

				if (xmlDoc.childNodes[count].nodeName == 'root') {
					var rootNode = xmlDoc.childNodes[count];

					// loop through the nodes and carry out their instructions
					for (var count2 = 0; count2 < rootNode.childNodes.length; count2++) {
						var currentNode = rootNode.childNodes[count2];

						switch (currentNode.nodeName) {
							case 'updateElement' :
								if ($(currentNode.getAttribute ('domId')) != null) {
									if (currentNode.hasChildNodes ())
										$(currentNode.getAttribute ('domId')).update (currentNode.firstChild.data);
									else
										$(currentNode.getAttribute ('domId')).update ('');
								}
								break;
							case 'showElement' :
								if ($(currentNode.getAttribute ('domId')) != null)
									$(currentNode.getAttribute ('domId')).show ();
								break;
							case 'hideElement' :
								if ($(currentNode.getAttribute ('domId')) != null)
									$(currentNode.getAttribute ('domId')).hide ();
								break;
							case 'enableElement' :
								if ($(currentNode.getAttribute ('domId')) != null)
									$(currentNode.getAttribute ('domId')).disabled = false;
								break;
							case 'disableElement' :
								if ($(currentNode.getAttribute ('domId')) != null)
									$(currentNode.getAttribute ('domId')).disabled = true;
								break;
							case 'setCookie' :
								gc_cookie.set (currentNode.getAttribute ('name'), currentNode.firstChild.data, currentNode.getAttribute ('expireSeconds'), currentNode.getAttribute ('path'), currentNode.getAttribute ('domain'), currentNode.getAttribute ('secure'));
								break;
							case 'removeCookie' :
								gc_cookie.remove (currentNode.getAttribute ('name'), currentNode.getAttribute ('path'), currentNode.getAttribute ('domain'));
								break;
							case 'alert' :
								alert (currentNode.firstChild.data);
								break;
							case 'redirect' :
								document.location.href = currentNode.firstChild.data;
								break;
							case 'eval' :
								if (currentNode.firstChild.data != '')
									gc_eval_js_response (currentNode.firstChild.data);
								break;
							case 'reload' :
								window.location.reload ();
								break;
						}
					}
				}
			}
			return true;
		}
	}

	// eval the given javascript,
	// called from the above process_ajax_response when eval'ing some js from an ajax response
	// so that the code is not running in the scope of the ajaxManager object
	var gc_eval_js_response = function (code) {
		eval (code);
	}








	// cookie addition to prototype.  adapted from:
	// http://wiki.script.aculo.us/scriptaculous/show/Cookie
	// http://gorondowtl.sourceforge.net/wiki/Cookie
	var gc_cookie = {
		// set the value of a cookie
		set: function (name, value, expireSeconds, path, domain, secure) {

			var expires = '';
			if (expireSeconds != undefined) {
				var d = new Date ();
//				d.setTime (d.getTime () + (86400000 * parseFloat (daysToExpire)));
				d.setTime (d.getTime () + parseFloat (expireSeconds));
				expires = d.toGMTString ();
			}
//			return (document.cookie = escape (name) + '=' + escape (value || '') + expire + ((path) ? "; path=" + path : ""));
			return document.cookie = escape (name) + '=' + escape (value || '')
									+ (expires ? "; expires=" + d.toGMTString () : "")
									+ (path ? "; path=" + path : "")
									+ (domain ? "; domain=" + domain : "")
									+ (secure ? "; secure" : "");
		},
		// fetch the value of a cookie
		get: function (name) {
			var cookie = document.cookie.match (new RegExp ('(^|;)\\s*' + escape (name) + '=([^;\\s]*)'));
			return (cookie ? unescape (cookie[2]) : null);
		},
		// remove a cookie
		remove: function (name, path, domain, secure) {
			var cookie = ((gc_cookie.get (name)) || (true));
			gc_cookie.set (name, '', -1, path, domain, secure);
			return cookie;
		},
		// find out if javascript is allowed to access cookies
		accept: function () {
			if (typeof navigator.cookieEnabled == 'boolean')
				return navigator.cookieEnabled;
			gc_cookie.set ('_test_cookie_abcdef_', '1');
			return (gc_cookie.remove ('_test_cookie_abcdef_') === '1');
		}
	}