# Marijns implemented (relatively) generic calendaring module

function cal_show(xc,opt)
{
	if(!opt) opt={};
	cal_populate_frame(xc,opt);	// show the correct frame
	cal_populate_events(xc,opt);
	do_resize_real();	// trigger a full sw resize to apply the correct calendar sizing
	cal_popover_hide=false;
}
function cal_populate_events(xc,opt)
{
	// delay it for rapid keystrokes if needed :-)
	populate_events_cb(xc,opt);
}
#cb once events have been fetched once recusive for day/week display
function cal_populate_events_cb(xc, raw_evs)
{
	var split;

	if(xc.cur_view=='week' || xc.cur_view=='day'){
		split=cal_split_events(xc,raw_evs);
		xc.evs=split.rest;
	}else if(xc.cur_view=='year'){
		xc.evs=raw_evs;
		cal_vfy_year_events(xc,xc.evs);
	}else{
		xc.evs=raw_evs;
	}	
	
	if(xc.cur_view=='list'){
		return;
	}
	
	cal_layout_sizes(xc,xc.evs);		// prereq = table initialised
	cal_layout_slots(xc,xc.evs);		// event horiz layout
	cal_dom_assign(xc,xc.evs);			// Assign the current DOM elemts
	cal_events_dnd(xc,xc.evs);			// tie DOM to js objects

	cal_update_lists(xc,xc.evs);
	
	if(!split) {
		xc.sub=U;
		return;
	}

	var xc2=cal_copy_for_sub(xc);
	xc2.bg_resat=80;xc2.br_resat=-40;
	xc2.slotting='colrec';
	cal_copy_assign(xc2,split.allday,xc)

	cal_layout_sizes(xc2,xc2.evs);
	cal_layout_slots(xc2,xc2.evs);
	cal_dom_assign(xc2,xc2.evs);
	cal_events_dnd(xc2,xc2.evs);
}
function cal_copy_for_sub(xc)
{
	var sub=cal_new();
	sub.evs=xc.evs;
	sub.cur_view=xc.cur_view;
	sub.col_slices=[xc.columns];
	sub.cell_left=xc.cell_left;	
	sub.col_t1s[0]=xc.col_t1s[0];
	sub.col_t1s[1]=xc.col_t1s[0]+xc.columns*60*60*24*1000;
	sub.table_inset=xc.table_inset;

	sub.root='cal_events_hdr';
	sub.region='cal_header';
	
	sub.horiz=true;

	sub.columns=1;
	sub.slot_inset=17;
	sub.pix_per_cell=92;
	sub.per_slice=1;

	return sub;
}


function cal_resize(xc,h,w)
{
	if(w) xc.frame_width = w;
	
	if(!xc.populated) return;
	cal_update_table_top(xc,h,w);
	cal_update_table(xc,h,w);	// <<< This sets the column sizes as used by the rest
	cal_update_table_top2(xc,h,w);
	cal_update_redline(xc);
	if(xc.noevents) return;
	cal_update_positions(xc,xc.evs);
	if(!xc.sub) return;
	cal_update_positions(xc.sub,xc.sub.evs);
}

# *** creation

function cal_new()
{
	var cal={};
	cal.DofW=[	'$$st_cal_day_sun$$','$$st_cal_day_mon$$','$$st_cal_day_tue$$','$$st_cal_day_wed$$',\
				'$$st_cal_day_thu$$','$$st_cal_day_fri$$','$$st_cal_day_sat$$'];
	cal.DofWf=[	'$$st_cal_fday_sun$$','$$st_cal_fday_mon$$','$$st_cal_fday_tue$$','$$st_cal_fday_wed$$',\
				'$$st_cal_fday_thu$$','$$st_cal_fday_fri$$','$$st_cal_fday_sat$$'];
	cal.MofY=[  '$$st_cal_mon_jan$$','$$st_cal_mon_feb$$','$$st_cal_mon_mar$$','$$st_cal_mon_apr$$',\
				'$$st_cal_mon_may$$','$$st_cal_mon_jun$$','$$st_cal_mon_jul$$','$$st_cal_mon_aug$$',\
				'$$st_cal_mon_sep$$','$$st_cal_mon_oct$$','$$st_cal_mon_nov$$','$$st_cal_mon_dec$$'];
	cal.MofYf=[ '$$st_cal_fmon_jan$$','$$st_cal_fmon_feb$$','$$st_cal_fmon_mar$$','$$st_cal_fmon_apr$$',\
				'$$st_cal_fmon_may$$','$$st_cal_fmon_jun$$','$$st_cal_fmon_jul$$','$$st_cal_fmon_aug$$',\
				'$$st_cal_fmon_sep$$','$$st_cal_fmon_oct$$','$$st_cal_fmon_nov$$','$$st_cal_fmon_dec$$'];
	
	cal.evs=[];
	cal.root='cal_events';
	cal.region='cal_details_scrolled';
	
	cal.columns = 1;
	cal.col_t1s=[];
	cal.col_slices=[]
	cal.col_width=1;
	cal.cell_left=[];
	cal.sec_per_item=60*60*24;
	cal.pix_per_cell=17;
	cal.slot_inset=0;
	cal.slot_max=1;
	cal.slot_fill=1.0;

	cal.frame_width = 300;
	
	cal_settoday(cal);

	cal.cur_view = 'month';
	
	cal.populated=false;
	cal.extra_allday=0;
	cal.resize_by=0;
	return cal;
}
function cal_slots_new()
{
	var ss={};
	ss.slots=[];
	ss.first=ss.max=0;
	return ss;
}
function cal_reset_events(xc)
{
	xc.evs=[];
	cal_dom_assign(xc,xc.evs);
	cal_dom_assign(xc,[],'cal_events_tmp');
}


# *** Build display frames BEGIN

function cal_dimensions(xc,hsize,top_height,table_inset,lmargin,cols)
{
	xc.hsize=hsize;
	xc.top_height=top_height;
	xc.table_inset=table_inset;
	xc.lmargin=lmargin;
	xc.columns = cols;
	cal_calc_col_width(xc,xc.frame_width);
}

function cal_calc_col_width(xc,w)
{
	var ccw=/*Math.round*/ ((w-20-30-xc.table_inset)/xc.columns);
	xc.col_width=ccw;
}

function cal_populate_frame(xc,opt)
{
	if(!opt) opt={};
	if(opt.keep_frame) return;
	xc.populated = true;
	var type = xc.cur_view;
	if(type=='day') {
		cal_dimensions(xc,70,50,53,20,1);
		el_assign('cal_nav',cal_mini_days(xc));
		el_assign('cal_outline',cal_frame_dayrange(xc,1));
	}else if(type=='week') {
		cal_dimensions(xc,50,50,53,0,7);
		el_assign('cal_nav',cal_mini_days(xc));
		el_assign('cal_outline',cal_frame_dayrange(xc,7));
	}else if(type=='month') {
		cal_dimensions(xc,50,16,0,20,7);
		el_assign('cal_nav',cal_mini_months(xc,xc.cur_year));
		el_assign('cal_outline',cal_frame_month(xc,xc.cur_month,xc.cur_year));
	}else if(type=='year') {
		cal_dimensions(xc,50,16,0,20,12);
		el_assign('cal_nav',cal_mini_years(xc,xc.cur_year));
		el_assign('cal_outline',cal_frame_year(xc,xc.cur_year));
	}else if(type=='list') {
		cal_dimensions(xc,50,16,0,20,12);
		el_assign('cal_nav','dunno');
		el_assign('cal_outline',cal_frame_list(xc));
	}
}	

function cal_mini_days(xc)
{
	var tq=cal_tq_init(xc,{'type':'days'});

	var mth=xc.cur_month;
	var type=xc.cur_view;
	if(!xc.sfn_delta) xc.sfn_day='swcal_nav';
	
	var txt='';
	txt+='<div style="text-align:center;"><table onmousedown="'+xc.sfn_day+'(event,this)" style="margin:auto;width:98%;border-collapse:collapse;text-align:center;">';
	var n=1, day, other, mdelta;
	for(var i=0;i<5;i++){
		txt+='<tr>';
		for (var j=0;j<7;j++){
			cal_tq_nxtday(xc,tq);
			
			if(n<=tq.skip) {
				day=tq.prevdays-(tq.skip-n); other=true; mdelta='-1'; 
			}else if(n>tq.skip+tq.curdays){
				day=n-tq.curdays-tq.skip; other=true; mdelta='+1'; 
			}else{
				day=n-tq.skip; other=false; mdelta='';
			}
			var extra = '';
			if(other){
				extra += ' cal_outside';
			} else {
				if (j==0||j==6) extra+=' cal_weekend2';
				if (day==xc.this_day && xc.cur_month==xc.this_month && xc.cur_year==xc.this_year) extra+=' cal_today';
			}
			if (type=='day' && day==xc.cur_day) extra+=' cal_sel';
			if (type=='week' && i==tq.wkrow) extra+=' cal_sel';
			txt+='<td class="nav w12 h20 cal_ymini'+extra+'" day="'+day+'" mdelta="'+mdelta+'"><a href="#">'+day+'</a></td>';
			n++;
		}
		txt += '</tr>'
	}
	txt+='</table></div>'
	txt+=cal_mini_nav(xc);
	return txt;
}
function cal_mini_months(xc,yr)
{
	var prv_yr=yr-1,nxt_yr=yr+1;
	var txt='';
	txt+='<div style="text-align:center;"><table onmousedown="swcal_nav(event)" style="margin:auto;width:98%;border-collapse:collapse;text-align:center;">';
	for(var i=0;i<3;i++){
		txt+='<tr>';
		for (var j=0;j<4;j++){
			var mth=i*4+j+1;
			var extra='';
			if (mth==xc.cur_month) extra+=' cal_sel';
			if (mth==xc.this_month) extra+=' cal_today';
			txt+='<td class="w25 h25 cal_ymini'+extra+' nav" month="'+mth+'"><a href="#">'+xc.MofY[mth-1]+'</a></td>';
		}
		txt += '</tr>'
	}
	txt+='</table></div>'
	txt+=cal_mini_nav(xc);
	return txt;
}
function cal_mini_years(xc,year)
{
	var txt='';
	txt+='<div style="text-align:center;"><table onmousedown="swcal_nav(event)" style="margin:auto;width:98%;border-collapse:collapse;text-align:center;">';
	var yr=year-4;
	var delta=(xc.this_year-year)%3;
	if(delta==1 || delta==-2) yr++;
	if(delta==2 || delta==-1) yr--;
	for(var i=0;i<3;i++){
		txt+='<tr class="">';
		for (var j=0;j<3;j++){
			var extra='';
			if (yr==xc.this_year) extra+=' cal_today';
			if (yr==xc.cur_year) extra+=' cal_sel';
			txt+='<td class="nav w33 h25 cal_ymini'+extra+'" year="'+yr+'"><a href="#">'+yr+'</a></td>';
			yr++;
		}
		txt += '</tr>'
	}
	txt+='</table></div>'
	txt+=cal_mini_nav(xc);
	return txt;
}

