// Copyright 2008 ProjectWizards GmbH. All rights reserved.

//console.log("PWUtils.js");

/*
 *	PWUtils namespace
 *  
 *  ProjectWizards Utility Functions
 */

/*
Copyright (c) 2006 Dan Webb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial 
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
IN THE SOFTWARE.
*/
DomBuilder = {
  IE_TRANSLATIONS : {
    'class' : 'className',
    'for' : 'htmlFor'
  },
  ieAttrSet : function(a, i, el) {
    var trans;
    if (trans = this.IE_TRANSLATIONS[i]) el[trans] = a[i];
    else if (i == 'style') el.style.cssText = a[i];
    else if (i.match(/^on/)) el[i] = new Function(a[i]);
    else el.setAttribute(i, a[i]);
  },
	apply : function(o) { 
	  o = o || {};
		var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" + 
					   "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" + 
					   "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
					   "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" + 
					   "label|dfn|kbd|samp|var").split("|");
    var el, i=0;
		while (el = els[i++]) o[el.toUpperCase()] = DomBuilder.tagFunc(el);
		return o;
	},
	tagFunc : function(tag) {
	  return function() {
	    var a = arguments, at, ch; a.slice = [].slice; if (a.length>0) { 
	    if (a[0].nodeName || typeof a[0] == "string") ch = a; 
	    else { at = a[0]; ch = a.slice(1); } }
	    return DomBuilder.elem(tag, at, ch);
	  }
  },
	elem : function(e, a, c) {
		a = a || {}; c = c || [];
		var isIE = Prototype.Browser.IE;
        var doc = document;
        if(a.document) {
            doc = a.document;
            delete a.document;
        }
		var el = doc.createElement((isIE && a.name)?"<" + e + " name=" + a.name + ">":e);
		for (var i in a) {
		  if (typeof a[i] != 'function') {
		    if (isIE) this.ieAttrSet(a, i, el);
		    else el.setAttribute(i, a[i]);
		  }
	  }
		for (var i=0; i<c.length; i++) {
			if (typeof c[i] == 'string') 
                c[i] = doc.createTextNode(c[i]);
            try {
                el.appendChild(c[i]);
            } catch(e){
//                console.log("error: %s", e);
            }
		} 
		return el;
	}
}

DomBuilder.apply(window);

var PWUtils;
if(!PWUtils) PWUtils = {
        
    addVMLGraphicToElement: function(elem)
    {
		var vml = document.createElement("v:group");
		vml.style.position = "absolute";
		vml.style.width = "500px";
		vml.style.height = "500px";
		vml.setAttribute("coordsize", "500 500");  
		vml.setAttribute("coordorigin", "0 0");
        vml.style.zIndex = "1000000";
    
		var bar = document.createElement("v:roundrect");
		bar.style.position = "absolute";
		bar.style.width = "100px";
		bar.style.height = "100px";
		bar.setAttribute("fill", "true");
		bar.setAttribute("fillcolor", "red");	
		bar.setAttribute("strokeweight", "2");	
        bar.style.zIndex = "1000000";
        
		vml.appendChild(bar);
		elem.appendChild(vml);
    },


	//	The observing methods should be used like this:
	//
	//		cell.f1 = this.clickedInColumnHead.bindAsEventListener(this);
	//		observe(cell, 'mousedown', cell.f1);
	//		stopObserving(cell, 'mousedown', cell.f1);
	//		

	stopObserving: function(element, name, handler) {
		if (element.removeEventListener) {
			element.removeEventListener(name, handler, false);
		} else {
			element.detachEvent("on" + name, handler);
		}
	},

	observe: function(element, name, handler, register, useCapture) {
		if(register) {
			if(!PWUtils.eventHandlerCache)
				PWUtils.eventHandlerCache = [];
			PWUtils.eventHandlerCache.push({element:element, eventName:name, handler:handler});
		}
		if (element.addEventListener) {
			element.addEventListener(name, handler, useCapture);
		} else {
			var result = element.attachEvent("on" + name, handler);
		}
	},
	
	disconnectEventListeners: function() {
		if(PWUtils.eventHandlerCache) {
			for(var i=0; i<PWUtils.eventHandlerCache.length; i++) {
				var info = PWUtils.eventHandlerCache[i];
				PWUtils.stopObserving(info.element, info.eventName, info.handler);
			}
		}
	},
	
	makeButton: function(button){
		var rows = button.getElementsByTagName("tr");
		if(rows && rows.length){
			button.segments = [];
			for(var i=0; i<rows[0].childNodes.length; i++){
				var segment = rows[0].childNodes[i];
				segment.origClassName = segment.className;
				button.segments.push(segment);
			}
			button._onMouseDown = function(event){
				for(var i=0; i<button.segments.length; i++){
					var segment = button.segments[i];
					segment.className = segment.origClassName+"Clicked";
				}
				eval(button.getAttribute("onClick"));
			}
			button._onMouseUp = function(event){
				for(var i=0; i<button.segments.length; i++){
					var segment = button.segments[i];
					segment.className = segment.origClassName;
				}
			}
			button._onMouseOut = function(event){
				button._onMouseUp(event);
			}
			PWUtils.observe(button, "mouseout", function(){button._onMouseOut();}, true);
			PWUtils.observe(button, "mousedown", function(){button._onMouseDown();}, true);
			PWUtils.observe(button, "mouseup", function(){button._onMouseUp();}, true);
		}
		return button;
	},
	
	makeMultiStateButton: function(button, firePeriodically){
		if(!button)
			return;
		button.enabled = true;
		if(!button.origClassName)
			button.origClassName = button.className;
		button.setEnabled = function(enable){
			if(!enable && !button.origOnClickMethod)
				button.origOnClickMethod = button.getAttribute("onClick");
			if(enable && !button.enabled){
				button.setAttribute("onClick", button.origOnClickMethod, 1);
				button.enabled = true;
				button.className = button.origClassName;
			}else if(!enable && button.enabled){
				button.setAttribute("onClick", null);
				button.enabled = false;
				button.className = button.origClassName + "Disabled";
			}
		}
		button._stopTimer = function(){
			if(button.timer)
				window.clearInterval(button.timer);		
		}
		button._onMouseDown = function(event){
			button.className = button.origClassName+"Clicked";
			if(firePeriodically){
				button._stopTimer();
				button.timer = window.setInterval(function(){
					eval(button.getAttribute("onClick"));
				}, 200);
			}
		}
		button._onMouseUp = function(event){
			button.className = button.origClassName + (button.enabled ? "" : "Disabled");
			button._stopTimer();
		}
		button._onMouseOut = function(event){
			button.className = button.origClassName + (button.enabled ? "" : "Disabled");
			button._stopTimer();
		}
		PWUtils.observe(button, "mouseout", function(){button._onMouseOut();}, true);
		PWUtils.observe(button, "mousedown", function(){button._onMouseDown();}, true);
		PWUtils.observe(button, "mouseup", function(){button._onMouseUp();}, true);
		return button;
	},
		
	getY: function(element){
		var y = 0;
		for(var e=element; e; e = e.offsetParent)
			y += e.offsetTop;
		for(e = element.parentNode; e && e!=document.body; e = e.parentNode)
			if(e.scrollTop) 
				y -= e.scrollTop;
		return y;
	},

	getX: function(element){
		var x = 0;
		for(var e=element; e; e = e.offsetParent)
			x += e.offsetLeft;
		for(e = element.parentNode; e && e!=document.body; e = e.parentNode)
			if(e.scrollLeft) 
				x -= e.scrollLeft;
		return x;
	},
	
	
	getLocalX: function(x, element){
		return x - this.getX(element);
	},

	getLocalY: function(y, element){
		return y - this.getY(element);
	},
	
	/*
	 *	dispatchMouseDownEvent 
	 *  Decides if the event is a click- or a drag-event and calls appropriate handler functions.
	 */
	
	dispatchMouseDownEvent: function(event, obj, clickHandler, dragHandler, startDragHandler, endDragHandler, biggerDragDetectionArea){		
		event = event || window.event;
		var origX = event.clientX;
		var origY = event.clientY;
		var srcElement = event.srcElement;
		var moved = false;
		if(document.addEventListener){
			PWUtils.observe(document, "mouseup", handleMouseUp);
			PWUtils.observe(document, "mousemove", handleMouseMove);
		}else if(document.attachEvent){
			srcElement.setCapture();
			PWUtils.observe(srcElement, "mouseup", handleMouseUp);
			PWUtils.observe(srcElement, "losecapture", handleMouseUp);
			PWUtils.observe(srcElement, "mousemove", handleMouseMove);
		}

		function handleMouseUp(event){
			if(document.removeEventListener){			
				PWUtils.stopObserving(document, "mouseup", handleMouseUp);
				PWUtils.stopObserving(document, "mousemove", handleMouseMove);
			}else if(document.detachEvent)	{
				PWUtils.stopObserving(srcElement, "mouseup", handleMouseUp);
				PWUtils.stopObserving(srcElement, "losecapture", handleMouseUp);
				PWUtils.stopObserving(srcElement, "mousemove", handleMouseMove);
				srcElement.releaseCapture();			
			}
			if(moved && endDragHandler)
				endDragHandler.call(obj, event);
			else if(clickHandler)
				clickHandler.call(obj, event);
		}

		function handleMouseMove(event){
			var newX = event.clientX;
			var newY = event.clientY;
			if(!moved) {
				if(!biggerDragDetectionArea || (newX>origX+1 || newX<origX-1 || newY>origY+1 || newY<origY-1)) {
					// I don't know a portable way to capture the initial event.
					// Instead I pass the X/Y values directly.
					if(startDragHandler)
						startDragHandler.call(obj, event, origX, origY);
					moved = true;
				}
			}
			if(dragHandler)
				dragHandler.call(obj, event);
		}
	},
			
	effectiveStyle: function(e){
		return e.currentStyle ? e.currentStyle : window.getComputedStyle(e, null);
	},

	scrollbarWidth: function(){ 
		return Prototype.Browser.IE ? 17 : 15;
	},

	drawTopAndBottomLine: function(place, topColor, bottomColor){
		var topBar = DIV({style:"position:absolute; left:0px; top:0px; width:100%; height:1px; z-index:-1; background-color:"+topColor+";"});
		place.appendChild(topBar);
		var bottomBar = DIV({style:"position:absolute; left:0px; bottom:0px; width:100%; height:1px; z-index:-1; background-color:"+bottomColor+";"});
		place.appendChild(bottomBar);
	},

	drawLeftAndRightLine: function(place, leftColor, rightColor){
		var leftBar = DIV({style:"position:absolute; left:0px; top:0px; bottom:0px; width:1px; background-color:"+leftColor+";"});
		place.appendChild(leftBar);		
		var rightBar = DIV({style:"position:absolute; right:0px; top:0px; bottom:0px; width:1px; background-color:"+rightColor+";"});
		place.appendChild(rightBar);		
	},

	drawHeaderGradient: function(place, imageURL){
		var gradientContainer = DIV({style:"position:absolute; left:0px; top:0px; bottom:0px; right:0px; z-index:-1;"});
		var img = IMG({style:"position:absolute; left:0px; top:0px; width:100%; height:100%; z-index:-1;"});
		img.src = imageURL;
		gradientContainer.appendChild(img);
		place.appendChild(gradientContainer);
		this.drawTopAndBottomLine(gradientContainer, "#ffffff", "#aaaaaa");
		return gradientContainer;
	},
		
	childWithClassInNode: function(className, node){
		var result = null;
		var childs = node.childNodes;
		for(var i=0; i<childs.length; i++){
			if(childs[i].className == className){
				result = childs[i];
				break;
			}
		}
		return result;
	},
	
	notificationCenter: NotificationCenter.defaultNotificationCenter(),
	
	setSelectionEnabled: function(element, enable){
		var changed = true;
		if(element.selectionState)
			changed = element.selectionState.enable != enable;
		element.selectionState = {enable:enable};
		if(changed){
			if(enable){
				element.style.cursor = element.origCursor;
				element.onselectstart = element.origOnSelectStart;
				element.unselectable = "off";
				element.style.MozUserSelect = "";
				element.style.KhtmlUserSelect = "";
			}else{
				if(!element.origOnSelectStart){
					element.origOnSelectStart = element.onselectstart;
					element.origCursor = element.style.cursor;
				}
				element.onselectstart = function() {
					return false;
				};
				element.unselectable = "on";
				element.style.MozUserSelect = "none";
				element.style.KhtmlUserSelect = "none";
				element.style.cursor = "default";
			}		
		}
	},
	
	dragDropPasteboard: [],
	
	dropObservers: [],
	
	// Drop Observers must implement this functions:
	//		validateDrop(pos, pasteboard) return true if the drop will be accepted
	//		acceptDrop(pos, pasteboard)
	
	addDropObserver: function(observer){	   
		if(this.dropObservers.indexOf(observer) == -1)
			this.dropObservers.push(observer);	
	},
	
	removeDropObserver: function(observer){
		if(this.dropObservers.indexOf(observer) != -1)
			this.dropObservers = this.dropObservers.without(observer);
	},
	
	notifyDropObserversOnMouseMove: function(event){
		for(var i=0; i<this.dropObservers.length; i++){
			this.dropObservers[i].validateDrop({x:event.clientX, y:event.clientY}, this.dragDropPasteboard);
		}
	},

	notifyDropObserversOnMouseUp: function(event){
		for(var i=0; i<this.dropObservers.length; i++){
			if(this.dropObservers[i].validateDrop({x:event.clientX, y:event.clientY}, this.dragDropPasteboard))
				this.dropObservers[i].acceptDrop({x:event.clientX, y:event.clientY}, this.dragDropPasteboard)
		}	
	},
	
	// A shield is an Element that captures all mouse clicks behind an element with a greater z-index.
	// The method clickedInShield is called on the delegate if a user clicks into the shield.
	//
	
	activateShield: function(delegate, zIndex){
		if(!PWUtils.shield){
			PWUtils.shield = DIV({'class':'PWUtilsShield'});
			$$('body')[0].appendChild(PWUtils.shield);
		}
		PWUtils.shieldDelegate = delegate;		
		PWUtils.shield.style.zIndex = zIndex;
		PWUtils.shield.style.display = 'block';
		PWUtils.observe(PWUtils.shield, "click", PWUtils.clickedInShield);
	},
	
	deactivateShield: function(){
		PWUtils.shield.style.display = 'none';
		PWUtils.stopObserving(PWUtils.shield, "click", PWUtils.clickedInShield);		
	},
	
	clickedInShield: function(event){
		PWUtils.shieldDelegate.clickedInShield.call(PWUtils.shieldDelegate, event, PWUtils.shield);
	}
	
}

