function Listbox() {
	/*extern DOMhelp, ShSettings*/
	var scrollPos=0;
	var firstvisible=0;
	var itemcount=0;
	var totalsizeneeded=0;
	var skip=0;
	var sdelta=0;
	var areaht=30;
	var extraspace=0;
	var divs=[];
	var indtop=0;
	var indht=0;
	var mouseY=0;
	var diff=0;
	var eThHt=0;
	var topOffset=0;
	var width=100;
	var mousedn=false;
	var usepress=false;
	var getLimitCB=null;
	var putItemInCB=null;
	var divht=ShSettings.lbItemHeight;
	var divwd=ShSettings.lbItemWidth;
	var divpad=ShSettings.lbItemPadding;
	var innerdivwd=divwd-2*divpad;
	var rowcount=0;
	var colcount=2;
	var divcount=rowcount*colcount;
	var o=DOMhelp.createElement;
	var highlightdiv=o('div#hilite');
	var matches=o('div#matches', highlightdiv);
	var scup=o('div#scup');
	var scthmid=o('div#sctmid');
	var scthumb=o('div#scthumb', o('div#scttop'), scthmid, o('div#sctbot'));
	var scdn=o('div#scdn');
	var scbar=o('div#scbar', scup, scthumb, scdn);
	var keyCB = function() {return true;};
	var highlighted = -1;

	this.DOMelements = function() {return [matches, scbar];};
	this.setKeyCB = function(fn) { keyCB=fn; };
	this.setLimitCB = function(fn) { getLimitCB=fn; };
	this.setItemInCB = function(fn) { putItemInCB=fn; };
	function endScroll() {
		sdelta=0;
		document.body.onmousemove=null;
		mousedn=false;
	}
	function trMouseFn(e) {
		if (e && (e.pageX || e.pageY)) {
			return function(x) {mouseY=x.pageY;};
		}
		if (e && (e.clientX || e.clientY)) {
			return function(x) {mouseY=x.clientY+document.body.scrollTop+document.documentElement.scrollTop;};
		}
		if (window.event.pageY) {
			return function() {mouseY=window.event.pageY;};
		}
		return function() {mouseY=window.event.clientY+document.body.scrollTop+document.documentElement.scrollTop;};
	}
	function trackThumb() {
		if (!mousedn) {
			return;
		}
		var d = mouseY-diff-indtop;
		if (d!==0) {
			scrollBy(Math.round(d*totalsizeneeded/(areaht-eThHt-34)));
		}
		setTimeout(trackThumb,50);
	}
	function findTop(obj) {
		var curtop = 0;
		if (obj.offsetParent) {
			curtop = obj.offsetTop;
			while ((obj = obj.offsetParent)) {
				curtop += obj.offsetTop;
			}
		}
		return curtop;
	}
	function trackUp() {
		if (!mousedn) {
			return;
		}
		if (skip%6===0 && mouseY<findTop(scthumb)) {
			scrollBy(-283);
		}
		if (skip) {
			skip--;
		}
		setTimeout(trackUp,50);
	}
	function trackDn() {
		if (!mousedn) {
			return;
		}
		if (skip%6===0 && mouseY>findTop(scthumb)+indht) {
			scrollBy(283);
		}
		if (skip) {
			skip--;
		}
		setTimeout(trackDn,50);
	}
	function fixhighlight() {
		var idx=highlighted-firstvisible;
		if (idx>=0 && idx<divs.length) {
			var extra=(window.ieLT7 ? 8 : 2);
			highlightdiv.style.display='block';
			highlightdiv.style.width=(divs[idx].offsetWidth+extra)+"px";
			highlightdiv.style.height=(divs[idx].offsetHeight+extra)+"px";
			highlightdiv.style.top=(divs[idx].offsetTop-4)+"px";
			highlightdiv.style.left=(divs[idx].offsetLeft-4)+"px";
		} else {
			highlightdiv.style.display='none';
		 }
	}
	function placeItemsTop() {
		for (var i=0; i<divs.length; ++i) {
			divs[i].style.top = (divht*Math.floor(i/colcount)-topOffset+1+divpad)+'px';
		}
		fixhighlight();
	}
	function placeItemsLeft() {
		for (var i=0; i<divs.length; ++i) {
			// divwd==-1 wont matter here because (i%colcount) will be 0
			divs[i].style.left = (extraspace+(divwd+extraspace)*(i%colcount)+divpad)+'px';
		}
		fixhighlight();
	}
	function doAScroll() {
		if (sdelta && (skip%6!==0 || scrollBy(sdelta))) {
			setTimeout(doAScroll,50);
		} else {
			sdelta=0;
		}
		if (skip) {
			skip--;
		}
	}
	function scrollUp() {sdelta=-34; skip=6; doAScroll(); return false;}
	function scrollDn() {sdelta=34; skip=6; doAScroll(); return false;}
	function mouseDnBar(e) {
		mousedn=true;
		(document.body.onmousemove=trMouseFn(e))(e);
		skip=6;
		if (mouseY<=findTop(scthumb)) {
			trackUp();
		} else {
			trackDn();
		}
		return false;
	}
	function mouseDnThumb(e) {
		mousedn=true;
		(document.body.onmousemove=trMouseFn(e))(e);
		diff=mouseY-indtop;
		trackThumb();
		return false;
	}

	scup.onmousedown=scrollUp;
	scdn.onmousedown=scrollDn;
	scbar.onmousedown=mouseDnBar;
	scthumb.onmousedown=mouseDnThumb;
	matches.onscroll=function() {
		if (matches.scrollTop>0) {
			scrollBy(matches.scrollTop);
			matches.scrollTop=0;
		}
	};
	scthumb.onselectstart=function() {return false;};
	function setItemDivCount(prepfn, clearall) {
		divcount=rowcount*colcount;
		var delto = clearall ? 0 : divcount;
		while (divs.length>delto) {
			matches.removeChild(divs.pop());
		}
		if (prepfn) {
			prepfn();
		}
		for (var n=divs.length; n<divcount; ++n) {
			divs[n]=o('div.item');
			divs[n].style.width=innerdivwd+"px";
			divs[n].style.top = (divht*Math.floor(n/colcount)-topOffset+1+divpad)+'px';
			divs[n].style.left = (extraspace+(divwd+extraspace)*(n%colcount)+divpad)+'px';
			matches.appendChild(divs[n]);
			if (putItemInCB && firstvisible+n<itemcount) {
				putItemInCB(firstvisible+n, divs[n]);
			}
		}
	}

	function movebar() {
		indtop=Math.floor(scrollPos*(areaht-eThHt-34)/totalsizeneeded+17);
		scthumb.style.top=indtop+'px';
	}
	function doind() {
		if (areaht>=totalsizeneeded) {
			scbar.style.visibility='hidden';
		} else {
			scbar.style.visibility='visible';
			indht = Math.floor(areaht*(areaht-34)/totalsizeneeded);
			if (indht>20) {
				eThHt = 0;
			} else {
				eThHt = 20-indht;
				indht = 20;
			}
			scthumb.style.height=(indht-2)+'px';
			scthmid.style.top=(indht/2-5)+'px';
			movebar();
		}
	}
	this.clearItems=function(newcount) {
		firstvisible=0;
		itemcount=newcount;
		totalsizeneeded=Math.max(Math.ceil(itemcount/colcount)*divht,1);
		scrollPos=0;
		topOffset=0;
		placeItemsTop();
		sdelta=0;
		var i=0;
		if (putItemInCB) {
			for (; i<newcount && i<divcount; ++i) {
				putItemInCB(i, divs[i]);
			}
		}
		for (; i<divcount; ++i) {
			divs[i].innerHTML='';
		}
		doind();
	};
	this.highlight=function(n) {
		highlighted=n;
		fixhighlight();
	};
	this.itemReceived=function(itemno) {
		if (itemno>=firstvisible && itemno<firstvisible+divcount && putItemInCB) {
			putItemInCB(itemno, divs[itemno-firstvisible]);
		}
	};
	function rewidth() {
		for (var i=0; i<divcount; ++i) {
		 	divs[i].style.width=innerdivwd+"px";
		}
	}
	function changeFirstVisible(newfv) {
		var delta=newfv-firstvisible;
		firstvisible=newfv;
		var beginreplace=0, endreplace=divcount;

		/* Salvage any existing data */
		var i;
		if (delta<0 && -delta<divcount) {
			for (i=0; i<-delta; ++i) {
				divs.unshift(divs.pop());
			}
			endreplace=-delta;
		} else if (delta>0 && delta<divcount) {
			for (i=0; i<delta; ++i) {
				divs.push(divs.shift());
			}
			beginreplace=divcount-delta;
		}
		if (delta!==0) {
			i=beginreplace;
			if (putItemInCB) {
				for (; i<endreplace && firstvisible+i<itemcount; ++i) {
					putItemInCB(firstvisible+i, divs[i]);
				}
			}
			for (; i<endreplace; ++i) {
				divs[i].innerHTML='';
			}
		}
	}
	function calcExtraSpace() {
		extraspace = (divwd<=0) ? 16 : width-colcount*divwd;
		if (areaht<totalsizeneeded) {
			extraspace-=16;
		}
		extraspace = Math.floor(extraspace/(colcount+1));
	}
	this.setWidth=function(wd, center) {
		width=wd;
		if (divwd<=0) {
			innerdivwd=Math.min(width-2*divpad, 650);
		}
		matches.style.width=(width+(window.ieLT7 ? 2 : 0))+"px";
		scbar.style.left=(width-8)+"px";
		var newcolcount = (divwd<=0) ? 1 : Math.max(Math.floor(width/divwd),1);
		if (colcount!=newcolcount) {
			colcount=newcolcount;
			var oldtsn = totalsizeneeded;
			totalsizeneeded=Math.max(Math.ceil(itemcount/colcount)*divht,1);
			doind();

			calcExtraSpace();
			setItemDivCount(divwd<=0 ? rewidth : null, false);
			if (center==-1) {
				if (scrollPos===0) {
					scrollTo(0, true, true);
				} else {
					scrollTo((scrollPos+areaht*0.5)*totalsizeneeded/oldtsn-areaht*0.5, true, true);
				}
			} else {
				scrollTo(Math.floor(center/colcount)*divht-(areaht-divht)/2, true, true);
			}
			placeItemsLeft();
		} else if (divwd<=0) {
			rewidth();
		} else {
			calcExtraSpace();
			placeItemsLeft();
		}
	};
	this.setItemDims=function(itemwd, itemht, wd, center) {
		divwd=itemwd;
		divht=itemht;
		rowcount=Math.ceil(areaht/divht)+2;
		width=wd;
		if (divwd<=0) {
			innerdivwd=Math.min(width-2*divpad, 650);
		} else {
			innerdivwd=divwd-2*divpad;
		}
		matches.style.width=(width+(window.ieLT7 ? 2 : 0))+"px";
		scbar.style.left=(width-8)+"px";
		colcount = (divwd<=0) ? 1 : Math.max(Math.floor(width/divwd),1);
		var oldtsn = totalsizeneeded;
		totalsizeneeded=Math.max(Math.ceil(itemcount/colcount)*divht,1);
		doind();

		calcExtraSpace();
		if (center==-1) {
			if (scrollPos===0) {
				scrollTo(0, true, false);
			} else {
				scrollTo((scrollPos+areaht*0.5)*totalsizeneeded/oldtsn-areaht*0.5, true, false);
			}
		} else {
			scrollTo(Math.floor(center/colcount)*divht-(areaht-divht)/2, true, false);
		}
		setItemDivCount(null, true);
	};
	this.setHeight=function(ht) {
		if (areaht!=ht) {
			areaht=ht;

			rowcount=Math.ceil(areaht/divht)+2;
			setItemDivCount(null, false);

			matches.style.height=(areaht-(window.ieLT7 ? 1 : 3))+"px";
			scbar.style.height=(areaht-3)+"px";
			scdn.style.top=(areaht-20)+"px";
			doind();
			scrollBy(0);
		}
	};
	function scrollBy(delta) {
		return scrollTo(scrollPos+delta, false, true);
	}
	function scrollTo(dest, force, rollItems) {
		dest=Math.round(dest);
		var limit=(getLimitCB) ? getLimitCB() : itemcount;
		var pixellimit=Math.ceil(limit/colcount)*divht;
		if (dest+areaht>pixellimit) {
			dest=pixellimit-areaht;
		}
		if (dest<0) {
			dest=0;
		}
		if (dest==scrollPos && !force) {
			return false;
		}

		scrollPos=dest;
		var firstvisiblerow = Math.floor(scrollPos/divht);
		if (rollItems) {
			changeFirstVisible(firstvisiblerow*colcount);
		} else {
			firstvisible=firstvisiblerow*colcount;
		}
		topOffset = scrollPos-firstvisiblerow*divht;
		placeItemsTop();
		movebar();    
		return true;
	}
	function mouseOut(e) {
		var ev=e || window.event;
		if (!ev.relatedTarget && !ev.toElement) {
			endScroll();
		}
	}
	function mwheelhandlerbody(e) {
		if (e.cancelable) {
			e.preventDefault();
		}
	}
	function mwheelhandlerbody2() {
		return false;
	}
	if (matches.addEventListener) {
		matches.addEventListener( 'DOMMouseScroll', function(e) {scrollBy(e.detail/3*51);}, false);
	} else {
		matches.onmousewheel=function() {scrollBy(-event.wheelDelta/120*51);};
	}
	function keyhandler(e) {
		var ev=e || window.event;
		var k = ev.keyCode ? ev.keyCode : ev.which;
		var press=(ev.type=='keypress');
		if (k>40 || k<33 || k==39 || (k>34 && k<38)) {
			return (press || keyCB(k));
		}
		if (press==usepress) {
			scrollBy(k>=38 ? (k==38 ? -51 : 51) : (k==33 ? -231 : 231));
		}
		if (press) {
			usepress=true;
		}
		return false;
	}
	this.turnOn=function() {
		document.onmouseup=endScroll;
		document.body.onmouseout=mouseOut;
		if (document.body.addEventListener) {
			document.body.addEventListener( 'DOMMouseScroll', mwheelhandlerbody, false);
		} else {
			document.body.onmousewheel=mwheelhandlerbody2;
		}
		document.onkeypress = document.onkeydown= keyhandler;
		scbar.style.display="block";
		matches.style.display="block";
	};
	this.turnOff=function() {
		document.onmouseup=null;
		document.onmousemove=null;
		document.body.onmouseout = null;
		if (document.body.addEventListener) {
			document.body.removeEventListener( 'DOMMouseScroll', mwheelhandlerbody, false);
		} else {
			document.body.onmousewheel = null;
		}
		document.onkeypress = document.onkeydown = null;
		scbar.style.display="none";
		matches.style.display="none";
	};
}