function cal_mini_nav(xc)
{
	var type=xc.cur_view;
	if(!xc.sfn_delta) xc.sfn_delta='swcal_delta';
	var settings={ 
			'day':{	p:[['&lt;&lt;','$$st_cal_week$$',0,0,-7],['&lt;','$$st_cal_day$$',0,0,-1]], 
					n:[['&gt;','$$st_cal_day$$',0,0,+1],['&gt;&gt;','$$st_cal_week$$',0,0,+7]]},
			'week':{p:[['&lt;&lt;','$$st_cal_month$$',0,-1,0],['&lt;','$$st_cal_week$$',0,0,-7]], 
					n:[['&gt;','$$st_cal_week$$',0,0,+7],['&gt;&gt;','$$st_cal_month$$',0,+1,0]]},
			'month':{p:[['&lt;&lt;','$$st_cal_year$$',-1,0,0],['&lt;','$$st_cal_month$$',0,-1,0]], 
					n:[['&gt;','$$st_cal_month$$',0,+1,0],['&gt;&gt;','$$st_cal_year$$',+1,0,0]]},
			'year':{p:[['&lt;','$$st_cal_year$$',-1,0,0]], n:[['&gt;','$$st_cal_year$$',+1,0,0]]}
	}
	var pa=settings[type].p, na=settings[type].n;
	txt='<div class="cal_mini_title"><span style="float:left;margin-left:5px;">'
	for (var i=0;i<pa.length;i++){
		var p=pa[i];
		txt+='<a class="link_blue" href="#" onclick="'+xc.sfn_delta+'(event,this,'+p[2]+','+p[3]+','+p[4]+')" title="Previous '+p[1]+'\n$$st_cal_arrows$$">'+p[0]+'</a> &nbsp;';
	}
	txt+='</span><span style="float:right;margin-right:5px;">';
	for (var i=0;i<na.length;i++){
		var p=na[i];
		txt+='<a class="link_blue" href="#" onclick="'+xc.sfn_delta+'(event,this,'+p[2]+','+p[3]+','+p[4]+')" title="Next '+p[1]+'\n$$st_cal_arrows$$">'+p[0]+'</a> &nbsp;';
	}
	txt+='</span><div style="height:12px;width:1px;"></div></div>';
	return txt;
}

function cal_warntxt()
{
	if(isIE && isVersion<8) return 'Out of date browser detected, <a href="#" onclick="cal_minbrowser()">upgrade browser</a> for best experience';

	if(sw.location_warn.length==0) return '';
	return 'WARNING: Location offset from browser by '+sw.location_warn+' hours, please <a href="#" onclick="manage_calendars(event)">update your location</a>';
}
function cal_tdate(xc,rollover)
{
	var warning='<span id="cal_warntxt" style="font-size:14px;color:#aa0000;display:inline-block;margin-left:20px;">'+cal_warntxt()+'</span>';
	if(xc.cur_view=='year'){
		return xc.cur_year+warning;
	} else if(xc.cur_view=='month'){
		return xc.MofY[xc.cur_month-1]+' '+xc.cur_year+warning;
	}else  if(xc.cur_view=='week'){
		if(rollover){
			return ' '+xc.MofY[xc.cur_month-1]+' - '+xc.MofY[xc.cur_month]+' '+xc.cur_year+warning;
		}else{
			return ' '+xc.MofY[xc.cur_month-1]+' '+xc.cur_year+warning;
		}
	}else{
		return xc.cur_day+' '+xc.MofY[xc.cur_month-1]+' '+xc.cur_year+warning;
	}
}


function cal_frame_dayrange(xc,days)
{
	var tq=cal_tq_init(xc,{'type':'days'});
	if(days==1) tq.lock_week=false;
	var dows=[];
	for (var i=0;i<days+1;i++){
		var d=new Date(xc.cur_year, xc.cur_month-1, xc.cur_day+i-(tq.lock_week?tq.dow:0));
		dows[i]=d.getDay();
	    xc.col_t1s[i]=d.getTime();
		xc.col_slices[i]=48;
		cal_tq_nxtday(xc,tq);
	}
	xc.pix_per_cell=25;
	xc.slot_inset=0;
	xc.sec_per_item=60*30;
	xc.per_slice=2;
	xc.bg_resat=80;xc.br_resat=-40;
	xc.slotting='overlap';

	if(days==1){
		var txt=cal_tdate(xc)+' <div style="margin-top:60px;border-right:2px solid gray;padding-right:10px;"><div style="font-size:16px;">Todays event\'s:</div><div id="daylist_list" class="daylist_list">';
		txt+='</div></div>';
		dge('cal_title').innerHTML=txt;
	}else{
		dge('cal_title').innerHTML='<div style="margin-left:'+xc.table_inset+'px;">'+cal_tdate(xc,tq.rollover)+'</div>';
	}
	xc.need_eight_am=true;

	
	cal_tq_rstday(xc,tq);
	var txt='';
	
	txt+='<div style="position:relative;"><div id="cal_events_hdr"></div>\
	    <div id="cal_header_tmp" class="cal_box" style="width:100%;z-index:3;"></div>\
		<table id="cal_header_table" style="background:white;"><tr>';
	txt+='<td class="" style="width:'+xc.table_inset+'px;vertical-align:top;padding-right:5px;padding-top:16px;text-align:right;white-space:nowrap;"><span class="cal_font">all day</span></td>';
	for (var j=0;j<days;j++){
		var day_txt=(days<=7 ? xc.DofWf[dows[j]] : xc.DofW[dows[j]]);
		txt+='<td id="cal_h'+j+'" class="cs_TH c_Hd cs_bb1 cs_bl1 cs_bt1 cal_font ';
		if(days>1 && (tq.rollover||tq.prev_rollover)) txt+='cal_outside';
		txt+='" style="height:'+(xc.top_height)+'px;white-space:nowrap;"><div>'+(tq.day)+' '+day_txt+'</div></td>';
		cal_tq_nxtday(xc,tq);
	}
	txt+='<td class="cs_bl1" style="width:1px;"></td></tr></table></div>'
	dge('cal_header').innerHTML=txt;

	txt='<div>';
	txt+='<table id="cal_body_table" cellpadding="0px" and cellspacing="0px">';
	xc.columns = days;
	for(var i=0;i<48;i++){
		var hr=Math.floor(i/2);
		var first=(i%2==0);
		txt+='<tr>';
		for (var j=0;j<days+1;j++){
			var tid='id=cal_b_'+i+'_'+(j-1);
			var extra='';
			if(j==0){
				if(first){
					txt+='<td id="cal_hr'+hr+'" class="" style="width:'+xc.table_inset+'px;vertical-align:top;padding-right:5px;text-align:right;"><span class="cal_font">'+format_hr(hr)+'</span></td>';
				}else{
					txt+='<td>&nbsp;</td>';
				}
				continue;
			}
			extra +=' cal_bs';
			if(first){
				switch(i){
					case 0: extra+=' cal_bt'; break;
					case 23:
					case 47: extra+=' cal_bb'; break;
					default: extra+=' cal_bt3a'; break;
				}
			}else{
				extra+=' cal_bt3b';
			}
			var ww=(days==1?100:14);
			
			if(hr<8||hr>20) { 
				extra+=' cal_weekend2';
			} else if(xc.cur_view=='week' && (dows[j-1]==0 || dows[j-1]==6)){
				extra+=' cal_weekend2';
			}
			txt+='<td '+tid+' class="'+extra+'" style="vertical-align:top;padding:4px;"><div class="cHd"><div class="cal_font;" style="width:10px;margin-left:-25px; margin-top:-7px;"></div></div></td>';
		}
		var tid='id=cal_b_'+i+'_'+days;
		txt+='<td '+tid+' class="cal_bs chy" style="width:1px;"></td>';
		txt+='</tr>';
	}
	txt+='</table></div>'
	return txt;
}