/* Function stringWithFormat(format_string,arguments...)
 * Javascript emulation of the C printf function (modifiers and argument types 
 *    "p" and "n" are not supported due to language restrictions)
 *
 * Copyright 2003 K&L Productions. All rights reserved
 * http://www.klproductions.com 
 *
 * Terms of use: This function can be used free of charge IF this header is not
 *               modified and remains with the function code.
 * 
 * Legal: Use this code at your own risk. K&L Productions assumes NO resposibility
 *        for anything.
 ********************************************************************************/
function stringWithFormat(fstring){ 
	var pad = function(str,ch,len)
	  { var ps='';
		for(var i=0; i<Math.abs(len); i++) ps+=ch;
		return len>0?str+ps:ps+str;
	  }
	var processFlags = function(flags,width,rs,arg)
	  { var pn = function(flags,arg,rs)
		  { if(arg>=0)
			  { if(flags.indexOf(' ')>=0) rs = ' ' + rs;
				else if(flags.indexOf('+')>=0) rs = '+' + rs;
			  }
			else
				rs = '-' + rs;
			return rs;
		  }
		var iWidth = parseInt(width,10);
		if(width.charAt(0) == '0')
		  { var ec=0;
			if(flags.indexOf(' ')>=0 || flags.indexOf('+')>=0) ec++;
			if(rs.length<(iWidth-ec)) rs = pad(rs,'0',rs.length-(iWidth-ec));
			return pn(flags,arg,rs);
		  }
		rs = pn(flags,arg,rs);
		if(rs.length<iWidth)
		  { if(flags.indexOf('-')<0) rs = pad(rs,' ',rs.length-iWidth);
			else rs = pad(rs,' ',iWidth - rs.length);
		  }    
		return rs;
	  }
	var converters = new Array();
	converters['c'] = function(flags,width,precision,arg)
	  { if(typeof(arg) == 'number') return String.fromCharCode(arg);
		if(typeof(arg) == 'string') return arg.charAt(0);
		return '';
	  }
	converters['d'] = function(flags,width,precision,arg)
	  { return converters['i'](flags,width,precision,arg); 
	  }
	converters['u'] = function(flags,width,precision,arg)
	  { return converters['i'](flags,width,precision,Math.abs(arg)); 
	  }
	converters['i'] =  function(flags,width,precision,arg)
	  { var iPrecision=parseInt(precision);
		var rs = ((Math.abs(arg)).toString().split('.'))[0];
		if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
		return processFlags(flags,width,rs,arg); 
	  }
	converters['E'] = function(flags,width,precision,arg) 
	  { return (converters['e'](flags,width,precision,arg)).toUpperCase();
	  }
	converters['e'] =  function(flags,width,precision,arg)
	  { iPrecision = parseInt(precision);
		if(isNaN(iPrecision)) iPrecision = 6;
		rs = (Math.abs(arg)).toExponential(iPrecision);
		if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs.replace(/^(.*)(e.*)$/,'$1.$2');
		return processFlags(flags,width,rs,arg);        
	  }
	converters['f'] = function(flags,width,precision,arg)
	  { iPrecision = parseInt(precision);
		if(isNaN(iPrecision)) iPrecision = 6;
		rs = (Math.abs(arg)).toFixed(iPrecision);
		if(rs.indexOf('.')<0 && flags.indexOf('#')>=0) rs = rs + '.';
		var a = rs.toArray();
		if(a.indexOf('.') != -1){
			var oCount = 0;
			if(!precision) {
				for(var i=a.length-1; i>0; i--){	// PWMod: remove trailing zeros
					var char = a[i];
					if(char != "0" && char != ".")
						break;
					oCount++;
					if(char == ".")
						break;
				}
			}
			rs = rs.substring(0, rs.length-oCount);
		}
		return processFlags(flags,width,rs,arg);
	  }
	converters['G'] = function(flags,width,precision,arg)
	  { return (converters['g'](flags,width,precision,arg)).toUpperCase();
	  }
	converters['g'] = function(flags,width,precision,arg)
	  { iPrecision = parseInt(precision);
		absArg = Math.abs(arg);
		rse = absArg.toExponential();
		rsf = absArg.toFixed(6);
		if(!isNaN(iPrecision))
		  { rsep = absArg.toExponential(iPrecision);
			rse = rsep.length < rse.length ? rsep : rse;
			rsfp = absArg.toFixed(iPrecision);
			rsf = rsfp.length < rsf.length ? rsfp : rsf;
		  }
		if(rse.indexOf('.')<0 && flags.indexOf('#')>=0) rse = rse.replace(/^(.*)(e.*)$/,'$1.$2');
		if(rsf.indexOf('.')<0 && flags.indexOf('#')>=0) rsf = rsf + '.';
		rs = rse.length<rsf.length ? rse : rsf;
		return processFlags(flags,width,rs,arg);        
	  }  
	converters['o'] = function(flags,width,precision,arg)
	  { var iPrecision=parseInt(precision);
		var rs = Math.round(Math.abs(arg)).toString(8);
		if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
		if(flags.indexOf('#')>=0) rs='0'+rs;
		return processFlags(flags,width,rs,arg); 
	  }
	converters['X'] = function(flags,width,precision,arg)
	  { return (converters['x'](flags,width,precision,arg)).toUpperCase();
	  }
	converters['x'] = function(flags,width,precision,arg)
	  { var iPrecision=parseInt(precision);
		arg = Math.abs(arg);
		var rs = Math.round(arg).toString(16);
		if(rs.length<iPrecision) rs=pad(rs,' ',iPrecision - rs.length);
		if(flags.indexOf('#')>=0) rs='0x'+rs;
		return processFlags(flags,width,rs,arg); 
	  }
	converters['s'] = function(flags,width,precision,arg)
	  { var iPrecision=parseInt(precision);
		var rs = arg;
		if(rs.length > iPrecision) rs = rs.substring(0,iPrecision);
		return processFlags(flags,width,rs,0);
	  }
	farr = fstring.split('%');
	retstr = farr[0];
	fpRE = /^([-+ #]*)(\d*)\.?(\d*)([cdieEfFgGosuxX])(.*)$/;
	for(var i=1; i<farr.length; i++)
	  { fps=fpRE.exec(farr[i]);
		if(!fps) continue;
		if(arguments[i]!=null) retstr+=converters[fps[4]](fps[1],fps[2],fps[3],arguments[i]);
		retstr += fps[5];
	  }
	return retstr;
}

/*	Date Extensions:
 *
 * 
 *
 */   

Date.prototype.timeIntervalSinceDate = function(aDate)
{
	return (this.getTime()-aDate.getTime())/1000;	
}

Date.prototype.wholeDaysSinceDate = function(date)
{
	if(!date)
		return 0;
	return (this.getTime() - date.getTime())/(24.0*3600.0*1000.0);
}

Date.prototype.getCalendarWeekUTC = function(environment)
{
	var year = this.getUTCFullYear() + 1;	
	var startOfFirstWeek = Date.startOfCalendarWeekInYear(1, year, true, environment);
	while(startOfFirstWeek.getTime() > this.getTime()){
		startOfFirstWeek = Date.startOfCalendarWeekInYear(1, --year, true, environment);
	}
	
	var days = this.wholeDaysSinceDate(startOfFirstWeek);
	return Math.floor(1+days/7);	
}

Date.startOfCalendarWeekInYear = function(week, year, utc, environment)
{
	var day = environment.firstDayOfWeek;
	var anchorDayInJanuary = day == 2 ? 4 : 1;		
	var date = utc ? new Date(Date.UTC(year, 0, anchorDayInJanuary)) : new Date(year, 0, anchorDayInJanuary);
	var result = date.getTime();
	var weekday;
	while(1){
		date = new Date(result);
		weekday = (utc ? date.getUTCDay() : date.getDay())+1;
		if(weekday!=day)
			result -= 86400000;
		else
			break;
	}
	result += ((week-1)*604800)*1000;
	return new Date(result);
}


Date.prototype.isEqual = function(date){
	if(!date)
		return false;
	return (this.getTime() == date.getTime());
}
	
Date.prototype.nextDay = function(utc){
	return Date.add("d", 1, this, utc);
}

/*
 * Instance method that you can call on Date objects
 * var dt = new Date(); 
 * dt.add('d', 5); 
 * return dt;
 * ========================
 */
Date.prototype.add = function(interv, incr, utc) {
    var ndt = Date.add(interv, incr, this, utc);
    this.setTime(ndt.getTime());
}

/*
 * Static method that requires a Date object passed in
 * ========================
 */
Date.add = function(interv, incr, dt, utc) {
    /*
    yyyy - Year
    q - Quarter
    m - Month
    y - Day of year
    d - Day
    w - Weekday
    ww - Week of year
    h - Hour
    n - Minute
    s - Second
    */
        
    var retDate = new Date(dt);    
    switch(interv) {
        case 'yyyy':
            if(utc)
				retDate.setUTCFullYear(dt.getUTCFullYear() + incr);
			else
				retDate.setFullYear(dt.getFullYear() + incr);
//			retDate.setFullYear((utc ? dt.getUTCFullYear() : dt.getFullYear()) + incr);
            break;
        case 'q':
            //var currQ = parseInt(mon/3)+1;
            //var incrYear = (incr > 0) ? parseInt(incr/4)+1 : parseInt(incr/4);
            
            // Naive implementation of quarters as just adding three months
            incr*=3;
            
            break;
        case 'm': 
	       var yea=utc ? dt.getUTCFullYear() : dt.getFullYear();
		   var mon=utc ? dt.getUTCMonth() : dt.getMonth();
            if (incr < 0){
                while (incr < 0){
                    incr++;
                    mon--;
                    if(mon < 0){
                        mon=11;
                        yea--;
                    }
                }                   
            }
            else {
                while(incr > 0){
                    incr--;
                    mon++;
                    if (mon > 11){
                        mon=0;
                        yea++;
                    }
                }
            } 
            if(utc){
				retDate.setUTCMonth(mon);
				retDate.setUTCFullYear(yea);			
				if (retDate.getUTCMonth() != mon)
					retDate.setUTCDate(0) 
			}else{
				retDate.setMonth(mon);
				retDate.setFullYear(yea);
				if (retDate.getMonth() != mon)
					retDate.setDate(0) 
			}

            break;
        case 'd':
        case 'y':
			if(utc)
				retDate.setUTCDate(dt.getUTCDate() + incr);
			else
				retDate.setDate(dt.getDate() + incr);
            break;
        case 'h':
            retDate = dt.getTime();
            retDate = retDate + (incr*3600000);
            retDate = new Date(retDate);
            break;
        case 'n':
            retDate = dt.getTime();
            retDate = retDate + (incr*60000);
            retDate = new Date(retDate);
            break;
        case 's':
            retDate = dt.getTime();
            retDate = retDate + (incr*1000);
            retDate = new Date(retDate);
            break;
        case 'w':
            var weekSpan = parseInt(incr/5); // Number of weeks spanned
            // If less than one week, check for intervening weekend
            if (weekSpan == 0) {
				if(utc){
					if ((dt.getUTCDay() + incr) > 4)
						retDate.setUTCDate(dt.getUTCDate() + incr + 2);
					else
						retDate.setUTCDate(dt.getUTCDate() + incr);
				}else{
					if ((dt.getDay() + incr) > 4)
						retDate.setDate(dt.getDate() + incr + 2);
					else
						retDate.setDate(dt.getDate() + incr);				
				}
            }
            // Otherwise add along with intervening weekend days
            else {
				if(utc)
					retDate.setUTCDate(dt.getUTCDate() + incr + (2*weekSpan));
				else
					retDate.setDate(dt.getDate() + incr + (2*weekSpan));
            }
            break;
        case 'ww':
			if(utc)
				retDate.setUTCDate(dt.getUTCDate() + (incr*7));
			else
				retDate.setDate(dt.getDate() + (incr*7));
            break;
        default:
            // Do nothing
            break;
        
    } 
    return retDate;
}

/*
 * Instance method that you can call on Date objects
 * var dt = new Date('10/31/2112'); 
 * var diff = dt.diff('d', new Date('12/31/2112')); 
 * return diff;
 * ========================
 */
Date.prototype.diff = function(interv, dt2, utc) {
    return Date.diff(interv, this, dt2, utc);
}

/*
 * Static method that requires a Date object passed in
 * ========================
 */
Date.diff = function(interv, dt1, dt2, utc) {

    /*
    yyyy - Year
    q - Quarter
    m - Month
    y - Day of year
    d - Day
    w - Weekday
    ww - Week of year
    h - Hour
    n - Minute
    s - Second
    */
    
    // Convert Unix timestamp to Date obj if need be
    var dtParam1 = (typeof dt1 == 'number') ? new Date(dt1) : dt1;
    // Convert Unix timestamp to Date obj if need be
    var dtParam2 = (typeof dt2 == 'number') ? new Date(dt2) : dt2; 
    var intervFlag = interv.toLowerCase();
    // Year, month
    var yeaDiff;
    var monDiff;
	if(utc){
		yeaDiff = dtParam2.getUTCFullYear() - dtParam1.getUTCFullYear();
		monDiff = (dtParam2.getUTCMonth() - dtParam1.getUTCMonth()) + (yeaDiff * 12);
	}else{
		yeaDiff = dtParam2.getFullYear() - dtParam1.getFullYear();
		monDiff = (dtParam2.getMonth() - dtParam1.getMonth()) + (yeaDiff * 12);	
	}
    // All others -- build incrementally
    var msDiff = dtParam2.getTime() - dtParam1.getTime(); // Millisecs
    var secDiff = msDiff/1000;
    var minDiff = secDiff/60;
    var houDiff = minDiff/60;
    var dayDiff = houDiff/24;
    // Counts number of seven-day intervals, not calendar weeks
    var weeDiff = dayDiff/7; 
    var ret = 0;
    
    switch(intervFlag) {
        case 'yyyy':
            ret = yeaDiff;
            break;
        case 'q':
            var mA = utc ? dtParam1.getUTCMonth() : dtParam1.getMonth();
            var mB = utc ? dtParam2.getUTCMonth() : dtParam2.getMonth();
            // Figure out which quarter the months are in
            var qA = parseInt(mA / 3) + 1;
            var qB = parseInt(mB / 3) + 1;
            // Add quarters for any year difference between the dates
            qB += (yeaDiff * 4);
            ret = qB - qA;
            break;
        case 'm':
            ret = monDiff;
            break;            
        case 'd':
        case 'y':
            // Daylight savings time switchover
            // Value will be over or under by an hour
            ret = dayDiff;
            break;
        case 'h':
            ret = houDiff;
            break;
        case 'n':
            ret = minDiff;
            break;
        case 's':
            ret = secDiff;
            break;
        case 'w':
            ret = dayDiff;
            break;
        case 'ww':
            ret = weeDiff;
            break;
        default:
            // Do nothing
            break;
        
    } 
    ret = Math.round(ret);
    return ret; // Return an integer
}

// ---------------------------------------------------------------------------------
// Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
// ---------------------------------------------------------------------------------

// Clamp a number to a range
Number.prototype.clamp = function(min,max)
{
	var c = this;
	if(c < min)
		c = min;
	if(c > max)
		c = max;
	return c;
}

// Get characters from the right end of a string
String.prototype.right = function(n)
{
	if(n < this.length)
		return this.slice(this.length-n);
	else
		return this;
}


// ---------------------------------------------------------------------------------
// RGB colour object
// ---------------------------------------------------------------------------------

// Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
function RGB(r,g,b)
{
	this.r = 0;
	this.g = 0;
	this.b = 0;
	if(typeof r == "string"){
		if(r.substr(0,1) == "#"){
			if(r.length == 7){
				this.r = parseInt(r.substr(1,2),16)/255;
				this.g = parseInt(r.substr(3,2),16)/255;
				this.b = parseInt(r.substr(5,2),16)/255;
			}else{
				this.r = parseInt(r.substr(1,1),16)/15;
				this.g = parseInt(r.substr(2,1),16)/15;
				this.b = parseInt(r.substr(3,1),16)/15;
			}
		}else{
			var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/ ;
			var c = r.match(rgbPattern);
			if(c){
				this.r = parseInt(c[1],10)/255;
				this.g = parseInt(c[2],10)/255;
				this.b = parseInt(c[3],10)/255;
			}
		}
	}else{
		this.r = r;
		this.g = g;
		this.b = b;
	}
	return this;
}

// Mixes this colour with another in a specified proportion
// c = other colour to mix
// f = 0..1 where 0 is this colour and 1 is the new colour
// Returns an RGB object
RGB.prototype.mix = function(c,f)
{
	return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
}

// Return an rgb colour as a #rrggbb format hex string
RGB.prototype.toString = function()
{
	var r = this.r.clamp(0,1);
	var g = this.g.clamp(0,1);
	var b = this.b.clamp(0,1);
	return("#" + ("0" + Math.floor(r * 255).toString(16)).right(2) +
				 ("0" + Math.floor(g * 255).toString(16)).right(2) +
				 ("0" + Math.floor(b * 255).toString(16)).right(2));
}
 	

/*********************************************** GUI Elements **********************************************/

var PopUpButton = Base.extend({
	constructor: function(height, align, imageURL, imageWidth, imageHeight, delegate, valueList, fontSize){
		this.delegate = delegate;
		this.height = height;
		this.fontSize = fontSize;
		this.valueList = valueList;		
		this.align = align;
		var imgOffsetTop = Prototype.Browser.WebKit ? (-3) : (-1);
		var imgOffsetRight = Prototype.Browser.WebKit ? 1 : 0;
		this.HTMLElement = DIV(	{style:"position:absolute; top:4px; overflow:visible; height:"+height+"px; width:"+height+"px;"},
								this.image = IMG({src:imageURL, width:imageWidth, height:imageHeight, style:"position:absolute; right:"+((height-imageWidth)/2+imgOffsetRight)+"px; top:"+((height-imageHeight)/2+imgOffsetTop)+"px;"}));
		if(align == "left")
			this.HTMLElement.style.left = "0px";
		else 
			this.HTMLElement.style.right = "0px";					
		PWUtils.observe(this.HTMLElement, "click", this.handleClick.bindAsEventListener(this), true);
		this.HTMLElement.controller = this; 
	},
	
	createMenu: function(valueList){
		PWUtils.activateShield(this, 0);
		var selectedValue = this.selectedValue();
		this.menuItems = [];
		var parent = this.HTMLElement.parentNode;
		var body = document.getElementsByTagName("body")[0];
		var x = PWUtils.getX(this.HTMLElement.parentNode);
		var y = PWUtils.getY(this.HTMLElement.parentNode);

		var oldMenu = this.menu;
		if(this.menu)
			this.menu.removeChild(this.menu.childNodes[0]);
		else
			this.menu = DIV({style:"position:absolute; z-index:1; border:1px solid #aaa; background-color:#fff;"});
		var offsetX = 3; 
		var offsetY = 3;		
		if(Prototype.Browser.WebKit){
			offsetX = 1;
			offsetY = 1;
		}else if(Prototype.Browser.IE){
			offsetX = -2; 
			offsetY = -1;
		}
		this.menu.style.top = y+offsetY+parent.offsetHeight+"px";
		this.menu.style.left = x+offsetX+"px";
		this.menu.style.width = this.HTMLElement.parentNode.offsetWidth-1+"px";	
		var tableBody;
		this.table = TABLE({style:"width:100%; border-spacing:0px;"},
							tableBody = TBODY({style:"font-size:"+this.fontSize+";"}));		
		PWUtils.observe(this.table, "click", this.handleClick.bindAsEventListener(this), true);
		if(!valueList)
			valueList = this.valueList; 
		for(var i=0; i<valueList.length; i++){
			var td;
			var selected = selectedValue && selectedValue == valueList[i];
			var color = selected ? "#3d80df" : "#ffffff";
			var tr = TR( td = TD({style:"background-color:"+color+"; cursor:default; line-height:110%;"}));
			td.selected = selected;
			td.onmouseover = function() {this.style.backgroundColor = "#3d80df"; this.selected = true;};
			td.onmouseout = function() {this.style.backgroundColor = "#ffffff"; this.selected = false;};
			td.appendChild(document.createTextNode(valueList[i]));
			this.menuItems.push(td);			
			tableBody.appendChild(tr);
		}
		this.menu.appendChild(this.table);
		if(!oldMenu)
			body.appendChild(this.menu);	
	},
	
	hideMenu: function(){
		if(this.menu) {
			PWUtils.deactivateShield();
			this.menu.style.visibility = "hidden";
		}
	},
	
	showMenu: function(valueList){
		this.createMenu(valueList);
        var top        = PWUtils.getY(this.menu);
        var bottom     = top + this.menu.getHeight();
        var maxHeight  = document.viewport.getHeight();
        var diff       = maxHeight - bottom;
        if(diff < 0)
            this.menu.style.top = "" + (top + diff) + "px";
		this.menu.style.visibility = "visible";	
	},
	
	menuIsVisible: function(){
		return this.menu && this.menu.style.visibility == "visible";
	},
	
	handleClick: function(event, row){
		var event = event || window.event;
		if(!this.menu || this.menu.style.visibility == "hidden"){
			this.showMenu(this.valueList);
		}else{
			var target = event.srcElement ? event.srcElement : event.target;
			this.hideMenu();						
			if(target.tagName == "TD"){
				var value = target.innerHTML;
				if(this.delegate){
					if(value != this._value){
						this._value = value;
						this.delegate.popUpButtonValueChanged(this, this._value);
					}
					this.delegate.popUpButtonMenuClosed(this);
				}
			}
		}
		Event.stop(event);
	},
	
	clickedInShield: function() {
		this.hideMenu();
	},

	value: function(){
		return this._value;
	},
	
	selectPreviousMenuItem: function(){
		var found = false;
		for(var i=0; i<this.menuItems.length; i++){
			var td = this.menuItems[i];
			if(td.selected){
				this.menuItems[i].selected = false;
				td.style.backgroundColor = "#ffffff";
				if(i!=0){
					this.menuItems[i-1].style.backgroundColor = "#3d80df";
					this.menuItems[i-1].selected = true;
					found = true;
					break;
				}
			}
		}
		if(!found){
			this.menuItems[this.menuItems.length-1].style.backgroundColor = "#3d80df";
			this.menuItems[this.menuItems.length-1].selected = true;		
		}		
	},
	
	selectNextMenuItem: function(){
		var found = false;
		for(var i=0; i<this.menuItems.length; i++){
			var td = this.menuItems[i];
			if(td.selected){
				if(i!=this.menuItems.length-1){
					this.menuItems[i].selected = false;
					td.style.backgroundColor = "#ffffff";
					this.menuItems[i+1].style.backgroundColor = "#3d80df";
					this.menuItems[i+1].selected = true;
					found = true;
					break;
				}
			}
		}
		if(!found){
			this.menuItems[0].style.backgroundColor = "#3d80df";
			this.menuItems[0].selected = true;		
		}
	},
	
	selectedMenuItem: function(){
		if(this.menuItems) {
			for(var i=0; i<this.menuItems.length; i++){
				var td = this.menuItems[i];
				if(td.selected)
					return td;
			}
		}
		return null;
	},
	
	selectedValue: function(){
		var item = this.selectedMenuItem();
		return item ? item.innerHTML: null;
	}
	
	
});

var ComboBox = Base.extend({
	constructor: function(height, fontSize, arrowImageURL, imageWidth, imageHeight, value, valueList, width, name, autocomplete, delegate, openMenu){
		this.autocomplete = autocomplete;
		this.delegate = delegate;
		this.fontSize = fontSize;
		this.value = value;
		this.valueList = valueList;
		this.width = width;
		if(Prototype.Browser.WebKit)
			height-=3;
		this.HTMLElement = DIV({'class':'combobox', value:value, name:name, style:"height:"+(height+6)+"px; position:relative;"},
								this.textInput=INPUT({name:name, type:'text', value:value, style:"position:absolute; left:0px; top:-1px; margin:0px; height:"+height+"px; font-size:"+fontSize+"px"}));
		if(!this.width)
			this.textInput.style.right = height+"px";
		else
			this.textInput.style.width = this.width-height+"px";
		this.HTMLElement.controller = this;
		this.popUpButton = new PopUpButton(height, "right", arrowImageURL, imageWidth, imageHeight, this, valueList, this.fontSize);
		this.HTMLElement.appendChild(this.popUpButton.HTMLElement);
		if(this.autocomplete){
			PWUtils.observe(this.textInput, "keyup", this.onKeyUp.bindAsEventListener(this), true);
			PWUtils.observe(this.textInput, "keydown", this.onKeyDown.bindAsEventListener(this), true);
		}
		if(openMenu && this.popUpButton.valueList && this.popUpButton.valueList.length) {
			var button = this.popUpButton;
			var showMenu = function(){
				button.showMenu(button.valueList);
			}
			showMenu.delay(0.1);
		}
	},

	popUpButtonValueChanged: function(popUp, value){
		this.value = value;
		this.textInput.value = this.value;
		this.HTMLElement.value = this.value;
	},
	
	popUpButtonMenuClosed: function(popUp){
		if(this.delegate && this.delegate.comboBoxMenuClosed)
			this.delegate.comboBoxMenuClosed(this);
	},

	functionKeys: [112,113,114,115,116,117,118,119,120,121,122,123],

	onKeyUp: function(event){
		var keyCode = event.keyCode;
		var matchingValues = [];			
		if( keyCode == 8 || keyCode == 46 || (!this.functionKeys[keyCode] && keyCode != 13) ){	// 8 = backspace, 46 = delete
			var searchText = this.textInput.value.toUpperCase();
			if(searchText.length){
				for(var i=0; i<this.valueList.length; i++){
					var value = this.valueList[i];
					if(value.toUpperCase().indexOf(searchText) == 0){
						matchingValues.push(value);
					}
				}
			}
			if(matchingValues.length){
				this.popUpButton.showMenu(matchingValues);	
			}else{
				this.popUpButton.hideMenu();		
			}
		}
		return false;
	},
	
	onKeyDown: function(event){
		if(this.autocomplete && this.popUpButton.menuIsVisible()){
			var keyCode = event.keyCode;			
			if(keyCode == 38){	// up
				this.popUpButton.selectPreviousMenuItem();
			}else if( keyCode == 40){  // Down
				this.popUpButton.selectNextMenuItem();
			}else if( keyCode == 13){	// enter
				var value = this.popUpButton.selectedValue();
				if(value)
					this.textInput.value = value;
				this.popUpButton.hideMenu();
			}
		}
	}
});

var DatePicker = Base.extend({
	constructor: function(height, fontSize, calendarImageURL, value, width, name, formatter, displayTime){
		this.fontSize = fontSize;
		this.value = value;
		this.width = width;
		this.formatter = formatter;
		this.displayTime = displayTime;		
		if(Prototype.Browser.WebKit)
			height-=3;
		this.HTMLElement = DIV({'class':'datePicker', name:name, value:value, style:"position:relative; height:"+(height+6)+"px;"},
								this.image=IMG({width:14, height:14, src:calendarImageURL, style:"position:absolute; left:0px; top:"+((height+6-14)*0.5)+"px;"}),
								this.textInput=INPUT({name:name, type:'text', value:this.formatter.stringForObjectValue(value), style:"position:absolute; top:0px; margin:0px; width:"+(this.width-height)+"px; left:"+height+"px; height:"+height+"px; font-size:"+fontSize+"px;"}));
		PWUtils.observe(this.image, "click", this.handleClick.bindAsEventListener(this), true);
		this.HTMLElement.controller = this;
	},
		
	handleClick: function(event, row){
		PWUtils.notificationCenter.addObserverForMessage(this, "click", "handleNotification");
		var event = event || window.event;
		var target = event.srcElement ? event.srcElement : event.target;
		var date = this.formatter.objectValueForString(this.textInput.value);
		if(!date)
			date = new Date();
		displayCalendar(date, true, this, this.textInput, this.formatter.format(), target, this.displayTime);
		Event.stop(event);
	},
	
	datePickerDidClose: function(date){
		this.textInput.value = this.formatter.stringForObjectValue(date);
		PWUtils.notificationCenter.sendMessage("datePickerDidClose", this.textInput);
	},
	
	handleNotification: function(msg, context){
		if(msg = "click"){
			var event = context || window.event;
			var target = event.srcElement ? event.srcElement : event.target;
			if(!elementBelongsToCalendar(target)){
				closeCalendar();
				PWUtils.notificationCenter.removeObserverForMessage(this, "click");
				Event.stop(context);
			}
		}
	}

});


// ........................................
// EVENT EXTENSIONS
// 
Object.extend(Event,{
  // get the character code for key pressed events.
  getCharCode: function(e) {
    return (e.keyCode) ? e.keyCode : ((e.which)?e.which:0) ; 
  },
  
  // get the pressed char as a string.
  getCharString: function(e) {
    return String.fromCharCode(Event.getCharCode(e)) ;
  },
  
  ALT_KEY: '_ALT',
  CTRL_KEY: '_CTRL',
  SHIFT_KEY: '_SHIFT'
  
});

var SegmentButton = Base.extend({
	constructor: function(segmentDescription, selectedSegmentIndex) {
		this.base();
		this.segmentDescription = segmentDescription;
		this.selectedSegmentIndex = selectedSegmentIndex;
		this.allCells = [];
		this.reactOnMouseOut = false;
		this.tempSelectedSegmentIndex = -1;
		for(var i=0; i<segmentDescription.length; i++){
			var segment = segmentDescription[i].segment;
			var method = segmentDescription[i].method;
			if(segment instanceof Array){	// button segments
				for(var x=0; x<segment.length; x++){
					var buttonPart = segment[x];
					buttonPart.origClassName = buttonPart.className;
					buttonPart.onmousedown = this.mouseDown.bindAsEventListener(this); 
					buttonPart.onmouseup = this.mouseUp.bindAsEventListener(this);
					buttonPart.onmouseout = this.mouseOut.bindAsEventListener(this);
					buttonPart.onmouseover = this.mouseOver.bindAsEventListener(this);
					buttonPart.segmentIndex = i;
					this.allCells.push(buttonPart);
				}
			}else{	// splitter segment
				segment.origClassName = segment.className;
				this.allCells.push(segment);
			}
		}
		if(this.selectedSegmentIndex != null)
			this.selectSegmentAtIndex(selectedSegmentIndex);
		this.mouseIsUp = false;
		var me = this;
		var MouseUpDetector = Base.extend({
			constructor: function(){
				PWUtils.observe($("body"), "mouseup", this.mouseUp.bindAsEventListener(this));
			},
			mouseUp: function(event){
				me.mouseIsUp = true;
			}
		});
		this.mouseUpDetector = new MouseUpDetector();	
		
	},
	
	cellFromEvent: function(event){
		var event = event || window.event;
		var cell = event.srcElement ? event.srcElement : event.target;
		while(cell.tagName != "TD")
			cell = cell.parentNode;
		return cell;			
	},

	cellsForSegmentIndex: function(index){
		var cells = [];
		var segment = this.segmentDescription[index];
		for(var i=0; i<segment.segment.length; i++)
			cells.push(segment.segment[i]);
		if(index != 0){
			var prevSegment = this.segmentDescription[index-1];
			if(typeof(prevSegment.segment) != "array")
				cells.push(prevSegment.segment);
		}
		var nextSegment = this.segmentDescription[index+1];
		if(nextSegment && typeof(nextSegment.segment) != "array")
			cells.push(nextSegment.segment);
		return cells;
	},
	
	relatedCells: function(cell){
		return this.cellsForSegmentIndex(cell.segmentIndex);
	},
	
	markCells: function(cells, mark){
		for(var i=0; i<cells.length; i++)
			cells[i].className = cells[i].origClassName + (mark ? mark : "");
	},
	
	selectSegmentAtIndex: function(index){
		this.segmentDescription[index].method.call();
		this.markCells(this.cellsForSegmentIndex(index), "Selected");
		this.selectedSegmentIndex = index;
	},
	
	activateSegmentAtIndex: function(index) {
		this.markCells(this.allCells);
		this.selectSegmentAtIndex(index);
	},

	mouseDown: function(event){
		var cell = this.cellFromEvent(event);
		this.markCells(this.relatedCells(cell), cell.segmentIndex == this.selectedSegmentIndex ? "SelectedPressed" :  "Pressed");
		this.tempSelectedSegmentIndex = cell.segmentIndex;
		this.mouseIsUp = false;
		this.reactOnMouseOut = true;
	},
	
	mouseUp: function(event){
		var cell = this.cellFromEvent(event);
		this.markCells(this.allCells);
		this.selectSegmentAtIndex(cell.segmentIndex);
		this.tempSelectedSegmentIndex = -1;
		this.reactOnMouseOut = false;
	},

	mouseOut: function(event){
		if(this.reactOnMouseOut){
			this.markCells(this.relatedCells(this.cellFromEvent(event)));
			this.selectSegmentAtIndex(this.selectedSegmentIndex);
		}
	},

	mouseOver: function(event){
		if(this.tempSelectedSegmentIndex != -1 && this.mouseIsUp)
			this.tempSelectedSegmentIndex = -1;
		var cell = this.cellFromEvent(event);
		if(this.tempSelectedSegmentIndex == cell.segmentIndex)
			this.markCells(this.relatedCells(cell), cell.segmentIndex == this.selectedSegmentIndex ? "SelectedPressed" :  "Pressed");
	}
	
});

function pointIsInElement(x, y, elem)
{
	var offset = Element.viewportOffset(elem);
	var right = offset.left+elem.getWidth();
	if(x<offset.left || x>right)
		return false;
	var bottom = offset.top+elem.getHeight();
	if(y<offset.top || y>bottom)
		return false;
	return true;
}

/* ************************************ Popup-Menu: **************************************** */

var popUpIsVisible = false;

function removePopUpMenuEventHandlerFromElement(element) {
	PWUtils.stopObserving(element, 'click', element._mouseUp);
	PWUtils.stopObserving(element, 'mousedown', element._mouseDown);
	PWUtils.stopObserving(element, 'mouseup', element._mouseUp);	
	PWUtils.setSelectionEnabled(element, true);
}

function makePopUpMenuForElement(element, value, options, selectionDidChangeMethod, shouldOpenMenuMethod, onClickMethod, optionsMethod, context) {
	var mouseX;
	var mouseY;
	var timer = 0;
	var date = 0;
	var table = null;
	var wantsToClose = false;
	
	PWUtils.setSelectionEnabled(element, false);
	if(options.pullDown && !options.delay)
		options.delay = 0.1;

	function shouldOpenMenu(event) {
		if(popUpIsVisible)
			return false;
		if(shouldOpenMenuMethod) {
			if((context && !shouldOpenMenuMethod.call(context, event)) || (!context && !shouldOpenMenuMethod(event)))
				return false;
		}
		return true;
	}
	
	function fetchPositionFromEvent(event) {
		mouseX = Event.pointerX(event);
		mouseY = Event.pointerY(event);	
	}

	element._mouseDown = function(event) {
		if(popUpIsVisible) {
			closeMenu();
			wantsToClose = true;
			return;
		}

		fetchPositionFromEvent(event);
		timer = showMenu.delay(options.delay, event);
		date = new Date();
	}

	element._mouseUp = function(event) {
		if(wantsToClose) {
			wantsToClose = false;
			return;
		}
		if(timer)
			window.clearTimeout(timer);
		fetchPositionFromEvent(event);
		if(!popUpIsVisible && (!options.delay || ((table && pointIsInElement(mouseX, mouseY, table)) || pointIsInElement(mouseX, mouseY, element)))) {
			if(onClickMethod){
				if(context)
					onClickMethod.call(context, element);
				else
					onClickMethod(element);
			}else 
				showMenu(event);
		}
	}

	function fetchOptions() {
		var result = options;
		if(optionsMethod) {
			result = context ? optionsMethod.call(context) : optionsMethod();
        }
		return result;
	}

	if(!options.pullDown)
		PWUtils.observe(element, 'click', element._mouseUp, false);
	else {
		PWUtils.observe(element, 'mousedown', element._mouseDown, false);
		PWUtils.observe(element, 'mouseup', element._mouseUp, false);
	}
	
	function closeMenu(newValue, changeValue) {
		PWUtils.stopObserving($$('body')[0], 'mousemove', handleMouseMove);
		if(options.removeAfterFirstClick) {
			if(!options.pullDown)
				PWUtils.stopObserving(element, 'click', element._mouseUp);
			else{
				PWUtils.stopObserving(element, 'mousedown', element._mouseDown);
				PWUtils.stopObserving(element, 'mouseup', element._mouseUp);				
			}
		}
		if(changeValue && selectionDidChangeMethod) {
			if(context)
				selectionDidChangeMethod.call(context, element, newValue);
			else
				selectionDidChangeMethod(element, newValue);
		}
		table.parentNode.removeChild(table);
		popUpIsVisible = false;
	}
	
	function handleMouseMove(event) {
		event = event || window.event;
		var x = Event.pointerX(event);
		var y = Event.pointerY(event)
		if(!pointIsInElement(x, y, table) && !pointIsInElement(x, y, element))
			closeMenu(value, false);				
	}
	
	function showMenu(event) {
		if(!shouldOpenMenu(event))
			return;	
		
		options = fetchOptions();
		if(!options.order)
			options.order = Object.keys(options.enums);
		if(options.pullDown && !options.delay)
			options.delay = 0.1;
		options.offsetTop = options.offsetTop || 0;
		options.offsetLeft = options.offsetLeft || 0;

		table = document.createElement('table');
		table.className='popUpMenu';
		if(options.minWidth)
			table.width = options.minWidth+"px";
		var tableBody = document.createElement('tbody');
		table.appendChild(tableBody);
		if(Prototype.Browser.IE)
			table.style.borderCollapse="collapse";
					
		function firstMenuItemClass() {
			return options.pullDown ? " firstMenuItemPullDown" : " firstMenuItem";
		}

		var hasGroups = false;
		var index = 0;
		var selectedIndex = 0;
		for(var i=0; i<options.order.length; i++){
			var key = options.order[i];            
			var title = options.enums[key];
			var isSeparatorItem = title.startsWith('//separator');
			var isGroupItem = title.startsWith('//') && !isSeparatorItem;
			if(isGroupItem && title.length > 2)
				title = title.substr(2);
			if(isGroupItem)
				hasGroups = true;
			if(key == value)
				selectedIndex = index;
			index++;
			var td;
			var tr = TR( td = TD() );
			td.originalClassName = "popUpMenuItem"
			td.className = "popUpMenuItem"
			if(i==0) {
				td.className += firstMenuItemClass();
				td.first = true;
			}
			if(i==options.order.length-1) {
				td.className += " lastMenuItem";
				td.last = true;			
			}
			if(!isGroupItem && !isSeparatorItem) {
				td.onmouseover = function(event) {							
					event = event || window.event;
					this.className = "popUpMenuItemSelected";
					if(this.first)
						this.className += firstMenuItemClass();
					if(this.last)
						this.className += " lastMenuItem";
				};
				td.onmouseout = function(event) {
					event = event || window.event;
					this.className = "popUpMenuItem";
					if(this.first)
						this.className += firstMenuItemClass();
					if(this.last)
						this.className += " lastMenuItem";
				};
			}
			
			function action(event) {
				event = event || window.event;
				var elem = Event.element(event);
				while(elem && elem.tagName != 'TD')
					elem = elem.parentNode;
				var newValue = elem.menuValue;
				closeMenu(newValue, true);
				Event.stop(event);			
			}

			if(options.pullDown)
				td.onmouseup = action;
			else
				td.onclick = action;
			
			var content = SPAN();
			if(options.images) {
				var image = options.images[key];
				var img;
				if(image) {
					content.appendChild(img = IMG({src:image}));
					if(options.imageClass)
						img.className = options.imageClass;
				}
			}
			if(!options.showOnlyImages) {
				if(isGroupItem)
					content.style.color = "#999";
				else if(hasGroups) 
					content.style.paddingLeft = "12px";
				if(!isSeparatorItem)
					content.appendChild(document.createTextNode(title))
			}
			if(isSeparatorItem)
				content.appendChild(DIV({'class':'separator'}))
			td.appendChild(content);
			td.menuValue = key;
			tableBody.appendChild(tr);						
		}
		$$("body")[0].appendChild(table);
		table = $(table);
		var rowHeight = table.getHeight()/index;
		var width = table.getWidth();
		if(options.pullDown) {
			table.style.top = PWUtils.getY(element)+element.offsetHeight+options.offsetTop+"px";
			table.style.left = PWUtils.getX(element)+options.offsetLeft+"px";		
		}else{
			table.style.top = mouseY-rowHeight/2-rowHeight*selectedIndex +"px";
			table.style.left = mouseX-width/2+ "px"; 
		}
		table.style.visibility = "visible";
		PWUtils.observe($$('body')[0], 'mousemove', handleMouseMove, false);
		popUpIsVisible = true;
	};
}

/* ************************************ Alerts: **************************************** */

var gModalWindow = null;

var ModalAlert = Base.extend({
    constructor: function(text, subtext, image, onclick, options) {
		this.base();
		this.onclick = onclick;
		this.options = options || {};
		this.options.buttons = (options && options.buttons) || ['OK'];
		this.shakeOffset = 0;
		this.windowResizedActionEventListener = this.center.bindAsEventListener(this);	
		this.shield = $(DIV({'class':'alertShield'}));
		var titleAlignment = this.options.titleAlignment ? this.options.titleAlignment : 'left';
		this.alertContainer =	$(DIV({'class':'alertContainer'}, 
									TABLE({'class':'alertTable'}, 
										TBODY({}, 
											  TR({}, TD({'id':'alertImageContainer'}), TD({'id':'alertTextContainer', style:'text-align:'+titleAlignment+';'}, SPAN({'id':'alertText'}), P({}, SPAN({'id':'alertSubtext'})))),
											TR({}, TD({'id':'alertButtonContainer', colSpan:(image ? 2 : 1)}))
										)
									)
								));
		$$('body')[0].insert(this.shield);
		$$('body')[0].insert(this.alertContainer);
		if(!image)
			$('alertImageContainer').remove();
		else
			$('alertImageContainer').insert(IMG({'id':'alertIcon', src:image}));
		var me = this;
		for(var i=0; i<this.options.buttons.length; i++){
			var label = this.options.buttons[i];
			var button 
			if(Prototype.Browser.IE) {	// I don't get the other button rendered correctly in IE so I use standard buttons
				button= $(BUTTON({'class':'IEAlertButton', type:'button'}));
				button.appendChild(document.createTextNode(label));
			}else{
				var blue = i==0 ? '_blue' : '';
				button= $(TABLE({'class':'Button'}, TR({}, TD({'class':'segment1'+blue, style:'padding-left:0px;'}), TD({'class':'segment2'+blue}), TD({'class':'segment4'+blue}))));
				button.select('.segment2'+blue)[0].appendChild(document.createTextNode(label));
			}
			button.isFirstButton = i==0;
			button.onclick = function(event) {
				event = event || window.event;
				var element = Event.element(event);
				while(!element.value)
					element = element.parentNode;
				var isValid = true;
				if(me.options.onValidate && element.isFirstButton) {
					isValid = me.options.onValidate(this);				
				}
				if(isValid) {
					if(onclick)
						me.onclick(element.value);
					me.hide();
				}else
					me.shake();
			};
			button.value = label;
			$('alertButtonContainer').insert(button);
			PWUtils.setSelectionEnabled(button, false);
		}
		if(text)
			$('alertText').appendChild(document.createTextNode(text));
		if(subtext) {
			if(typeof subtext == 'string')
				$('alertSubtext').appendChild(document.createTextNode(subtext));
			else {
				this.customElement = subtext;
				$('alertSubtext').appendChild(subtext);	
				this.inputs = $('alertSubtext').select('INPUT');
				if(this.inputs && this.inputs.length) {
					var me = this;
					window.setTimeout(function(){
						me.selectNextInput(); 
					}, 10);
				}
			}
		}
		this.alertWidth = this.alertContainer.getWidth(); 
		this.alertHeight = this.alertContainer.getHeight(); 
		this.alertContainer.style.top = -this.alertHeight+"px";
		this.alertContainer.style.visibility = "visible";
		this.center();
		this.show();
	},
	
	selectNextInput: function() {
		if(this.inputs) {
			var newFocusedInput = this.inputs[0];
			for(var i=0; i<this.inputs.length; i++) {
				var input = this.inputs[i];
				if(input.isFocused) {
					input.isFocused = false;
					if(i+1<this.inputs.length)
						newFocusedInput = this.inputs[i+1];
				}
			}
			if(newFocusedInput.focus) {
				newFocusedInput.focus();
				if(newFocusedInput.select)
					newFocusedInput.select();
			}					
			newFocusedInput.isFocused = true;			
		}
	},

	hide: function() {
		var me = this;
		PWUtils.stopObserving(window, "resize", this.windowResizedActionEventListener);		
		new Fx.Style(this.alertContainer, 'top', {duration:200, onComplete:function(){Element.remove(me.alertContainer);}})._start(0, -this.alertHeight);
		if(Prototype.Browser.WebKit){
			Element.remove(me.shield); 
			delete gModalWindow; 
			gModalWindow = null;	
		}else
			new Fx.Style(this.shield, 'opacity', {duration:200, onComplete:function(){ Element.remove(me.shield); delete gModalWindow; gModalWindow = null;}})._start(0.2, 0);		
	},

	center: function() {
		this.alertContainer.style.left = (((Prototype.Browser.IE ? document.documentElement.clientWidth : window.innerWidth)-this.alertWidth)/2)+this.shakeOffset+"px";
	},

	shake: function() {
		var shakeTimer;
		var shakes = 8;
		var me = this;
		var shakerMethod = function() {
			me.shakeOffset = ((shakes--)%2==0) ? 20 : 0;
			me.center();
			if(!shakes)
				window.clearInterval(shakeTimer);
		}
		shakeTimer = window.setInterval(shakerMethod, 50);
	},

	show: function() {
		PWUtils.observe(window, "resize", this.windowResizedActionEventListener, true);
		if(!Prototype.Browser.WebKit)
			new Fx.Style(this.shield, 'opacity', {duration:200})._start(0, 0.2);
		new Fx.Style(this.alertContainer, 'top', {duration:200})._start(-this.alertHeight, -10);
	},

	handleKeyboardEvent: function(event, keys) {
		var key = keys.length ? keys[0] : null;
		if(key) {
			var button = 0;
			if(key == "esc" && this.options.buttons.length > 1)
				button = 1;
			if(key == "esc" || key == "enter"){
				var isValid = true;
				if(this.options.onValidate && key == "enter")
					isValid = this.options.onValidate(this);
				if(isValid || key == "esc") {
					if(this.onclick)
						this.onclick(this.options.buttons[button], this);
					this.hide();
				}else
					this.shake();
			}
			if(key == "tab")
				this.selectNextInput();
		}
	}
});

var disableAlert = false;

function runAlert(text, subtext, image, onclick, options) {
	if(disableAlert)
		return;
	if(!gModalWindow)
		gModalWindow = new ModalAlert(text, subtext, image, onclick, options);
}

function runLoginDialog(title, onValidate, context, environment, username, pwd, possibleUsernames) {
	var user		= environment.localizedString("Name:");
	var password	= environment.localizedString("Password:");
	var ok			= environment.localizedString("OK");
	var cancel		= environment.localizedString("Cancel");
	var buttons		= [ok, cancel];
	var userInput;
	var passwordInput;
	var form;
	pwd = pwd || '';
	username = username || '';

	if(possibleUsernames && possibleUsernames.length){
		if(Prototype.Browser.IE){
			form = FORM({style:'overflow:hidden;'}, 
								SPAN({}, user), SPAN({}, userInput = SELECT({name:'username', style:'width:97%;'})),
								SPAN({}, password), SPAN({}, passwordInput = INPUT({type:'password', style:'width:97%;', value:pwd}))
							);
		}else{				
			form = FORM({style:'overflow:hidden;'}, 
								TABLE({style:'width:300px;'},
									TR({}, TD({style:'text-align:right;'}, SPAN({}, user)), TD({style:'overflow:hidden; width:218px;'}, userInput = SELECT({name:'username', style:'width:97%;'}))), 
									TR({}, TD({style:'text-align:right;'}, SPAN({}, password)), TD({style:'overflow:hidden; width:218px;'}, passwordInput = INPUT({type:'password', style:'width:97%;', value:pwd}))) 
								)
							);	
		}		
		for(var i=0; i<possibleUsernames.length; i++) {
			var aUsername = possibleUsernames[i];
			var option = OPTION({value:i, label:aUsername}, aUsername);
			userInput.appendChild(option);
		}
	}else{
		if(Prototype.Browser.IE){
			form = FORM({style:'overflow:hidden;'}, 
							SPAN({}, user), SPAN({}, userInput = INPUT({type:'text', style:'width:97%;', value:username})),
							SPAN({}, password), SPAN({}, passwordInput = INPUT({type:'password', style:'width:97%;', value:pwd}))
						);
		}else{							
			form = FORM({style:'overflow:hidden;'}, 
								TABLE({style:'width:300px;'},
									TR({}, TD({style:'text-align:right;'}, SPAN({}, user)), TD({style:'overflow:hidden; width:218px;'}, userInput = INPUT({type:'text', style:'width:97%;', value:username}))), 
									TR({}, TD({style:'text-align:right;'}, SPAN({}, password)), TD({style:'overflow:hidden; width:218px;'}, passwordInput = INPUT({type:'password', style:'width:97%;', value:pwd}))) 
								)
							);
		}
	}

	var validate = function(alert){
		var isValid = true;
		if(onValidate) {
			var theUsername = userInput.value;
			if(possibleUsernames && possibleUsernames.length)
				theUsername = possibleUsernames[userInput.value];
			isValid = onValidate(theUsername, passwordInput.value, context);
		}
		return isValid;
	}
	
	runAlert(title, form, environment.images.Hut, null, {buttons:buttons, onValidate:validate});		
}

function modalWindow(){
	return gModalWindow;
}

function showAboutBox(html, subtitle, copyright) {
	var pos = 120;
	var timer = null;
	var credits;
	var shield = $(DIV({'class':'alertShield', style:'cursor:default;'}));
	aboutBox =	$( DIV({'id':'aboutBox'}, 
				   SPAN({'class':'title'}, 'Merlin Web'),
				   SPAN({'class':'subtitle'}, subtitle),
				   DIV({'id':'creditsContainer'}, credits = DIV({'id':'creditsText'})),
				   DIV({'class':'topFade'}),
				   DIV({'class':'bottomFade'}),
				   SPAN({'class':'copyright'}, copyright) ));
	credits = $(credits);
	credits.innerHTML = html;

	function center() {
		aboutBox.style.left=(((Prototype.Browser.IE ? document.documentElement.clientWidth : window.innerWidth)-620)/2)+"px";
	}
	center();

	$$('body')[0].insert(shield);
	$$('body')[0].insert(aboutBox);		

	function scroll() {
	  	pos -=1;
	  	if (pos < 0-credits.getHeight()+130) 
			pos=120;
	  	credits.style.top=pos+"px";
	  	timer = window.setTimeout(scroll,30);
	}
	scroll();
	new Fx.Style(shield, 'opacity', {duration:1000})._start(0.0, 0.2);
	if(Prototype.Browser.IE)
		aboutBox.style.visibility = 'visible';
	else
		new Fx.Style(aboutBox, 'opacity', {duration:1000})._start(0.0, 1.0);		
	shield.onclick = aboutBox.onclick = function(event) {
		if(Prototype.Browser.IE)
			Element.remove(aboutBox);
		else
			new Fx.Style(aboutBox, 'opacity', {duration:500, onComplete:function(){ Element.remove(aboutBox);}})._start(1.0, 0.0);
		new Fx.Style(shield, 'opacity', {duration:500, onComplete:function(){ Element.remove(shield); }})._start(0.2, 0);		
		PWUtils.stopObserving(window, "resize", center);
		window.clearTimeout(timer);
	}
	PWUtils.observe(window, "resize", center, true);
}


/* ************************************ Tooltips: **************************************** */

var gTooltip = null;

var Tooltip = Base.extend({
	constructor: function(element, message, options) {
		this.base();
		Tooltip.detach(element);
		this.options = options || {};
		this.options.delay = typeof this.options.delay != 'undefined' || 300;
		this.fadeInOutTime = 300;
		this.element = element;
		this.message = message;
		this.mouseMoveEventListener = this.mouseMove.bindAsEventListener(this);
		this.mouseOutEventListener = this.mouseOut.bindAsEventListener(this);
		this.mouseDownEventListener = this.mouseDown.bindAsEventListener(this);
		PWUtils.observe(this.element, "mousemove", this.mouseMoveEventListener);
		PWUtils.observe(this.element, "mouseout", this.mouseOutEventListener);		
		PWUtils.observe(this.element, "mousedown", this.mouseDownEventListener);		
	},
		
	detach: function() {
		this.hide();
        PWUtils.stopObserving(this.element, 'mousemove', this.mouseMoveEventListener);
        PWUtils.stopObserving(this.element, 'mouseout', this.mouseOutEventListener);
	},

	show: function(pos){
		if(!gTooltip) {
			gTooltip = $(SPAN({'class':'tooltip'}));	
			gTooltip.style.visibility = 'hidden';
			$$('body')[0].appendChild(gTooltip);
		}
		if(gTooltip.activeTooltip && gTooltip.activeTooltip != this)
			gTooltip.activeTooltip.hide();
		if(gTooltip.activeTooltip && gTooltip.activeTooltip != this || !gTooltip.activeTooltip)
			gTooltip.innerHTML = this.message;			
		gTooltip.activeTooltip = this;		
		pos.x += 10;
		pos.y += 20;
		if(!this.width) {
			gTooltip.style.left = "0px";
			this.width = gTooltip.getWidth();
		}
		var rightEdge = pos.x+this.width;
		var bodyWidth = $$('body')[0].getWidth();
		var xOffset = bodyWidth-rightEdge;
		if(xOffset < 0)
			pos.x += (xOffset-20);
		gTooltip.style.left = pos.x + "px";
		gTooltip.style.top = pos.y + "px";
		if(!this.isVisible) {
			new Fx.Style(gTooltip, 'opacity', {duration:this.fadeInOutTime})._start(0, 0.9);
			this.isVisible = true;
		}
	},
	
	hide: function(){
		if(gTooltip && gTooltip.activeTooltip.isVisible) {
			new Fx.Style(gTooltip, 'opacity', {duration:this.fadeInOutTime})._start(0.9, 0);		
			gTooltip.activeTooltip.isVisible = false;
		}
	},
	
	mouseMove: function(event) {
		event = event || window.event;
		if(!this.mouseDownInElement){ 
			var pos = {x:Event.pointerX(event), y:Event.pointerY(event)};
			if(!this.isVisible){
				if(this.timer)
					window.clearTimeout(this.timer);
				var me = this;
				var method = function() { 
					me.show(pos); 
				}
				this.timer = method.delay(this.options.delay/1000);
			}else
				this.show(pos);
		}
	},

	stopTimer: function() {
		if(this.timer){
			window.clearTimeout(this.timer);
			this.timer = null;
		}	
	},

	mouseOut: function(event) {
		this.stopTimer();
		this.mouseDownInElement = false;
		this.hide();
	},

	mouseDown: function(event) {
		this.stopTimer();		
		this.mouseDownInElement = true;
		this.hide();
	}	
},{	
	attach: function(element, message, options){
		return element ? new Tooltip(element, message, options) : null;
	},
	
	detach: function(element){
		if(element && element.tooltip)
			element.tooltip.detach();
	}
});

/* ************************************ Tooltips: **************************************** */

function addBehaviorToMainMenu(menu){
	if(Prototype.Browser.IE)
		return;
	
	menu = $(menu);
	var showMenus = false;
	var activeItemInLevel = [];
	var items = menu.select('li');

	function cleanup () {
		PWUtils.stopObserving($$('body')[0], 'click', cleanup);
		items.each(function(item) {
			item.setAttribute('lang','');	// IE7 attribute selectors are not recognized if artificial attributes are used 
		});									// Therefore I use the 'lang' attribute.
		showMenus = false;
		menu.setAttribute('lang', '');				
		activeItemInLevel = [];		
	}

	items.each(function(item){
		item.level = 0;
		var iterItem = item;
		while(iterItem != menu) {
			if(iterItem.tagName != "UL")
				item.level++;
			iterItem=iterItem.parentNode;
		}
		if(item.level == 1){
			item.onclick = function(event) {
				showMenus = !showMenus;
				menu.setAttribute('lang', showMenus ? 'opened' : '');				
				if(showMenus){
					activeItemInLevel[item.level] = item;				
					item.setAttribute('lang', 'opened');
					PWUtils.observe($$('body')[0], 'click', cleanup);
				}else
					cleanup();
				Event.stop(event || window.event);
			};
		}		
		item.onmouseover = function(event){			
			var lastItemInThisLevel = activeItemInLevel[item.level];
			if(lastItemInThisLevel && (item != lastItemInThisLevel)) {
				items.each(function(item){
					item.setAttribute('lang', '');
				});
			}
			activeItemInLevel[item.level] = item;
			item.setAttribute('lang', 'opened');
		}
	});
}