function cal_frame_month(xc,mth,yr)
{
	var d, year=xc.cur_year;
	var tq=cal_tq_init(xc,{'type':'month'});

	var today_show = (mth==xc.this_month);
	var doc=xc.this_day+tq.skip-1;
	var today_rc = [Math.floor(doc/7), doc%7];

	xc.columns = 6;
	xc.pix_per_cell=92+10;
	xc.sec_per_item=60*60*24;
	xc.per_slice=1;	
	xc.slot_inset=20;
	xc.bg_resat=80;xc.br_resat=-40;
	xc.slotting='colrec';

	dge('cal_title').innerHTML=cal_tdate(xc);

	var txt='';
	txt+='<div><table id="cal_header_table" style="background:white;">';
	for (var j=0;j<6;j++){
		txt+='<td id="cal_h'+j+'" class="cs_TH cs_bb1" style="padding-left:5px;"><div style="height:14px;"></div></td>';
	}
	txt+='<td class="" style="width:1px;"></td></tr></table></div>'
	dge('cal_header').innerHTML=txt;

	var day=1-tq.skip;
	for(var i=0;i<7;i++){	// not days!
		var d=new Date(year, mth-1, day);
		xc.col_t1s[i]=d.getTime();
		xc.col_slices[i]=7;
		day+=7;
	}

	var txt='<div><table id="cal_body_table" class="_cal_frame" >';
	var n=1, day, other, days=7, complete=false;
	for(var i=0;i<7;i++){
		txt+='<tr>';
		for (var j=0;j<days;j++){
			var extra2='';
			var tid='id=cal_b_'+i+'_'+j;
			if(n<=tq.skip) {
				day=tq.prevdays-(tq.skip-n); other=true;
			}else if(n>tq.skip+tq.curdays){
				day=n-tq.curdays-tq.skip; other=true; 
				if(day==1) extra2=' '+xc.MofY[mth-1+1];
			}else{
				day=n-tq.skip; other=false;
				if(day==1) extra2=' '+xc.MofY[mth-1];
			}
			var today=today_show&&(today_rc[0]==i)&&(today_rc[1]==j);
			var today_prev=today_show&&((today_rc[0]-1)==i)&&(today_rc[1]==j);
			var extra='cal_bs';
			if(today_prev) { 
				extra+=' cal_bt2';	// bit hackish but shows red border on previous cell "abovetoday"
			} else if(today) { 
				extra+=' cal_today2';
			} else{ 
				extra+=' cal_btb';
			}
			if(other){
				extra += ' cal_outside';
			} else {
				if (j==0||j==6) extra+=' cal_weekend2';
			}
			var t1m = xc.col_t1s[i]+j*24*60*60*1000;
			txt+='<td '+tid+' class="'+extra+'" style="width:14.25%;vertical-align:top;padding:3px;position:relative;"><div class="chm"><span class="day_span">'+day+extra2+'</span> <span class="more_span" onclick="cal_mnth_more(event,'+t1m+')">more</span></div></td>';
			n++;
			if(!other && day==tq.curdays) complete=true;
		}
		var tid='id=cal_b_'+i+'_'+days;
		txt+='<td '+tid+' class="cal_bs chy" style="width:1px;"></td>';
		txt+='</tr>';
		if(complete==true) {
			xc.columns=i+1;
			break;
		}
	}
	
	txt+='</table></div>';
	var key=cmd_ctl_as_text();
	txt+='<div class="cal_hint">'+$('$$st_cal_suggestion$$',key,key)+'</div>';
	return txt;
}
function cal_frame_year(xc,year)
{
	var tq=cal_tq_init(xc,{'type':'year'});

	var day=1, last_days=0, mnths=12;
	dge('cal_title').innerHTML=cal_tdate(xc);

	var txt='';
	txt+='<div><table id="cal_header_table" style="background:white;">';
	for (var j=0;j<mnths;j++){
		txt+='<td id="cal_h'+j+'" class="cs_TH c_Hd cs_bb1 cal_font" style="height:14px;padding-left:5px;"><div>'+xc.MofY[j]+'</div></td>';
	}
	txt+='<td class="_cs_bl1" style="width:1px;"></td></tr></table></div>'
	dge('cal_header').innerHTML=txt;

	var txt='<div><table id="cal_body_table">';
	var dows=[];
	xc.columns = mnths;
	for(var i=0;i<mnths+1;i++){
		var d=new Date(year, i, 1);
		dows[i]=d.getDay();
	    xc.col_t1s[i]=d.getTime();
		d=new Date(year, d.getMonth()+1, 0);
	    xc.col_slices[i]=d.getDate();
	}
	xc.pix_per_cell=17;
	xc.sec_per_item=60*60*24;
	xc.per_slice=1;	
	xc.slot_inset=17;
	xc.bg_resat=0;xc.br_resat=0;
	xc.slotting='global';
	
	var days = xc.col_slices;
	for(var i=0;i<31;i++){
		txt+='<tr>';
		var day=i+1;
		for (var j=0;j<mnths;j++){
			var dow=(dows[j]+day-1)%7;
			var tid='id=cal_b_'+i+'_'+j;
			var extra='';
			if(day>days[j]){
				if(day<=days[j-1]) extra='cal_bs';
				txt+='<td class="'+extra+'"></td>';
			}else{
				extra='cal_bs cal_pl2';
				if (dow==0||dow==6) extra+=' cal_weekend';
				extra+=' cal_font';
				switch(i){
					case 1: extra+=' cal_bt'; break;
					case days[j]-1: extra+=' cal_bb'; break;
					default: extra+=' cal_bm'; 
				}
				txt+='<td '+tid+' class="'+extra+'" ><div class="chy"><span>'+day+'</span></div></td>';
			}
		}
		var tid='id=cal_b_'+i+'_'+mnths;
		txt+='<td '+tid+' class="cal_bs chy" style="width:1px;"></td>';
		txt+='</tr>'
	}
	txt+='</table></div>'
	return txt;
}
function cal_frame_list(xc)
{
	dge('cal_title').innerHTML='the date...';
	dge('cal_header').innerHTML='no header';
	return 'text list of events';
}

# *** Build display frames END

function cal_col_find(xc,x)
{
	var i=0;
	while(true){
		var left=xc.cell_left[i];
		if(left==9999) return -1;
		if(x<left) return i-1;
		i++
	}
}

function cal_xy_to_rc_restrict(xc,ev,x,y,opt)	
{
	var col, rec, per_slice;
	if(!opt) opt={};
	xc.pos_bad=false;
	dbg("*** floor="+opt.floor);
	var xfn=(opt.floor?Math.floor:Math.round);
	var yfn=(opt.floor?Math.floor:Math.round);
	per_slice=(opt.per_slice?opt.per_slice:xc.per_slice);
	
	if(xc.horiz){
		col=yfn(y*per_slice/xc.pix_per_cell)/per_slice;									// !!!!!!!!!!!!!!!!!
		rec=xfn((x-10-xc.table_inset)/(xc.cell_left[2]-xc.cell_left[1]));
#		if(opt.xrec && rec==opt.xrec) rec+=1; 
	}else{
		var col_v1=xfn((x-10-xc.table_inset)/xc.col_width);
dbg("** x="+x+" col1="+col_v1+" cw="+xc.col_width)
// This one newer but less reliable??
		var col_v2=cal_col_find(xc,x); 
dbg("** x="+x+" col2="+col_v2)
		// hack to try and make it more reliable
		col=col_v2; //Math.floor((col_v1+col_v2)/2); // (col_v1<=1?col_v1:col_v2);
dbg("** x="+x+" col_final="+col)

		rec=yfn(y*per_slice/xc.pix_per_cell)/per_slice;
	}
	dbg('>>>**B4**<<< col='+col+' rec='+rec);
	var cr=range_limit(0, col, xc.columns-1);
	if(cr!=col) {xc.pos_bad=true; col=cr;}
#	rec=range_limit(0, rec, xc.col_slices[col]-(ev?ev.len:0));
	dbg('>>>**AF**<<< col='+col+' rec='+rec);

	return [col,rec];
}

 		
function cal_apply_min_size(xc,xy1,xy2,rec)
{
	if(xc.horiz){
		if(xy1[0]==xy2[0]){
			var cell=Math.floor(rec);
			s =	xc.cell_left[cell+1]-xc.cell_left[cell];
			xy2[0]=xy1[0]+s;
		}
	}else{
		if(xy1[1]==xy2[1]){
			s = (0.5)*xc.pix_per_cell+1;
			xy2[1]=xy1[1]+s;
		}
	}
	return xy2;
}
function cal_ev_pos(xc,ev)	// QQ.8		//QQQPOS
{
	var xys={};
	if(xc.horiz){
		var cell=Math.floor(ev.rec);						// GOOD more like THIS
		var cell2=Math.floor(ev.rec2);
		xys.x = xc.cell_left[cell] /*ev.rec*xc.pix_per_cell*/;
		xys.y = ev.col*xc.pix_per_cell + xc.slot_inset /*+xc.table_inset*/+(ev.slot-1)*18;
		var cell2b=cell2+1; if(cell2b>xc.col_slices[ev.col]) cell2b--;	// HACK fix??
		xys.s =	xc.cell_left[cell2b]-xc.cell_left[cell];	// QQBAD not ok cross column??
		if(xys.s<10) xys.s=10;
	
		xys.ew=xys.s; xys.eh=17;
	}else{
		var colw = xc.cell_left[ev.col+1] - xc.cell_left[ev.col] - xc.slot_inset;
		var slotw = colw*ev.slot_ratio;
		xys.x = xc.cell_left[ev.col] + xc.slot_inset + (ev.slot-1)*slotw;
		xys.y = ev.rec * xc.pix_per_cell;
		xys.s = (ev.rec2-ev.rec) * xc.pix_per_cell+1;
		if(xys.s<10) xys.s=10;
		xys.eh=xys.s;
		xys.ew = slotw*xc.slot_fill+1;
	}
	
	return xys;
}
function cal_rc_to_xy(xc,ev,rc)		// QQLIM
{
	var col=rc[0], rec=rc[1], xy;	// QQ.8 // QQQPOS
	if(xc.horiz){
		var cell=Math.floor(rec);				
		xy=[xc.cell_left[cell], 
				col*xc.pix_per_cell + xc.slot_inset /*+xc.table_inset+((ev?ev.slot:1)-1)*xc.slot_width*/];	//<<< THIS WAS ACTUAL BAD
	}else{
		var slot_x=0;
		if(ev){
			var colw = xc.cell_left[col+1] - xc.cell_left[col] - xc.slot_inset;
			slot_x = ((ev?ev.slot:1)-1)*ev.slot_ratio*colw;
		}
		xy=[xc.cell_left[col] + xc.slot_inset + slot_x, rec*xc.pix_per_cell];	
	}
	dbg('RC2XY c='+rc[0]+' r='+rc[1]+' x='+xy[0]+' y='+xy[1]);
	return xy;
}

# drag and drop etc callbacks based on dom elements
function cal_validate_posn(oo,x,y)
{
	var o2=cal_hdl_to_event(oo), xc=o2.xc, ev=o2.ev;
	var rc=cal_xy_to_rc_restrict(xc,ev,x,y);
	var xy=cal_rc_to_xy(xc,ev,rc);
	dbg("validate posn *** c=",rc[0]);
	cal_store_coordinates(xc,rc,xy);
#	return xy;
#	// original event now stays in original postition during move and copy moves & resizes aroudn the screen :-)
	if(o2.orig_xy && !o2.real_move){
		var d1=Math.abs(x-o2.orig_xy[0]);
		var d2=Math.abs(y-o2.orig_xy[1]);
		if(d1>5 || d2>5) {
			o2.real_move=true;
		}
	}
	return [ob_l(oo),ob_t(oo)];	
}

function cal_validate_handle(oo,x,y)
{
#	return [x,y]
	var o=cal_hdl_to_body(oo), o2=cal_hdl_to_event(oo), xc=o2.xc, ev=o2.ev;

	var rc=cal_xy_to_rc_restrict(xc,ev,x,y);
	var xy=cal_rc_to_xy(xc,ev,rc);		// TODO: Further range limit the suitably??
	
	cal_store_coordinates(xc,rc,xy);
	var h_xy;
	if(xc.horiz){
		var left=class_contains(oo,'handleL');
		h_xy=[(left?xy[0]:xy[0]-4), xy[1]];
	}else{
		var top=class_contains(oo,'handleT');
		h_xy=[xy[0], (top?xy[1]:xy[1]-4)];
	}
	dbg('*** h='+h_xy+' xy='+xy+' rc='+rc);
	return h_xy;
}	//cal_size_dragend
function cal_create_validate(o,x,y)
{
	var xc=o.xc, rc;
	cal_check_moved(o,[x,y]);
	var rc=cal_xy_to_rc_restrict(xc,U,x,y,{per_slice:1,floor:!o.new_event})
	var xy=cal_rc_to_xy(xc,U,rc);
	dbg('*********** VALIDATE validate rc '+rc)
	cal_store_coordinates(xc,rc,xy);
	return xy;
}

# *** Util functions
function cal_check_moved(o,xyraw)
{
	if(o.didmove_ok) return;
	if(!o.didmove_xyraw){
		o.didmove_xyraw=xyraw;
		return;
	}
	if(Math.abs(o.didmove_xyraw[0]-xyraw[0])<2 && Math.abs(o.didmove_xyraw[1]-xyraw[1])<2) return;
	o.didmove_ok=true;
}
function cal_time_to_rc(xc,tt)
{
	var cols=xc.columns;
	var ret;
	for(var i=0;i<cols;i++){
#		dbg('>>>>> xc.col_t1s='+ms2dv(xc.col_t1s[i])+' tt='+ms2dv(tt)+' xc.col_t1s='+ms2dv(xc.col_t1s[i+1]))
		var qqq='>>>>> xc.col_t1s='+ms2dv(xc.col_t1s[i])+' tt='+ms2dv(tt)+' xc.col_t1s='+ms2dv(xc.col_t1s[i+1]);
		var az = tt - xc.col_t1s[i];
		
		if(xc.col_t1s[i]<=tt && tt<xc.col_t1s[i+1]) {
			ret=[i, (tt-xc.col_t1s[i]) / 1000  / xc.sec_per_item];
			break;
		}
	}
	
	if(!ret) ret=[(tt>xc.col_t1s[cols]?cols:-1),(xc.cur_view=='week'||xc.cur_view=='day'?8*2:-1)];
	return ret;
}

function cal_evt_pos_limits(xc,ev,rc1,rc2)
{
	if(xc.cur_view=='year') {
		if(!ev.subday) return;
		rc1[1] = Math.floor(rc1[1])+0.25;
		rc2[1] = Math.floor(rc2[1])+0.3;
	}else if (xc.horiz){
		if(ev.allday){
			rc2[1]=rc2[1]-1;
		}
	}
}
function cal_evt_pts(xc,ev)
{
	var rc1=cal_time_to_rc(xc,ev.t1);
	var rc2=cal_time_to_rc(xc,ev.t2);

	cal_evt_pos_limits(xc,ev,rc1,rc2);

	ev.col=rc1[0]; ev.rec=rc1[1];
	ev.col2=rc2[0]; ev.rec2=rc2[1];
#debugger;	// QQMULTI 1

	ev.multi=(ev.col!=ev.col2);
	ev.len=(ev.rec2-ev.rec)+(ev.col2-ev.col)*30;	// QQHACK
}

function cal_rc_to_time(xc,rc)
{
	return xc.col_t1s[rc[0]]+rc[1]*xc.sec_per_item*1000;
}

# *** Other init code
function uid_to_el(uid)
{
	var el=dge('evt_'+uid);
	if(!el) return null;
	var el2=el.firstChild;
	if(!el2) return null;
	if(class_contains(el2,'chV')) el2=el.childNodes[2];
	if(!el2) return null;
	return el2;
}
function cal_dom_assign(xc,evs,loc)
{
	if(!loc) loc=xc.root;
	var el=dge(loc);
	if(!el) return;
	el.innerHTML=cal_events_html(xc,evs);
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
#   debugger;	// QQMULTI 3
		var el=uid_to_el(ev.uid);
		if(!el) continue;
		var c=1;
		var col2=ev.col2;
		for(var j=ev.col; j<=col2; j++,c++){
			var el=dge('evt_'+(ev.uid)+'_c'+(c));
			if(!el) continue;
			var o2=cal_evt_node(el);
			o2.ev=ev; o2.xc=xc;
			ev.els.push(el);
		}
	}
}
function cal_handles(xc,ev,c)
{
	var txt='';
	if(ev.nohandles || ev.readonly || ev.locked) return txt;
	if(mtime()-ev.t2>msec_thisweek()) return txt;

	if(xc.horiz){	// QQHORIZ
		txt+='<div id="h1_'+(ev.uid)+'" class="chH handleL"></div>\
			  <div id="h2_'+(ev.uid)+'" class="chH handleR"></div>';
	}else{
		txt+='<div id="h1_'+(ev.uid)+'" class="chV handleT"></div>\
		 	  <div id="h2_'+(ev.uid)+'" class="chV handleB"></div>';
	} 	

	return txt;
}
function cal_body_time(ev,pending)
{
	var t1,t2;
	if(pending){
		t1=ev.nt1; t2=ev.nt2;
	}else{
		t1=ev.t1; t2=ev.t2;
	}
	var type=ev.bodytime;
	if(type==2){
		var duration = Math.abs(t2-t1) / (1000*60)
		return '<span class="body_time">'+showtime_short(t1)+' &ndash; '+showtime_short(t2)+'</span>' + (duration>35 ? '<br>' : ' ');
	}else if(type==1 && !ev.allday){
		return '<span class="body_time">'+showtime_short(t1)+'</span> ';
	}else{
		return '';
	}
}
function cal_events_html(xc,evs)
{
	var handles, txt='', body='', show_body, bodytime=0
	
	if(xc.cur_view=='week' || xc.cur_view=='day'){
		show_body=true;
		if(xc.horiz){
			bodytime=1;
		}else{
			bodytime=2;
		}
		
	}else if(xc.cur_view=='month'){
		show_body=true;
		bodytime=1;
	}else if(xc.cur_view=='year'){
		show_body=false;
	}
	for(var i=0;i<evs.length;i++){	
		var ev=evs[i];
		ev.bodytime=bodytime;
		if(ev.overfull) continue;
		body=cal_body_time(ev);
		if(show_body) body+=ev.desc;
		txt+='<div id="evt_'+(ev.uid)+'" class="cal_event" _style="top:0px;left:0px;right:0px;bottom:0px;position:absolute;">';
		var c=1;
		var col2=ev.col2;
		if(col2>xc.columns-1) col2=xc.columns-1;	
		if(col2>ev.col && ev.rec2==-1) col2--;
		for(var j=ev.col; j<=col2; j++,c++){
			txt+='<div id="evt_'+(ev.uid)+'_c'+c+'" class="cal_block" style="position:absolute; overflow:hidden;" >\
				<div style="position:absolute;background-color:'+resaturate(ev.clr,xc.bg_resat)+';border: 1px solid '+resaturate(ev.clr,xc.br_resat)+';border-radius:4px;top:0px;left:0px;right:0;bottom:0;opacity:0.5;z-index:10;"></div>\
				<div style="position:absolute;padding:3px 1px 1px 4px;_height:10px;color:black;font-size:11px;font-weight:bold; overflow:hidden; text-overflow:ellipsis;z-index:11;opacity:0.8;">'+body+'</div> \
			</div>\
			';
		}
		txt+=cal_handles(xc,ev,j);
		txt+='</div>';
	}
	return txt;
}
function cal_new_block(xc, o, ev, b4)
{
	var el;
	var txt='\
			<div style="position:absolute;background-color:'+resaturate(ev.clr,xc.bg_resat)+';border: 1px solid '+resaturate(ev.clr,xc.br_resat)+';border-radius:4px;top:0px;left:0px;right:0;bottom:0;opacity:0.5;z-index:10;"></div>\
			<div style="padding:3px 1px 1px 4px;_height:10px;color:black;font-size:11px;font-weight:bold; overflow:hidden; text-overflow:ellipsis;z-index:11;opacity:0.8;">'
	if(xc.cur_view!='year') 
		txt+=ev.desc;
	txt+='</div>';
	if(b4){
		el=new_anchor_b4('div',txt,b4);
	}else{
		el=new_anchor('div',txt,o);
	}

	el.className="cal_block";
	el.style.position="absolute";
	el.style.overflow="hidden"; 
	return el;
}


function cal_temp_event(t1,t2,desc,color,slot,max,allday)
{
	return [{ cal:2, t1:t1,t1s:U,  t2:t2,t2s:U, desc:desc, cal_id:'', uid:'new', els:[], clr:color, slot:slot, slot_ratio:1/max, allday:allday}];
}


# *** Core calendaring display logic 

# make sure that the event are in an array of linear time order
#  (only used when moving / creating events)
function cal_reshuffle(xc,ev)
{
	if(!ev) return;
	var wanted=0,current=0;
	var evs=xc.evs;
	for(var i=0;i<evs.length;i++){
		var evi=evs[i];
		if(evi==ev) current=i;
		if(evi.t1<ev.t1) wanted=i+1;
	}
	if (wanted<current){
		var frag=evs.splice(current,1);
		evs.splice(wanted,0,frag[0]);
	}else if (wanted>current){
		var frag=evs.splice(current,1);
		evs.splice(wanted-1,0,frag[0]);
	}
}
function cal_vfy_year_events(xc,evs)
{
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
		if(ev.subday) {
			ev.nohandles=true;
			ev.nomove=true;
		}
	}	
}

# Make sure the begin / end row&columns & length are correctly assigned
function cal_layout_sizes(xc,evs)
{
	for(var i=0;i<evs.length;i++){
		cal_evt_pts(xc,evs[i]);
	}
}
# overlapping event slot layout engine 
function cal_layout_slots(xc,evs)
{
	var slots=cal_slots_new();
	cal_slots_prepare(xc,evs,slots);
	cal_slots_assign(xc,evs,slots);
	cal_slots_widths(xc,evs,slots);
}
function cal_slots_prepare(xc,evs,ss)
{
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
		ev.slots=1;
		ev.slot_ratio=1;
	}
	if(xc.cur_view=='year'){
		for(var i=0;i<evs.length;i++){
			var ev=evs[i];
			if(!ev.subday) continue;
			ev.slot=1;
			ev.slotted=true;
		}
		ss.first=ss.max=1;
	}else if(xc.slotting=='colrec'){
#		for(var i=0;i<evs.length;i++){
#			ev.ft1=ev.t1;
#			ev.ft2=ev.t2;
#		}		
	}
}
function cal_slots_assign(xc,evs,ss)
{
	if(xc.slotting=='global'||xc.slotting=='overlap'){
#		Normal overlap based slotting	
		for(var i=0;i<evs.length;i++){
			var ev=evs[i],busy=0;
			if(ev.slotted) continue;
# 			// reset any slots no longer occupied
			for(var j=ss.first;j<ss.max;j++){
				var evo=ss.slots[j];
				if(!evo) continue;
				if(evo.t2<=ev.t1){
					ss.slots[j]=U;
					continue;
				}
				busy++;
			}
#			// Actually assign next slot
			for(var j=ss.first;j<ss.max+1;j++){
				if(!ss.slots[j]) {
					ev.slot=j+1;
					ev.other=busy;
					ss.slots[j]=ev;
					if(j>=ss.max) ss.max=j+1;
					break;
				}
			}
		}			
	}else if(xc.slotting=='colrec'){
#		Day based slotting	
		for(var i=0;i<evs.length;i++){
			var ev=evs[i],busy=0;
# 			// reset any slots no longer occupied
			var ev_colrec=Math.floor(ev.col)+'_'+Math.floor(ev.rec);
			if(ss.cur_colrec!=ev_colrec){
				for(var j=ss.first;j<ss.max;j++){
					var evo=ss.slots[j];
					if(!evo) continue;
					if(evo.t2<ev.t1){
						ss.slots[j]=U;
						continue;
					}
					busy++;
				}
			}
#			// Actually assign next slot
			for(var j=ss.first;j<ss.max+1;j++){
				if(!ss.slots[j]) {
					ev.slot=j+1;
					ev.other=busy;
					ss.slots[j]=ev;
					if(j>=ss.max) ss.max=j+1;
					ss.cur_colrec=ev_colrec;
					break;
				}
			}
		}
	}
}
function cal_slots_widths(xc,evs,ss)
{
	var i_start=i_end=0, max=0;
	if(xc.slotting=='global'){	
		var max=0;
		for(var i=0;i<evs.length;i++){
			var ev=evs[i];
			if(ev.slot>max) max=ev.slot;
		}
		if(xc.cur_view=='year' && max<6) max=6;
		for(var i=0;i<evs.length;i++){
			var ev=evs[i];
			ev.slots=max; ev.slot_ratio=1/max;
		}
		xc.slot_max=max;
	}else if (xc.slotting=='overlap'){
		for(i=0;i<evs.length;i++){
			var ev=evs[i];
			if(ev.other==0&&i_start<i-1){
				// walk others setting slots & ratio
				for(j=i_start;j<i;j++){
					var ev2=evs[j];
					ev2.slots=max; ev2.slot_ratio=1/max;
				}
				max=0;
			}
			if(ev.slot>max) max=ev.slot;
			if(ev.other>0) continue;
			i_start=i;			
		}
		for(j=i_start;j<evs.length;j++){
			var ev2=evs[j];
			ev2.slots=max; ev2.slot_ratio=1/max;
		}
		xc.slot_max=1;
	}else if (xc.slotting=='colrec'){
		for(j=0;j<evs.length;j++){
			var ev=evs[j];
			if(xc.cur_view=='day' || xc.cur_view=='week'){
				if(ev.slot>2) {
					xc.extra_allday=ev.slot-2;
				}
			}else{
				if(ev.slot>4) {
					ev.overfull=true;
					xel=dge('cal_b_'+Math.floor(ev.col)+'_'+Math.floor(ev.rec));
					if(xel!=U){
						class_add(xel,'overfull');
					}
				}
			}
		}
		xc.slot_max=1;
	}

	if(xc.cur_view=='year'){
		xc.slot_fill=0.7;
	}else{
		xc.slot_fill=1.0;
	}
}

function cal_xy_position_event(xc,el,xys)
{
	el.style.left=px(xys.x);
	el.style.top=px(xys.y);
}
function cal_sz_position_event(xc,el,xys)
{
	el.style.width=px(xys.ew);
	el.style.height=px(xys.eh);
}
function cal_sz_handles_event(xc,el,xys,ht)
{
	if(xc.horiz){ 
		if(ht==1||ht==3){
			var hL=evt_to_handle(el,'L');
			if(hL) { hL.style.left=px(xys.x); hL.style.top=px(xys.y); hL.style.height=px(xys.eh); }
		}
		if(ht==2||ht==3){
			var hR=evt_to_handle(el,'R')
			if(hR) { hR.style.left=px(xys.x+xys.ew-4); hR.style.top=px(xys.y); hR.style.height=px(xys.eh);}
		}
	}else{
		if(ht==1||ht==3){
			var hT=evt_to_handle(el,'T');
			if(hT) { hT.style.left=px(xys.x); hT.style.top=px(xys.y); hT.style.width=px(xys.ew); }
		}
		if(ht==2||ht==3){
			var hB=evt_to_handle(el,'B');
			if(hB) { hB.style.left=px(xys.x); hB.style.top=px(xys.y+xys.eh-4); hB.style.width=px(xys.ew); }
		}
	}		
}



# Update event positioning
function cal_update_positions(xc,evs)	// QQUPDATE
{
	var ew,eh,es;
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
		var el2=dge('evt_'+ev.uid), ht;
		// QQMULTI 4 		QQHORIZ
		var c1=ev.col, c2=ev.col2, r1=ev.rec, r2=ev.rec2;
		var c=1;
		for(var j=c1; j<=c2; j++,c++){
			var el=dge('evt_'+ev.uid+'_c'+c);
			if(!el) continue;
			if(ev.overfull) {
				class_add(el,'hidden');
			}else{
				class_remove(el,'hidden');
			}
			
			ev.col=ev.col2=j;
			if (j==c1 && j==c2){
				ev.rec=r1; ev.rec2=r2; ht=3;
			}else if (j==c1) { 
				ev.rec=r1; ev.rec2=xc.col_slices[j]; ht=1;
			}else if (j==c2) {
				ev.rec=0; ev.rec2=r2; ht=2;
			}else{ 
				ev.rec=0; ev.rec2=xc.col_slices[j]; ht=0;
			}
			var xys=cal_ev_pos(xc,ev);
			if(j>ev.col) j+=100;
			cal_xy_position_event(xc,el,xys);
			cal_sz_position_event(xc,el,xys);
			cal_sz_handles_event(xc,el2,xys,ht);
		}
		ev.col=c1, ev.col2=c2, ev.rec=r1, ev.rec2=r2;

	}	
}
function evt_to_handle(o,type)
{
	for(var i=0;i<o.childNodes.length;i++){
		var el=o.childNodes[i];
		if(!el || !el.className) continue;
		if(class_contains(el,'handle'+type)) return el;
	}
	return U;
}
function cal_hdl_to_body(o)
{
	return o.parentNode;
}
function cal_hdl_to_event(o)
{
	return ancestor_by_class(o,'cal_event');
}
function cal_evt_node(o)
{
	return ancestor_by_class(o,'cal_event');
}
function cal_update_table_top(xc,h,w)
{
	var xh=0;
	var el=dge('cal_header_table');
	if(!el) return;
	if((xc.cur_view=='day' || xc.cur_view=='week') && xc.sub) {
		xh=xc.sub.extra_allday*20+5;
		el.style.height=px(50+xh);
		xc.resize_by=xh;
	}
	var os=dge("cal_prescroll_space").style;
	os.height=px(xc.hsize+xc.top_height+xh);
	os.marginLeft=px(xc.lmargin);
	var y=xc.hsize;
	var os=dge("cal_header").style;
	os.marginTop=px(xc.hsize);
	var os=dge("cal_shadow").style;
	os.marginTop=px(xc.hsize+xc.top_height+1);
}
function cal_update_table_top2(xc,h,w)
{
	var el=dge('cal_header_table');
	if(!el) return;
	var os=dge("cal_header").style;
	var ss=dge('cal_shadow').style;
	if(xc.cur_view=='day'){
		var el2=dge('cal_details_scrolled');
		var size_left=parseInt(el2.style.marginLeft)-20;
		os.marginLeft=px(size_left);
		ss.marginLeft=px(size_left);
		el3=dge('cal_header_table');
		el3.style.width=px(el2.offsetWidth-50);
	}else{
		os.marginLeft=px(0);
		ss.marginLeft=px(0);
	}
}

function cal_update_table(xc,h,w)		// cal_resize
{
	var o=dge("cal_body_table");
	if(!o) return;
	o.style.width=px(xc.frame_width-(xc.cur_view=='day'?400:0));
#	class_update(dge("cal_width"),'cal_scroller',true||o.style.height>h);
	
	o=dge("cal_details_scrolled");
	os=o.style;
	os.height=px(h-xc.hsize-xc.top_height-xc.resize_by);
	
	var eight_am=0;
	var oo=dge("cal_hr8");
	if(oo) eight_am=oo.offsetTop

	if(h-xc.hsize<xc.pix_per_cell*32+10 || (xc.cur_view=='day'||xc.cur_view=='week')){
		os.overflowY='scroll';
	}else{
		o.scrollTop=0;
		os.overflowY='hidden';
	}
	if(xc.need_eight_am && eight_am>0){
		o.scrollTop=eight_am-15;
		xc.need_eight_am=false
	}
	cal_scroll();
	
	os.overflowX='hidden';
	if(xc.cur_view=='day'){
		os.marginLeft=px(400);
	}else{
		os.marginLeft=px(xc.lmargin);
	}
	o=dge("cal_daylist");
	if(o){
		o.style.height=px(h-o.style.top-140);
	}
	var ww=(w-10-2-10-xc.table_inset-10)/xc.columns;	// QQQW
	var ncell=xc.horiz?xc.col_slices[0]:xc.columns;
	for(var i=0;i<ncell+1;i++){
		el=dge('cal_b_0_'+i)
		xc.cell_left[i]=el.offsetLeft;
		if(i==xc.columns) continue;
		if(el){
#			dbg('CAL '+i+' B='+ww);
			el.width=ww;
		}
		var el=dge('cal_h'+i)
		if(el){
#			dbg('CAL '+i+' H='+ww);
			el.width=ww;
		}
#		dbg("WIDTH="+ww)
	}
	xc.cell_left[ncell+1]=9999;
	el=dge('cal_header_table');
	if(el){
		el.style.width=px(w);
		dge('cal_shadow').style.width=px(w);
	}
}

function cal_update_redline(xc)
{
	var el=dge('cal_redline');
	if(!el || !xc.col_width) return;
	var show=false;
	if(xc.cur_view=='year'){
		show=(xc.cur_year==xc.this_year);
	} else if (xc.cur_view=='week'){
		show=(/*xc.cur_day==xc.this_day &&*/ xc.cur_month==xc.this_month && xc.cur_year==xc.this_year);
	}
	show_hide(el,show);
	if(!show) return;
	
	var xx=new Date();
	var rc=cal_time_to_rc(xc,xx.getTime());
	var clL=cal_cl(rc[0]), clR=cal_cl(rc[0]+1);
	el.style.width=px(clR-clL-1);
	el.style.left=px(clL+1);
	el.style.top=px((rc[1])*xc.pix_per_cell);
	el.style.height=px(1);
	el.title='Now ';
}

function cal_event_dragged(xc,ev,moved)
{
#	var t1s=ms2dv(ev.t1);
#	var t2s=ms2dv(ev.t2);
#	alert(t1s+' '+t2s); return;
	
	if(!moved) return;
	
	cal_query_repeat(ev,function(n){	
		cal_reload_prepare(ev);
		cal_event_savetimes(xc,ev);
		cal_event_updated(xc,ev);
	},true);
}
function cal_event_updated(xc,ev)
{
	cal_reshuffle(xc,ev);
	cal_update_details(xc,ev);
	cal_layout_sizes(xc,xc.evs);
	cal_layout_slots(xc,xc.evs);
	cal_update_positions(xc,xc.evs);
	
	cal_update_lists(xc,xc.evs);
}

function cal_update_lists(xc,evs)
{
	if(xc.cur_view=='day'){
		dge("daylist_list").innerHTML=cal_event_listing(evs);
	}
}
function cal_event_listing(evs)
{
	var txt='<table style="width:340px;border-collapse:collapse;">';
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
		var timing=showtime(ev.t1)+' - '+showtime(ev.t2);
		txt+='<tr><td class="evt_list_item" style="width:10%;white-space:nowrap;" ><span style="color:gray;" >'+timing+'</span></td>\
				  <td  class="evt_list_item">'+ev.desc+'</td></tr>';
	}
	if(evs.length==0){
		txt+='<tr><td class="evt_list_empty" > Nothing scheduled</td></tr>';
	}
	txt+='</table>';
	return txt;
}
function showtime(t)
{
	date = new Date(t);
	var hh = date.getHours();
	var xm="am";
	var h = hh;
	if (h >= 12) {
		h = hh-12;
		xm = "pm";
	}
	if (h == 0) {
		h = 12;
	}
	var m = date.getMinutes();
	if(m<10) m="0"+m;
	return h+':'+m+' '+xm;
}
function showtime_short(t)
{
	date = new Date(t);
	var hh = date.getHours();
	var xm="";
	var h = hh;
	if (h >= 12) {
		h = hh-12;
		xm = "p";
	}
	if (h == 0) {
		h = 12;
	}
	var m = date.getMinutes();
	if(m<10) m="0"+m;
	if(m=='00'){
		txt=h+xm;
	}else{
		txt=h+':'+m+xm;
	}
	return txt;
}
# *** Dynamic behaviour

function cal_events_dnd(xc,evs)
{
	for(var i=0;i<evs.length;i++){
		var ev=evs[i];
		for(var k=0;k<ev.els.length;k++){
			var el=ev.els[k];
			if(!el) continue;
			cal_evt_node(el).xc=xc;
			Drag.init(el, el);
			Drag.validate(el,cal_validate_posn,true);
			Drag.cb(el,cal_move_dragstart,cal_move_drag,cal_move_dragend);
			if(ev.nohandles || ev.readonly || ev.locked) continue;
			var handles=(xc.horiz?['L','R']:['T','B']);
			for(var j=0;j<handles.length;j++){
				var el2=evt_to_handle(el.parentNode,handles[j]);
				if(!el2) continue;
				el2.is_handle=true;
				Drag.init(el2,el2);
				Drag.validate(el2,cal_validate_handle,true);
				Drag.cb(el2,cal_size_dragstart,cal_size_drag,cal_size_dragend);
			}
		}
	}
	var el=dge(xc.region);
	if(!el.drag_init){
		el.xc=xc;
		Region.init(el);
		Region.validate(el,cal_create_validate,true);
		Region.cb(el,cal_create_dragstart,cal_create_drag,cal_create_dragend);
	}
}

function cal_changeable(xc,ev,warn)
{
	if(!ev) return false;
	if(ev.readonly) {
		if(warn) set_status('Read only calendar event cannot be modified','info');
		return false;
	}
	if(ev.locked) {
		if(warn) set_status('Event is locked, doubleclick and unlock to modify','info');
		return false;
	}
	if(cal_notsafe(ev)){
	
		if(warn) set_status('Older past events locked against accidental drag & drop editing (enable in settings, or double click instead)','info');	
		return false;
	}
	if(ev.nomove){
		if(warn) set_status('Edit short events by double clicking or in month / week / day views','info');
		return false;
	}
	return true;
}
function cal_move_dragstart(nx,ny)	// prereq: cal_validate_posn
{
	var oo=Drag.obj, o2=cal_hdl_to_event(oo), xc=o2.xc;
#	if(!cal_changeable(xc,o2.ev)) return;
	if(class_contains(oo,'handleT') || class_contains(oo,'handleL')){
		o2.ott=o2.ev.t1; o2.otx=o2.ev.t2;
	}else{
		o2.ott=o2.ev.t2; o2.otx=o2.ev.t1;
	}
	o2.real_move=false;
	o2.move_start=mtime();
	calhint_enable(false);
}
function cal_move_drag(nx,ny)
{
	var oo=Drag.obj, o2=cal_hdl_to_event(oo), ev=o2.ev, xc=o2.xc, rc=xc.validate_rc, xy=xc.validate_xy, co2=oo.tmp;
	if(any_modifier(event)) return;
	var tt=cal_rc_to_time(xc,rc);
	dbg('drag col='+rc[0]+' tt='+tt+' tcd='+ms2dv(tt));
	if(!co2){
		if(mtime()-o2.move_start<100) return;
		if(!cal_changeable(xc,ev,true)) return;
		cal_event_each_block(o2,function(el){el.style.opacity=0.6;});
		co2=cal_create_one(xc,rc,xy,{src_ev:ev});							// QQCREATE
		dbg('EV CHANGE START FIRST tt=',ms2dv(ev.t1))
		event_change_start(co2,o2,ev.t1);		
		oo.tmp=co2;
		o2.orig_xy=xy;
	}
	var td=tt-co2.ott;
	dbg('drag ott='+ms2dv(co2.ott)+' t1='+ms2dv(co2.ot1+td)+' t2='+ms2dv(co2.ot2+td));
	cal_update_el_tt(xc,co2,co2.ot1+td,co2.ot2+td);
}
function cal_move_dragend(nx,ny)	// use already updated object rc location and appply to both timeslots
{
	var oo=Drag.obj, o2=cal_hdl_to_event(oo), ev=o2.ev, xc=o2.xc, rc=xc.validate_rc;
	if(any_modifier(event)){
		var tt=cal_rc_to_time(xc,rc);
		cal_click_set_day(xc,tt,true);
		return;
	}
	if(!oo.tmp || !o2.nt1 || !o2.nt2) return; 
	if(!cal_changeable(xc,o2.ev)) return;
	var moved=(ev.t1!=o2.nt1 || ev.t2!=o2.nt2);
	ev.t1=o2.nt1; ev.t2=o2.nt2;	
	cal_event_dragged(xc,ev,moved);
	cal_event_each_block(o2,function(el){
		el.style.zIndex=1;
		el.style.opacity=1.0;//0.75;
	});
	remove_node(oo.tmp); oo.tmp=U;
	calhint_enable(true);
	if(!moved && !o2.real_move){
		calhint_evel=o2;
		cal_showpopover();
	}
}

function cal_size_dragstart(nx,ny)	// prereq: cal_validate_handle
{
	var oo=Drag.obj, o=cal_hdl_to_body(oo), o2=cal_hdl_to_event(oo), xc=o2.xc;
	if(class_contains(oo,'handleT') || class_contains(oo,'handleL')){
		o2.ott=o2.ev.t1; o2.otx=o2.ev.t2;
	}else{
		o2.ott=o2.ev.t2; o2.otx=o2.ev.t1;
	}
	calhint_enable(false);
}
function cal_size_drag(nx,ny)
{
	var oo=Drag.obj, o2=cal_hdl_to_event(oo), xc=o2.xc, rc=xc.validate_rc;
	var tt=cal_rc_to_time(xc,rc);
	cal_update_el_tt(xc,o2,tt,o2.otx);
}
function cal_size_dragend(nx,ny)	
{
	var oo=Drag.obj, o=cal_hdl_to_body(oo), o2=cal_hdl_to_event(oo), ev=o2.ev, xc=o2.xc, rc=xc.validate_rc;
	ev.t1=o2.nt1; ev.t2=o2.nt2;
	cal_event_dragged(xc,ev,true);
	calhint_enable(true);
}

function cal_create_dragstart(nx,ny)
{
	var o=Region.obj, o2=o.new_event, xc=o.xc;
	o.didmove_xyraw=o.didmove_ok=U;
	if(xc.cur_view=='month') return;
	dbg('*********** RESET validate rc ')
	xc.validate_rc=o.drag_invalid=U;
	o.first_mousemove=true;
	calhint_enable(false);
}
function cal_create_drag(nx,ny)
{
	var o=Region.obj, o2=o.new_event, xc=o.xc, rc=xc.validate_rc, xy=xc.validate_xy;
	var tt=cal_rc_to_time(xc,rc);
	
	if(o.first_mousemove && xc.pos_bad) {
		o.drag_invalid=true; o.first_mousemove=false;
	}
	if(o.drag_invalid || any_modifier(event)) return;	
	if(xc.cur_view=='month') return;
	if(!o.didmove_ok) return;
	
	if(!o2){ 
		if(rc[0]<0) { 
			o.drag_invalid=true;
			return;
		} 
		o2=cal_create_one(xc,rc,xy);			// QQCREATE
		event_change_start(o2,U,tt);		
		o.new_event=o2;
	}
	cal_update_el_tt(xc,o2,tt,o2.ott);
}
function cal_create_dragend(nx,ny)
{
	var o=Region.obj, o2=o.new_event, xc=o.xc, rc=xc.validate_rc;
	if(!o.didmove_ok && rc){
		var tt=cal_rc_to_time(xc,rc);
		cal_click_set_day(xc,tt);
		return;
	}
	if(o.drag_invalid || !rc || !o2) {
		o.drag_invalid=U;
		return;
	}
	if(xc.cur_view=='month') return;
	var ev=o2.ev;
	dbg("event cal_end c="+rc[0]+" r="+rc[1])
	o.new_event=U;
	ev.t1=o2.nt1; ev.t2=o2.nt2;	
	
	cal_event_t2ts(ev);
	
	cal_new_event(o2);
	calhint_enable(true);
}

#// called to create entry for drag new creation
#	- specified cal colour
#	- std min size
#// called to create copy displayed during drag
#	- color same as old
#	- size same as old
# Create temp event + matchign ui compoments
function cal_create_one(xc,rc,xy,opt)	// QQCREATE
{
	if(!opt) opt={};
	if(opt.src_ev){
		opt.cal=opt.src_ev.cal_id;	
	} else if (!opt.cal){
		opt.cal=swcal_creation_cal();
	}
	var t1,t2,desc,clr,cal,cal_id;
# 	configure new event
	if(!clr&&opt.clr) clr=opt.clr;
	if(!clr&&opt.cal) clr=sw.cals[opt.cal].clr;
	if(!clr&&opt.src_ev&&opt.src_ev.cal_id) clr=sw.cals[opt.src_ev.cal_id].clr;
	if(!clr) clr='#F75D59';

	if(opt.src_ev){
		var evo=opt.src_ev;
		t1=evo.t1; 
		t2=evo.t2;
		desc=evo.desc;
	}else{
		t1=cal_rc_to_time(xc,rc);	
		if(xc.cur_view=='month') t1+=9*60*60*1000;
		if(xc.cur_view=='year') {
#			// all day event
			t2=t1+24*60*60*1000;
		}else{
#			// one hour event by default
			t2=t1+60*60*1000;
		}
		desc='$$st_cal_new_event$$';
	}
	var allday=(xc.cur_view=='year');
	var slot=(xc.cur_view=='year'&&evo ? evo.slot : 1);
	var evs2=cal_temp_event(t1,t2,desc,clr,slot,xc.slot_max,allday);
	cal_layout_sizes(xc,evs2);
#	cal_layout_slots(xc,evs2);		// event horiz layout
	cal_dom_assign(xc,evs2,(xc.is_sub?'cal_header_tmp':'cal_events_tmp'));
	var o=dge('evt_new');
	cal_events_dnd(xc,evs2);
	cal_update_positions(xc,evs2);
	
	o.rc1=rc; o.xy1=xy;
	var new_ev=evs2[0]
	o.evs2=new_ev;
	new_ev.cal_id=opt.cal;
		
	o.newly_created=true;
	
	return o;
}




function cal_store_coordinates(xc,rc,xy)
{
	dbg('*********** STORE validate rc '+rc)
	xc.validate_rc=rc;
	xc.validate_xy=xy;
}
function ob_l(o)
{
	return parseInt(o.style.left);
}
function ob_t(o)
{
	return parseInt(o.style.top);
}
function ob_h(o)
{
	return parseInt(o.style.height);
}
function ob_w(o)
{
	return parseInt(o.style.width);
}
function cal_now()
{
	return new Date();
}
function cal_now_ms()
{
	return new Date().getTime();
}
function cal_setwhen(xc,y,m,d)
{
	cal_reset_events(xc);
	if(y!=U) xc.cur_year = y;
	if(m!=U) xc.cur_month = m;
	if(d!=U) xc.cur_day = d;
}
function cal_setview(xc,type)
{
	cal_reset_events(xc);
	xc.cur_view = type;
	xc.horiz = (type=='month');
}
function cal_settoday(xc)
{
	var d=new Date();
	xc.cur_year = d.getFullYear();
	xc.cur_month = d.getMonth()+1;
	xc.cur_day = d.getDate();

	xc.this_year = xc.cur_year;
	xc.this_month = xc.cur_month;
	xc.this_day = xc.cur_day;	
}
function cal_setcurday(xc,t)
{
	var d=new Date(t);
	xc.cur_year = d.getFullYear();
	xc.cur_month = d.getMonth()+1;
	xc.cur_day = d.getDate();
}
function cal_curdelta(xc,dy,dm,dd)
{
	var d=new Date(xc.cur_year+dy, xc.cur_month-1+dm, xc.cur_day+dd);
	xc.cur_year = d.getFullYear();
	xc.cur_month = d.getMonth()+1;
	xc.cur_day = d.getDate();
}
function cal_thisdelta(xc,dy,dm,dd)
{
	var d=new Date(xc.this_year+dy, xc.this_month-1+dm, xc.this_day+dd);
	xc.cur_year = d.getFullYear();
	xc.cur_month = d.getMonth()+1;
	xc.cur_day = d.getDate();
}
function cal_cur_offset(xc)
{
	var c=new Date(xc.cur_year, xc.cur_month-1, xc.cur_day);
	var d=new Date(xc.this_year, xc.this_month-1, xc.this_day);
	var delta=Math.round((c-d)/(24*60*60*1000));
	return delta;
}

function cal_tq_init(xc,opt)
{
	var tq={};
	tq.curdays=new Date(xc.cur_year, xc.cur_month, 0).getDate();
	tq.prevdays=(new Date(xc.cur_year, xc.cur_month-1, 0)).getDate();
	tq.skip=new Date(xc.cur_year, xc.cur_month-1, 1).getDay();
	tq.wkrow=Math.floor((xc.cur_day+tq.skip)/7);
	tq.dow=new Date(xc.cur_year, xc.cur_month-1, xc.cur_day).getDay();
	tq.lock_week=true;
	tq.week_days=7;
	cal_tq_rstday(xc,tq);
	xc.tq=tq;
	return tq;
}
function cal_tq_nxtday(xc,tq)
{
	if(!tq) tq=xc.tq;
	var day=tq.day;
	day++;
	if(tq.prev_rollover){
		if(day>tq.prevdays) {
			day=1;
			tq.prev_rollover=false;
		}	
	}else{
		if(day>tq.curdays) {
			day=1;
			tq.rollover=true;
		}
	}
	tq.day=day;
	return tq.rollover;
}
function cal_tq_rstday(xc,tq)
{
	if(tq.lock_week){
		tq.day=xc.cur_day-tq.dow;
		if(tq.day<1) {
			tq.day+=tq.prevdays;
			tq.prev_rollover=true;
		}
	}else{
		tq.day=xc.cur_day;
	}	
	tq.rollover=false;
}
function cal_cl(n)
{
	var el=dge('cal_b_0_'+n);
	if(!el) return 0;
	return el.offsetLeft;
}

#	var d2=dt2dv(d); var d3=ms2dv(t);
function cal_event_savetimes(xc,ev)
{
	if(!ev.t1 || !ev.t2) return;
	var t1s=ms2dvDZ(ev.t1,ev.allday);
	var t2s=ms2dvDZ(ev.t2,ev.allday);
	ev.t1s = t1s
	ev.t2s = t2s
#	if(t1s.indexOf('T150000')!=-1) event_delete_cb(xc,ev);
	event_savetimes_cb(xc,ev);
}
function cal_update_details(xc,ev)
{
	if(!ev) return;
	var el=uid_to_el(ev.uid);
	cal_relayout_event(xc,el.parentNode,ev.t1,ev.t2);
}

function event_change_start(co2,o2,ott)
{		
	co2.style.zIndex=1000;
	dbg('EV CHANGE START ott=',ms2dv(ott))
	co2.ott=ott;
	if(o2){
		co2.ot1=o2.ev.t1;
		co2.ot2=o2.ev.t2;
		co2.orig=o2;
	}
}
function cal_body_update(xc,col,ev,pending)
{
	if(xc.cur_view=='year') return;
	if(!col) return;
	var children=col.childNodes;
	if(!children || children.length<2) return;
	children[1].innerHTML=cal_body_time(ev,pending)+ev.desc;
}

# update display of an event (possibly multiclounm) based on changes in tt and tx
function cal_update_el_tt(xc,o,tt,tx)	// QQUPDATE
{
	var rev=false,t1,t2;
	if(tt<tx) { t1=tt; t2=tx; rev=!o.hT;} else { t1=tx; t2=tt; rev=o.hT;}
	var store=o.orig?o.orig:o;
	store.nt1=t1, store.nt2=t2;
	o.ev.nt1=t1; o.ev.nt2=t2; 
	cal_relayout_event(xc,o,t1,t2,true);
}
function cal_relayout_event(xc,o,t1,t2,pending)
{
	var nc=0, onc=o.childNodes.length;
	rc1 = cal_time_to_rc(xc,t1);
	rc2 = cal_time_to_rc(xc,t2);
	dbg('RC1 c='+rc1[0]+','+rc1[1]+' RC2 c='+rc2[0]+','+rc2[1]);
	// for each wanted colum, grab column or create, update positioning as needed, updates handles as needed, delete any left over columns
	var col2=rc2[0];
	// Tidy up multiday events??
	if(col2>xc.columns-1) col2=xc.columns-1;
	// trim any "start of day" endpoints
	if(col2>rc1[0] && rc2[1]==0) col2--;

	for (c=rc1[0];c<=col2;c++){
		// if col too small { suitably hide first handle; continue;}
		// if col too big { suitably hide last handle; continue;}
		var col=o.childNodes[nc];
		var first=(c==rc1[0]), last=(c==rc2[0]);
		var need_insert=col&&col.is_handle;
		if(!col || need_insert) {
			col=cal_new_block(xc,o,o.ev,col);
			col.style.zIndex=10;
			col.style.opacity=1.0;//0.75;
			var pcol=o.childNodes[nc-1];
			col.style.width=pcol.style.width;
		}
# 		assign size for column
		var r1=0,r2=xc.col_slices[c];
 		if(first) r1=rc1[1];
 		if(last) r2=rc2[1];
 		var xy1=cal_rc_to_xy(xc,o.ev,[c,r1]);
 		var xy2=cal_rc_to_xy(xc,o.ev,[c,r2]);
 		xy2=cal_apply_min_size(xc,xy1,xy2,r1);	// maybe change this logic...
//		cal_xy_position_event(xc,el,xys)
		col.style.left=px(xy1[0]);
		col.style.top=px(xy1[1]);
#		var qq=xy2[1]-xy1[1];
#		dbg('QQQ col='+c+' qq='+qq+' 1='+xy1[1]+' 2='+xy2[1])
		if(xc.horiz){
			col.style.width=px(xy2[0]-xy1[0]);
		}else{
			col.style.height=px(xy2[1]-xy1[1]);
		}
		cal_body_update(xc,col,o.ev,pending);
		nc++;
	}
	for(c=nc;c<onc;c++){
		var col=o.childNodes[c];
		if(!col || col.is_handle) continue;
		remove_node(col);
	}
}


function cal_event_each_block(o,fn)
{
	for (var i=0;i<o.childNodes.length;i++){
		var block=o.childNodes[i];
		if(!block) continue;
		if(block.is_handle) continue;
		fn(block);
	}
}


function cal_split_events(xc,evs)
{
	var allday=[], rest=[], ev, ret={};
	for(var i=0;i<evs.length;i++){
		ev=evs[i];
		if(ev.allday){
			allday.push(ev);
		}else{
			rest.push(ev);
		}
	}
	ret.allday=allday;
	ret.rest=rest;
	return ret;
}

function cal_copy_assign(xc2,evs,xc)
{
	xc2.evs=evs;
	for(var i=0;i<evs.length;i++){
		evs[i].xc=xc2;
	}
	xc2.is_sub=true;
	xc.sub=xc2;
}


var cal_keep_count=1;
function next_keepid()
{
	return 'keep'+cal_keep_count++;
}
function cal_temp_keep(xc,ev,el)
{
	if(!el.newly_created) return;

	var i, evs=xc.evs;
	evs.push(ev);	
	cal_update_ids(ev,el,next_keepid());
	dge('cal_events').appendChild(el);

	evs2=[];
	var cal_events_tmp=dge('cal_events_tmp');
	cal_events_tmp.innerHTML='';
}

function cal_update_ids(ev,el,uid)
{
	var el2;
	ev.uid=uid;
	el.id='evt_'+uid;
	for(i=0;i<el.childNodes.length;i++){
		el2=el.childNodes[i];
		el2.id='evt_'+uid+'_c'+(i+1);
	}
	el.newly_created=U;
}
function cal_click_set_day(xc,t,nosingle)
{
	cal_setcurday(xc,t);
	var e=event;
	if(nosingle && single_modifier(e)) return;
	if(any_modifier(e)){
		var up=shift_alt(e);
		setTimeout(function(){
			if(up){
				cal_nav_up(xc,1);
			}else{
				cal_nav_down(xc,1);
			}
		},10);
	}
}
function cal_scale_asint(view)
{
	switch(view){
		case 'year': return 4;
		case 'month': return 3;
		case 'week': return 2;
		case 'day': return 1;
	}
	return -1;
}
function cal_scale_astxt(view)
{
	switch(view){
		case 4: return 'year';
		case 3: return 'month';
		case 2: return 'week';
		case 1: return 'day';
	}
	return 'year';
}
function cal_scale_limit(view)
{
	if(view<1) return 1;
	if(view>4) return 4;
	return view;
}
function cal_apply_step(xc,step,sign)
{
	if(step>5) step=5;
	if(step<1) step=1;
	switch(step){
		case 5:cal_curdelta(xc,sign*3,0,0);break;
		case 4:cal_curdelta(xc,sign*1,0,0);break;
		case 3:cal_curdelta(xc,0,sign*1,0);break;
		case 2:cal_curdelta(xc,0,0,sign*7);break;
		case 1:cal_curdelta(xc,0,0,sign*1);break;
	}
}

function cal_nav_up(xc,keys)
{
	var scale=cal_scale_asint(xc.cur_view);
	scale=cal_scale_astxt(cal_scale_limit(scale+keys));
	if(scale==xc.cur_view) return;
	swcal_switch(scale);
}
function cal_nav_down(xc,keys)
{
	var scale=cal_scale_asint(xc.cur_view);
	scale=cal_scale_astxt(cal_scale_limit(scale-keys));
	if(scale==xc.cur_view) return;
	swcal_switch(scale);
}
function cal_nav_left(xc,keys)
{
	var step=cal_scale_asint(xc.cur_view);
	if(keys>1) step+=(keys-1);
	cal_apply_step(xc,step,-1);
	swcal_switch(xc.cur_view);
}
function cal_nav_right(xc,keys)
{
	var step=cal_scale_asint(xc.cur_view);
	if(keys>1) step+=(keys-1);
	cal_apply_step(xc,step,+1);
	swcal_switch(xc.cur_view);
}

function cal_event_t2ts(ev)
{
	ev.t1s=ms2dv(ev.t1); 
	ev.t2s=ms2dv(ev.t2);	
	cal_event_checkts(ev);
}
function cal_event_checkts(ev)
{
	if(ev.t1s.length!=15 || ev.t2s.length!=15) return;
	var tail1=ev.t1s.substring(8);
	var tail2=ev.t2s.substring(8);
	if(tail1=='T000000' && tail2=='T000000'){
		ev.t1s=ev.t1s.substring(0,8);
		ev.t2s=ev.t2s.substring(0,8);
	}
}
function cal_notsafe(ev)
{
	if(pref.cal_noedit=='') return false;
	if(mtime()-ev.t2>msec_month()) return true;
	if(pref.cal_noedit=='older') return false;
	if(ev.t2-mtime()>msec_month()) return true;
	return false;
}

function cal_colors()
{
#	// Cols: #b90e28 = red, #f64f00 = orange, #e6c800 = yellow, #44a703 = green
#	//		 #0e61b9 = blue, #711a76 = purple, #882f00 = brown
			// orange	blue		green		yellow		red		purple		brown
	return ['#f64f00', '#0e61b9', '#44a703', '#e6c800', '#b90e28', '#711a76', '#882f00'];
			
}

function cal_location_warn(warn)
{
	sw.location_warn = warn;
	var el=dge("cal_warntxt");
	if(el) el.innerHTML=cal_warntxt();
}

function cal_minbrowser()
{
	x_alert("Upgrade to IE9  or latest Chrome / Firefox / Safari for best user experience (IE8 is mostly compatible too)");
}

function cal_find_cal(name)
{
	if(!name || !sw.cals_array) return U;
	for(var i=0;i<sw.cals_array.length;i++){
		var id=sw.cals_array[i], cal=sw.cals[id];
		if(cal.name==name) return id;
	}
	return U;
}