/*
 * Copyright IPVision, Inc 2003 - 2005.   All rights reserved.
 */
 

function UIMap(containerId, mapSettings, tsName)
{
	uiMapInit(this);

	if(containerId)
		this.containerId = containerId;

	this.mapSettings = mapSettings;
	this.tsName = tsName;


    this.scroll.delta 			= 80;
	this.viewport.zIndex 		= 50000;
	this.viewport.background 	= "#ccf";
	this.viewport.border 		= "3px solid #ccf";
	this.viewport.sizeToContainer = true;
	this.canvas.background 		= "#eef";
	this.tools.domElemId 		= this.domElemId + ".tools";
}

var uiMapFlyoverVisuals = {
			ignoreOnClick:   true,
			ignoreMouseover: true,
			boxStyle:	"margin: 0px; border: 1px solid black; padding: 0px; position: absolute;  overflow: hidden",
			textStyle:	"margin: 0px; border: 0px; padding: 0px; cursor: hand; font: 13px, 'arial'; ;padding-left: 2px; display: block; ",
			textCount:	3,
			width: 		400,
			height: 	74,
			border: 	{top: 8, bottom: 0},
			scale: 		{x: 1, y: 1.2}
};


function uiMapInit(object)
{
// INHERIT
	uiViewInit(object);

// MEMBER VARIABLES
	object.domElemId 			= "uiMap." + object.id;
	object.isInteractiveItems	= false;
	object.items 				= null;				// UIMapItems
	object.nItems 				= 0;
	object.itemData 			= new Array();
	object.itemVisuals 			= null;
	object.itemFlyover 			= new UIMapItem(object, null, uiMapFlyoverVisuals);
	object.flyoverItemIndex 	= -1;

	object.overview				= null;
	object.isOverviewEnabled	= true;
	object.entityMap 			= null;				// Raw data specifying the map
	object.selector 			= new UIMouseSelect();
	object.selection			= new UIMapSelection(object);

//  METHODS
	object.buildItems			= uiMapBuildItems;
	object.buildItemData		= uiMapBuildItemData;
	object.moveCanvasDone		= uiMapMoveCanvasDone;
	object.adjustViewport		= uiMapAdjustViewport;
	object.adjustCanvas			= uiMapAdjustCanvas;
	object.adjustTools			= uiMapAdjustTools;

	object.baseMouseup			= object.mouseup;
	object.mouseup				= uiMapMouseup;

	object.addItem				= uiMapAddItem;
	object.setItemAllSelect 	= uiMapSetItemAllSelect;
	object.setItemSelect  		= uiMapSetItemSelect;
	object.setItemSelectFromString = uiMapSetItemSelectFromString;
	object.getItemIds   		= uiMapGetItemIds;
	object.getItemIdsString     = uiMapGetItemIdsString;

	object.setItemVisuals 		= uiMapSetItemVisuals;
	object.setEntityMap			= uiMapSetEntityMap;

}

//------------------------------------------------------------------

/************************************************************/
UIMap.prototype.getMapInfo = function()
{
	var info = {entityMap: 			this.entityMap,
				title:				this.mapSettings.title,
				nItems: 			this.itemData.length,
				selection:			this.selection};
	return info;
}

/************************************************************/
UIMap.prototype.selectArea = function(cmd, selectBounds, isSelectOn)
{
	var nItems = this.itemData.length;
	if(cmd == "start") {
		if(!this.itemsTopSorted)
			this.itemsTopSorted = this.itemData.slice().sort(function(a,b) { return a.top - b.top; })
		this.isSelectStateModified = new Array(nItems);
		return;
	}

	if(cmd == "end") {
		this.selection.update('Drag select items', true);
		return;
	}

	var data = this.itemsTopSorted[0];
	var domElem = GetDomElem(this.domElemId + '.item.' + data.itemIndex);
	var xDelta = data.left - GetAbsLeft(domElem);
	var yDelta = data.top  - GetAbsTop(domElem);

	var top  = selectBounds.top  + yDelta;
	var left = selectBounds.left + xDelta;
	var bottom = selectBounds.bottom + yDelta;
	var right  = selectBounds.right  + xDelta;

	for(var n=0;  n < nItems;  n++) {
		var data = this.itemsTopSorted[n];
		if(((top < data.top && bottom > data.top) || (top < data.bottom && bottom > data.bottom) || (top > data.top && bottom < data.bottom)) &&
				((left <  data.left && right > data.left) || (left < data.right && right > data.right) || (left > data.left && right < data.right)) ) {
			if(!this.isSelectStateModified[n]) {	// Select rect has changed and now INLCUDES the item
				if(isSelectOn == !data.isSelected) {
					this.isSelectStateModified[n] = true;
					this.setItemSelect(data.itemIndex, !data.isSelected);
					// DebugText("Selected:  top=" + data.top + ", left=" +data.left + ", bottom=" + data.bottom + ", right=" + data.right);
				}
			}
		}
		else {
			if(this.isSelectStateModified[n]) { 	// Select rect has changed and now EXCLUDES a previously INCLUDED item
				this.setItemSelect(data.itemIndex, !data.isSelected);
				this.isSelectStateModified[n] = false;
			}
		}
	}
}

/************************************************************/

UIMap.prototype.selectEntities = function(cmd, entityUids, descr)
{
	if(typeof entityUids == "string")
		entityUids = PipeSetToArray(entityUids);

	var hashMap = new Object();
	for(var n=0; n < entityUids.length; n++)
		hashMap[entityUids[n]] = true;

	for(var n=0;  n < this.itemData.length; n++){
		var data = this.itemData[n];
		var isInList = hashMap[data.uid];
		if(cmd == "Select")
			this.setItemSelect(data.itemIndex, isInList);
		if(cmd == "Add" && isInList)
			this.setItemSelect(data.itemIndex, true);
		if(cmd == "Subtract" && isInList)
			this.setItemSelect(data.itemIndex, false);
		if(cmd == "Intersect" && !isInList)
			this.setItemSelect(data.itemIndex, false);
	}

	if(cmd == "Select")
		this.selection.reset();
	this.selection.update(descr);
}

//=== FLASH HILITE ========================================================================/

UIMap.prototype.startHeadFlashSelected = function(flashMode, flashInterval)
{
	var hashMap = this.getEntityUidHash();
	var booleans = new Array(this.itemData.length);

	for(var n=0;  n < this.itemData.length;  n++){
		var data = this.itemData[n];
		if(data.isSelected) {
			var index = hashMap[data.entity.substituteUid ? data.entity.substituteUid : data.uid]
			booleans[index] = true;
		}
	}
	this.startFlashHilite(flashMode, BooleansToBase64String(booleans), flashInterval);
}

UIMap.prototype.startFlashSelected = function(flashMode, flashInterval)
{
	this.startFlashHilite(flashMode, this.getItemIdsString(true), flashInterval);
}

UIMap.prototype.startFlashByEntitySet = function(flashMode, uidSet, flashInterval)
{
	var itemsString = this.makeItemIdsStringFromIndexArray(uidSet);
	this.startFlashHilite(flashMode, itemsString, flashInterval);
}

UIMap.prototype.startFlashHilite = function(flashMode, itemsString, flashInterval)
{
	if(!itemsString) {
		alert("Nothing to hilite");
		return;
	}

	this.stopFlashHilite();
	this.initFindItem();

	// Send a request for the bitmap to the server.
	// The server returns URL which is used to set the src on the overlay <img> DOM element
	this.itemHiliteUrlRequest(this.domElemId + ".overlay", entityMap.pxScale, flashMode, itemsString);

	this.hiliteFlashId = !this.hiliteFlashId ? 1 : (this.hiliteFlashId+1);
	this.toggleFlashHilite(true, this.hiliteFlashId, flashInterval);
}

UIMap.prototype.toggleFlashHilite = function(makeVisible, hiliteFlashId, flashInterval)
{
	var domElem = GetDomElem(this.domElemId + ".overlay");
	if(this.hiliteFlashId == hiliteFlashId) {
		domElem.style.display = makeVisible ? "block" : "none";
		if(flashInterval == undefined)
			flashInterval = [1000, 500];
		if(flashInterval[1] == 0)
			return;     // Don't flash, just leave it on
		var jsCode = 'GetUiElem(' + this.id + ').toggleFlashHilite(' + (!makeVisible) + ',' + hiliteFlashId + ', [' + flashInterval.join(',') + '])';
		setTimeout(jsCode, flashInterval[makeVisible ? 0 : 1]);
	}
}

UIMap.prototype.stopFlashHilite = function()
{
	var domElem = GetDomElem(this.domElemId + ".overlay");
	domElem.src = "";
	domElem.style.display = "none";
	this.hiliteFlashId = !this.hiliteFlashId ? 1 : (this.hiliteFlashId+1);
}

//=== HILITE OVERLAY EVENTS =====================================================================/

UIMap.prototype.overlayEvent = function(event)
{
	var itemIndex = this.findItem(event);
	if(event.type == "mousemove")
	{
		if(itemIndex != -1)
			this.mouseoverItem(event, itemIndex);
		else
			this.mouseoutItem(event);
	}
	else if(event.type == "contextmenu" && itemIndex != -1)
		this.rightClickItem(event, itemIndex);
	else
		this.stopFlashHilite();

	UiCancelEvent(event);
}

UIMap.prototype.findItem = function(event)
{
	var coord = UiMouseToElemCoord(event);
	var items = this.itemsLeftSorted;

	while(this.hLeftIndex > 0 && items[this.hLeftIndex].left > coord.x)
		this.hLeftIndex--;
	while(this.hLeftIndex < items.length - 2 && items[this.hLeftIndex + 1].left <= coord.x)
		this.hLeftIndex++;

	var itemIndex = -1;
	for(var n = this.hLeftIndex; n >= 0; n--)
	{
		var item = items[n];
		if(item.left + this.maxItemWidth < coord.x)
			break;
		if(item.top <= coord.y && item.bottom >= coord.y)
			itemIndex = (itemIndex > items[n].itemIndex ? itemIndex : item.itemIndex);
	}
	return itemIndex;
}

UIMap.prototype.initFindItem = function()
{
	this.hLeftIndex = 0;

	if(!this.itemsLeftSorted)
	{
		var items = this.itemData.slice().sort(function(a, b)
		{
			return a.left - b.left;
		})
		this.itemsLeftSorted = items;
		this.maxItemWidth = 0;
		for(var n = 0; n < items.length; n++)
			this.maxItemWidth = Math.max(this.maxItemWidth, items[n].right - items[n].left);
	}
}

/************************************************************/

UIMap.prototype.extendSelected = function(extendForward)
{
	var itemCount = this.itemData.length;
	var lookup = new Array();

	for(var n=0;  n < itemCount;  n++) {
		var data = this.itemData[n];
		if(data.isSelected)
			lookup[data.uid] = true;
	}

	var hashmap = this.getEntityUidHash();

	var connect = this.entityMap.Connect;
	for(var n=0;  n < connect.length;  n++) {
		var fromId = extendForward ? connect[n].srcUid : connect[n].dstUid;
		var toId   = extendForward ? connect[n].dstUid : connect[n].srcUid;
		if(lookup[fromId] && !lookup[toId])
			this.setItemSelect(hashmap[toId], true);
	}

	this.selection.update("Extend selection " + (extendForward ? "forward" : "backward"));
}

UIMap.prototype.getEntityUidHash = function()
{
	if(!this.entityUidHash) {
		var itemCount = this.itemData.length;
		this.entityUidHash = new Array(itemCount);
		for(var n = 0; n < itemCount; n++)
			this.entityUidHash[this.itemData[n].uid] = n;
	}
	return this.entityUidHash;
}

/************************************************************/
function uiMapMouseup(domElem, event)
{
	if(this.baseMouseup)
		this.baseMouseup(domElem, event);
}

function uiMapMouseoverItem(event, itemIndex, xyDomElem)
{
	if(this.selector.isMouseActive  || event.ctrlKey)
		return;

	this.mouseoutItem(event);

	var domElem = GetDomElem(this.domElemId + ".item." + itemIndex);
	var itemFlyover = this.itemFlyover;
	var itemVisuals = this.itemVisuals;
	var itemData = this.itemData[itemIndex];
	var visuals = itemFlyover.visuals;

	if(this.viewport.bounds == undefined)
		this.viewport.bounds = GetAbsBounds(this.viewport.domElem);
	viewportBounds = this.viewport.bounds;

	if(!xyDomElem)
		xyDomElem = domElem;

	var top  = GetAbsTop(xyDomElem) - visuals.height - 3;
	var left = GetAbsLeft(xyDomElem)+ Math.round((itemVisuals.width-visuals.width)/2,0);

	if(left <= viewportBounds.left)
		left = viewportBounds.left + 1;
	if(left+visuals.width >= viewportBounds.right)
		left = viewportBounds.right - visuals.width - 1;

	itemFlyover.setPosition(left, top);
	itemFlyover.setData(itemData);
	domElem.style.backgroundColor = "#999";
	domElem.style.borderTopColor = "black";

	var flyoverContainer = GetDomElem("global.flyover");
	flyoverContainer.innerHTML = itemFlyover.getHTML();
	flyoverContainer.style.zIndex = FLYOVER_ZINDEX;	// Setting innerHTML seems to erase this
	flyoverContainer.style.display = "block";

	this.flyoverItemIndex = itemIndex;
}

function uiMapMouseoutItem(event, itemIndex)
{
	if(itemIndex == undefined || itemIndex == -1)
		itemIndex = this.flyoverItemIndex;

	if(itemIndex == -1 )
		return;

	var domElem = GetDomElem(this.domElemId + '.item.' + itemIndex);
	var itemData = this.itemData[itemIndex];

	domElem.style.backgroundColor = itemData.backgroundColor;
	domElem.style.borderTopColor = itemData.trimColor;

	var flyoverContainer = GetDomElem("global.flyover");
	flyoverContainer.style.display = "none";

	this.flyoverItemIndex = -1;
}


function uiMapMousedownItem(event, itemIndex)
{
	if(event.button != 2)
		this.leftClickItem(event, itemIndex);
}

function uiMapLeftClickItem(event, itemIndex, xyDomElem)
{
	UiCancelEvent(event);

   	var domElem = GetDomElem(this.domElemId + ".item." + itemIndex);
	var data = this.itemData[itemIndex];
	this.setItemSelect(itemIndex, !data.isSelected)
	if(this.flyoverItemIndex > -1)
		this.mouseoverItem(event, this.flyoverItemIndex, xyDomElem);	// repaint flyover with changed selection
	this.mouseoverItem(event, itemIndex, xyDomElem);

	if(data.isSelected)
		this.selection.update("Selected from Map " + data.uid);
	else
		this.selection.update("Deselected from Map" + data.uid);
}

function uiMapRightClickItem(event, itemIndexOrUid)
{
	UiCancelEvent(event);

	if(typeof itemIndexOrUid == "number")
		var uid = this.itemData[itemIndexOrUid].uid;
	else
		var uid = itemIndexOrUid;

	entityMenu = GetEntityMenu(uid);
	if(entityMenu == null) {
		alert("No action menu for " + uid);
		return;
	}

	if(!entityMenu.contextMenu) {
		var actionMenu = MakeDetailActionMenu(entityMenu, "Actions", "5555555", null, null);
		entityMenu.contextMenu = makeContextMenu(actionMenu);
	}

	entityMenu.contextMenu.setLabel(entityMenu.entityLabel + " " + ParseUid(uid).entityId);
	entityMenu.contextMenu.activateAtClick(event, [uid, this.tsName]);
}

function uiMapBuildSmallItems()
{
	this.leftClickItem	= uiMapLeftClickItem;
	this.rightClickItem	= uiMapRightClickItem;
	this.mouseoverItem = uiMapMouseoverItem;
	this.mouseoutItem = uiMapMouseoutItem;
	this.mousedownItem = uiMapMousedownItem;

	var html = new Array(30);
	var visuals = this.itemVisuals;
	var showText = visuals.height-visuals.border.top > 6;

	for(var n=0;  n < this.itemData.length;  n++) {
		var nHtml = 0;

		var data = this.itemData[n];
		if(data.isSelected)
			data.backgroundColor = visuals.selectedColor;

		var itemDomElemId = this.domElemId + '.item.' + n;

		html[nHtml++] = ('<div id="' + itemDomElemId + '" ');

		var boxStyle = (data.entity.fadeHide && data.entity.fadeHide == GC.FADE_RENDER_CD) ? visuals.fadeBoxStyle : visuals.boxStyle;

		if(boxStyle.charAt(0) == '$')		// BEGIN style attribute
			html[nHtml++] = ('class=' + boxStyle.substring(1) + ' style="')
		else
			html[nHtml++] = ('style="' + boxStyle + '; ');

		html[nHtml++] = ('left: ' + data.left + '; ')
		html[nHtml++] = ('top: ' + data.top + '; ');
		html[nHtml++] = ('width: ' +  (data.right - data.left) + '; ')
		html[nHtml++] = ('height: ' + (data.bottom - data.top) + '; ');
		html[nHtml++] = ('background: ' + data.backgroundColor + '; ');

		with(this.itemVisuals.border) {
			if(top)		html[nHtml++] = ('border-top: solid ' + top + 'px ' + data.trimColor + '; ');
			if(bottom)	html[nHtml++] = ('border-bottom: solid ' + bottom + 'px ' + data.trimColor + '; ');
		}
		html[nHtml++] = ('" ');

		html[nHtml++] = ('onContextMenu="GetUiElem(' + this.id + ').rightClickItem(event,' + n + ');" ');
		html[nHtml++] = ('onMousedown="GetUiElem(' + this.id + ').mousedownItem(event,' + n + ');" ');
		html[nHtml++] = ('onMouseup="UiCancelEvent(event);" ');
		html[nHtml++] = ('onMouseover="GetUiElem(' + this.id + ').mouseoverItem(event,' + n + '); return false;" ');
		html[nHtml++] = ('onMouseout="GetUiElem(' + this.id + ').mouseoutItem(event,'+ n + '); return false;" ');
		html[nHtml++] = ('>');

		if(showText) {
			if(visuals.textStyle.charAt(0) == '$')
				html[nHtml++] = ('<span class=' + visuals.textStyle.substring(1) + '>')
			else
				html[nHtml++] = ('<span style="' + visuals.textStyle + '">');

			var text = data.text.slice(0,visuals.textCount);
			for(var m=0;  m < text.length;  m++) {
				var xmlText = '<font style="color: ' + data.fontColor + '">' + ToXmlText(text[m]) + '</font>';
				if(m < 2)	// Netscape requires a <div> in order not to wrap around the text
					html[nHtml++] = ('<div style="width: 3000px; font-size: ' + (n ? "100%" : "110%") + ';" ><b>' + xmlText + '</b></div>\n');
				else
					html[nHtml++] = ('<span>' + xmlText + '</span>\n');
			}
			html[nHtml++] = ('</span>\n');
		}

		html[nHtml++] = ('</div>\n');
		this.addHTML(html.slice(0,nHtml).join(""));
	}
}

function uiMapBuildItems()
{
	if(!this.isInteractiveItems)
		return;

	this.useSmallItems = true || (this.itemVisuals.height-this.itemVisuals.border.top) < 6;

	if(this.useSmallItems) {
		this.buildSmallItems = uiMapBuildSmallItems;
		this.buildSmallItems();
		return;
	}

	if(this.items == null) {
		this.items = new Array(this.itemData.length);
		this.nItems = 0;

		for(var n=0;  n < this.itemData.length;  n++) {
			var uiMapItem = new UIMapItem(this, this.itemData[n], this.itemVisuals);
			this.addItem(uiMapItem);
		}
	}
	else {
		for(var n=0;  n < this.nItems;  n++) {
		 	this.items[n].setData(this.itemData[n]);
			this.items[n].setVisuals(this.itemVisuals);
		}
	}

	for(var n=0;  n < this.nItems;  n++) {
		this.items[n].computePosition();
		this.addHTML(this.items[n].getHTML());
	}
}

//===HOTSPOTS============================================================================/

UIMap.prototype.setHotSpots = function(hotSpot)
{
	this.entityMap.hotspot = hotSpot;
	if(!hotSpot || hotSpot.length == 0)
		return;

	this.entityMap.HotSpot = hotSpot;
	uiElem = new UIElement();
	for(var n=0;  n < hotSpot.length;  n++) {
		var hs = hotSpot[n];
		if(hs.type == "statItem")
			hs.type = GC.ENTITY_SELECT_HOT_SPOT;

		var coords = 'top: ' + hs.yPx + '; left: ' + hs.xPx + '; width: '+ hs.widthPx + '; height: ' + hs.heightPx + '; '
		var backColor = (hs.type == GC.ENTITY_SELECT_HOT_SPOT ? "white": "lightBlue");
//		backColor = (n%2) ? "red" : "blue";
		var visual = 'position: absolute; overflow: hidden; filter: alpha(opacity=40); opacity:0.4; background: ' + backColor + '; '
		var onMouse = ' onMousemove="UiCancelEvent(event);" onMousedown="UiCancelEvent(event);" onMouseover="GetUiElem(' + this.id + ').mouseoverHotSpot(this,event,' + n + ');" '
				+ 'onMouseout="GetUiElem(' + this.id + ').mouseoutHotSpot(this,event,' + n + ');" '
				+ 'onClick="GetUiElem('+this.id+').leftClickHotSpot(this,event,'+n+');" '
				+ 'onContextMenu="GetUiElem('+this.id+').rightClickHotSpot(this,event,'+n+');" '

		uiElem.addHTML('<div style="' + coords + visual + '" ' + onMouse + '></div>');
	}

//	alert(uiElem.getHTML())
	var domElem = GetDomElem(this.bitmap.domElemId + ".hotSpot")
	domElem.innerHTML = uiElem.getHTML();
}

UIMap.prototype.mouseoverHotSpot = function(domElem, event, index)
{
	var hotSpot = this.entityMap.HotSpot[index];

	if(hotSpot.type == GC.ENTITY_ID_HOT_SPOT) {
		var itemIndex = this.getEntityUidHash()[hotSpot.data];
		if(itemIndex != undefined)
			this.mouseoverItem(event, itemIndex, domElem);
	}
	if(hotSpot.type == "url")
		window.status = hotSpot.data;

	domElem.style.backgroundColor = "orange";
	UiCancelEvent(event);
}

UIMap.prototype.mouseoutHotSpot = function(domElem, event, index)
{
	var hotSpot = this.entityMap.HotSpot[index];
	if(hotSpot.type == GC.ENTITY_ID_HOT_SPOT)
		this.mouseoutItem();
	if(hotSpot.type == GC.URL_HOT_SPOT)
		window.status = "";

	domElem.style.backgroundColor = (hotSpot.type == GC.ENTITY_SELECT_HOT_SPOT ? "white" : "lightBlue");
	UiCancelEvent(event);
}

UIMap.prototype.leftClickHotSpot = function(domElem, event, index)
{
	var hotSpot = this.entityMap.HotSpot[index];

	if(hotSpot.type == GC.ENTITY_ID_HOT_SPOT) {
		var itemIndex = this.getEntityUidHash()[hotSpot.data];
		if(itemIndex != undefined)
			this.leftClickItem(event, itemIndex, domElem);
	}
	if(hotSpot.type == GC.ENTITY_SELECT_HOT_SPOT) {
		if(!event.altKey) {
			var cmd = event.ctrlKey ? "Subtract" : "Add";
			this.selectEntities(cmd, hotSpot.data, cmd + " from HotSpot")
		}
		else
			this.startFlashByEntitySet(2, hotSpot.data)
	}
	if(hotSpot.type == GC.URL_HOT_SPOT)
		NewWindow(hotSpot.data)

	UiCancelEvent(event);
}

UIMap.prototype.rightClickHotSpot = function(domElem, event, index)
{
	var hotSpot = this.entityMap.HotSpot[index];
	if(hotSpot.type == GC.ENTITY_ID_HOT_SPOT)
		this.rightClickItem(event, hotSpot.data);

	UiCancelEvent(event);
}

//==========================================================================================/

function uiMapAdjustViewport()
{
	var containerDomElem =  this.getContainerDomElem();
	this.viewport.width = containerDomElem.offsetWidth;
	this.viewport.height = containerDomElem.offsetHeight;
}

function uiMapAdjustCanvas()
{
	// Center the canvas in the viewport
	var left = Math.round((this.viewport.width - this.canvas.bounds.left - this.canvas.bounds.right)/2, 0);
	var top  = Math.round((this.viewport.height - this.canvas.bounds.top - this.canvas.bounds.bottom)/2, 0);
	this.moveCanvas(left, top);
}

function uiMapAdjustTools()
{
	if(!this.tools || !this.tools.domElemId)
		return;

	if(!this.tools.domElem)
		this.tools.domElem = GetDomElem(this.tools.domElemId);

	if(!this.overview)
		this.overview = new UIMapOverview(this.tools.domElemId, 99999);

	// Ratio of canvas size to viewport size
	var ratio = this.overview.setupOverview(this, this.entityMap, this.backgroundBitmapUrlRequest);
	if(this.isOverviewEnabled && ratio > 1)
		this.overview.pushHTML(this.tools.domElem);
	else {
	 	this.tools.domElem.innerHTML = "";
		this.overview = null;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////


function uiMapMoveCanvasDone()
{
	if(!this.isMoveCanvasDoneActive && this.overview) {
		this.isMoveCanvasDoneActive = true;
		this.overview.parentCanvasMoved(this.canvas.left, this.canvas.top);
		this.isMoveCanvasDoneActive = false;
	}
}

//------------------------------------------------------------------
// PUBLIC CLASS METHODS
//------------------------------------------------------------------

function uiMapAddItem(uiMapItem)
{
	this.items[this.nItems++] = uiMapItem;
}

function uiMapSetItemVisuals(visuals)
{
	this.itemVisuals = visuals;
	for(var n=0;  n < this.items.length;  n++)
		this.items[n].setVisuals(visuals);

	this.resetHTML();
	this.pushHTML();
}

function uiMapSetEntityMap(entityMap, backgroundBitmapUrlRequest, itemHiliteUrlRequest)
{
	this.entityMap = entityMap;
	this.backgroundBitmapUrlRequest	= backgroundBitmapUrlRequest;
	this.itemHiliteUrlRequest = itemHiliteUrlRequest;

	this.items = null;
	this.resetHTML();
}


function uiMapBuildItemData()
{
	if(this.itemData.length > 0)
		return;

	var entityMap = this.entityMap;
	var colors = new Array();
	var palette = gRes.palette.colors;

	for(var n=0;  n < palette.length;  n++)
		colors[palette[n].name] = "#" + palette[n].rgbHex;

	this.isOverviewEnabled = (entityMap.pxScale > 2);

	this.itemData = new Array(entityMap.Entity.length);

	var width  = parseInt(entityMap.entityWidthPx);
	var height = parseInt(entityMap.entityHeightPx);

	var fontSize = Math.min(12, Math.round(8.5 * (height/50), 1));
	var borderWidth = Math.round(Math.max(Math.min(4,height), Math.min(10,8*height/60)), 0);

	this.itemVisuals = {
				fadeBoxStyle:	"margin: 0px; border: 1px solid " + GC.MAP_ITEM_FADE_FORE_COLOR + "; padding: 0px; position: absolute; overflow: hidden; ",
				boxStyle:		"margin: 0px; border: 1px solid black; padding: 0px; position: absolute; overflow: hidden; ",
				textStyle:		"display: block; margin: 0px; border: 0px; padding: 0px; cursor: hand; padding-left: 2px;"
								+ "font: " + fontSize + "px 'arial';",
				textCount:		3,
				width: 			width+1,
				height: 		height+1,
				border: 		{top:  borderWidth, bottom: 0},
				selectedColor:	UI_SELECT_HILITE
	};

	var dataCount = 0;

	for(var n=0;  n < entityMap.Entity.length;  n++) {
		var entity = entityMap.Entity[n];
		if(entity.fadeHide && entity.fadeHide == GC.HIDE_RENDER_CD)
			continue;

		var top  = parseInt(entity.yPx);
		var left = parseInt(entity.xPx);
		var itemWidth  = entity.widthPx ? parseInt(entity.widthPx) : width;
		var itemHeight = entity.heightPx ? parseInt(entity.heightPx) : height;

		if(itemWidth <= 0)
		    itemWidth = width;
		if(itemHeight <= 0)
		    itemHeight = height;

		this.itemData[dataCount] = {
					entity:     entity,
					uid:        entity.uid,
					itemIndex:  dataCount,
					top:        top,
					left:	    left,
					bottom:	    top  + itemHeight + 1,
					right:	    left + itemWidth + 1,
					text:       [entity.title, entity.subTitle, entity.text],
					zIndex:     (n+100),
					trimColor: colors[entity.headerColor], backgroundColor: colors[entity.bodyColor] ,
					origTrimColor: colors[entity.headerColor], origBackgroundColor: colors[entity.bodyColor],
					fontColor:  'black'
				};

		dataCount++;

		if(entity.fadeHide && entity.fadeHide == GC.FADE_RENDER_CD) {
			data = this.itemData[dataCount-1];
			data.trimColor = GC.MAP_ITEM_FADE_FORE_COLOR;
			data.backgroundColor = GC.MAP_ITEM_FADE_BACK_COLOR;
			data.origTrimColor = GC.MAP_ITEM_FADE_FORE_COLOR;
			data.origBackgroundColor = GC.MAP_ITEM_FADE_BACK_COLOR;
			data.fontColor = GC.MAP_ITEM_FADE_FORE_COLOR;
		}
	}

	if(dataCount < n)
		this.itemData = this.itemData.slice(0,dataCount);

	this.isInteractiveItems =(this.itemData.length < 1000) || !confirm("Large map.  OK to turn off interactive map items? (Cancel to enable interactive items)");

	this.setItemSelectFromString(this.mapSettings.selectedItemsString);
	this.selection.update('Initial selection settings', true);

	this.bitmap = {
		domElemId:		this.domElemId + ".bitmap",
		width:			entityMap.mapWidthPx,
		height: 		entityMap.mapHeightPx,
		scale:			entityMap.pxScale,
		isScrolling:	true
	};

	this.canvas.width  = this.bitmap.width
	this.canvas.height = this.bitmap.height;
	this.canvas.bounds  = {left: 0, top: 0, right: this.canvas.width, bottom: this.canvas.height};

	this.backgroundBitmapUrlRequest(this.bitmap.domElemId, entityMap.pxScale);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Item selection and de-selelction
////////////////////////////////////////////////////////////////////////////////////////////////////

UIMap.prototype.setUiSibbling = function(uiSibbling)
{
	this.uiSibbling = uiSibbling;
}

UIMap.prototype.setItemSelectFromSibbling = function(itemUid, isSelected)
{
    for(var itemIndex in this.itemData)
    {
        var data = this.itemData[itemIndex];
        if(data.uid == itemUid)
        {
            if(data.isSelected != isSelected) {
                this.setItemSelect(itemIndex, isSelected, true);
                this.selection.update((isSelected ? "Selected" : "Deselected") + " from List View " + data.uid );
            }
            break;
        }
    }
}

UIMap.prototype.setItemSelectAllFromSibbling = function(isSelectAll)
{
	this.setItemAllSelect(isSelectAll);
}

function uiMapSetItemAllSelect(isSelectAll)
{
	for(var n=0;  n < this.itemData.length;  n++)
		this.setItemSelect(n, isSelectAll);

	if(typeof isSelectAll != "undefined") {
		this.selection.reset();
		if(isSelectAll)
			this.selection.update("Select all items");
	}
	else
		this.selection.update("Toggle selections")
}

function uiMapSetItemSelect(itemIndex, isSelected, skipNotifySibbling)
{
	var data = this.itemData[itemIndex];
	if(isSelected == "toggle")
		isSelected = !data.isSelected;
	if(!data.isSelected == !isSelected)
		return;

	var visuals = this.itemVisuals;

	data.isSelected = isSelected;
	data.backgroundColor = isSelected ? visuals.selectedColor : data.origBackgroundColor;

	if(this.viewport.domElem) {
		var domElem = GetDomElem(this.domElemId + ".item." + itemIndex);
		if(domElem) {
			var hideTrimColor = isSelected && visuals.height-visuals.border.top < 6;
			if(hideTrimColor)
				domElem.style.borderTop = "solid black 1px";
			else
				domElem.style.borderTop = "solid " + visuals.border.top + "px " + data.trimColor;

			domElem.style.backgroundColor = data.backgroundColor;
		}
	}

	if(!skipNotifySibbling && this.uiSibbling && this.uiSibbling.setItemSelectFromSibbling)
		this.uiSibbling.setItemSelectFromSibbling(data.uid, isSelected);
}

UIMap.prototype.getItemPipeSet = function(selectedState)
{
	return "|" + this.getItemIds(selectedState, true).join("|") + "|";
}

UIMap.prototype.getSelectedItemIds = function()
{
	return this.getItemIds(true);
}

UIMap.prototype.getUnselectedItemIds = function()
{
	return this.getItemIds(false);
}

function uiMapGetItemIds(selectedState)
{
	var selectedIds = new Array(this.itemData.length);
	var selectedCount = 0;

	for(var n=0;  n < this.itemData.length;  n++) {
		var data = this.itemData[n];
		if(!data.isSelected == !selectedState)
			selectedIds[selectedCount++] = data.uid;
	}
	return selectedIds.slice(0,selectedCount);
}

//=== ITEM SET TRANSFORMATION =====================================================================/

UIMap.prototype.makeItemIdsStringFromIndexArray = function(uidSet)
{
	var itemIndex = this.makeItemIndexArrayFromUidSet(uidSet);
	var booleans = new Array(this.itemData.length)

	for(var n = 0; n < itemIndex.length; n++)
		booleans[itemIndex[n]] = true;

	return BooleansToBase64String(booleans);
}

UIMap.prototype.makeItemIndexArrayFromUidSet = function(uidSet)
{
	var hashMap = this.getEntityUidHash();
	var uids = IsArray(uidSet) ? uidSet : PipeSetToArray(uidSet);
	var indexArray = new Array(uids.length);
	var count = 0;

	for(var n = 0; n < uids.length; n++) {
		var itemIndex = hashMap[uids[n]];
		if(itemIndex != undefined)
			indexArray[count++] = itemIndex;
	}

	return indexArray.slice(0,count);
}

function uiMapGetItemIdsString(selectedState)
{
	var booleans = new Array(this.itemData.length);

	for(var n=0;  n < this.itemData.length;  n++)
		booleans[n] = (!this.itemData[n].isSelected == !selectedState);

	return BooleansToBase64String(booleans);
}

var BASE_64_CODE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
var BASE_64_BITS = [32, 16, 8, 4, 2, 1];

function uiMapSetItemSelectFromString(selectedItemsString)
{
	if(!selectedItemsString)
		return;

	for(var n=0;  n < selectedItemsString.length;  n++) {
		var bitAccum = BASE_64_CODE.indexOf(selectedItemsString.charAt(n));
		var mMax = Math.min(this.itemData.length-n*6, 6);
		for(var m=0;  m < mMax;  m++)
			this.setItemSelect(n*6+m, (bitAccum & BASE_64_BITS[m]) != 0);
	}
}

function BooleansToBase64String( booleans )
{
	// Encode booleans in a string, 6 booleans per char. Each char stores a 6 bit number.
	// Encoding of the 6 bit number isas follows: 0-25=a-z, 26-51=A-Z, 52-61=0-9, 62=+, 63=#.
	var outChars = new Array;
	var bitAccum = 0;
	var bitAccumSum = 0;

	for(var n=0;  n < booleans.length;  n++) {
		bitAccum += (booleans[n] ? BASE_64_BITS[n%6] : 0);
		if( (n%6) == 5 || n+1 == booleans.length) {
			outChars.push(BASE_64_CODE.charAt(bitAccum));
			bitAccumSum += bitAccum;
			bitAccum = 0;
		}
	}

	return bitAccumSum ? outChars.join("") : "";
}

//========================================================================
// BEGIN: UIMapOverview Class
//========================================================================

	function UIMapOverview(containerId, zIndex)
	{
	// INITIALIZE
		uiMapOverviewInit(this);
		this.containerId = containerId;
		if(zIndex)
			this.viewport.zIndex = zIndex;
	}

	function uiMapOverviewInit(object)
	{
	// INHERIT
		uiViewInit(object);

	// MEMBER VARIABLES
		object.domElemId 		= "uiMapOverview." + object.id;
		object.parent			= null;			// Main view for which this is an overview

	// INTERNAL METHODS
		object.buildItems		= uiMapOverviewBuildItems;
		object.moveCanvasDone	= uiMapOverviewMoveCanvasDone;

	// PUBLIC METHODS
		object.setupOverview	= uiMapOverviewSetupOverview
		object.parentCanvasMoved= uiMapOverviewParentCanvasMoved;

	// STATIC INITIALIZATION
	}

//------------------------------------------------------------------
// PRIVATE CLASS METHODS
//------------------------------------------------------------------

function uiMapOverviewBuildItems(tokenWidth, tokenHeight)
{
	this.addHTML('<div style="border: 2px solid red; z-index: 9999; xIndex: 9999; ');
	this.addHTML('width: ' +  this.tokenWidth + 'px; ');
	this.addHTML('height: ' + this.tokenHeight + 'px; ');
	this.addHTML( 'background: yellow; filter: alpha(opacity=40); opacity:0.4;');
	this.addHTML('"></div>');
}

//------------------------------------------------------------------
// PUBLIC CLASS METHODS
//------------------------------------------------------------------

function uiMapOverviewSetupOverview(parent, entityMap, backgroundBitmapUrlRequest)
{
	this.resetHTML();

	this.parent = parent;
	var ratio = Math.max(parent.canvas.width/parent.viewport.width, parent.canvas.height/parent.viewport.height);

	if(false) {
		var scale = Math.min(250,100*ratio)/Math.max(parent.canvas.width, parent.canvas.height);

		this.viewport.width  = Math.round(parent.canvas.width*scale,0);
		this.viewport.height = Math.round(parent.canvas.height*scale,0);
		this.scroll.delta = -25;
	}
	else {
		var factor = 250;
		factor =  Math.floor(factor/Math.max(entityMap.mapWidth, entityMap.mapHeight));
		factor = (factor < 0.5) ? 1 : Math.max(2,factor);

		this.viewport.width  = Math.ceil(factor * entityMap.mapWidth);
		this.viewport.height = Math.ceil(factor * entityMap.mapHeight);

		this.scroll.delta = -Math.ceil(parent.scroll.delta/ratio);

		this.bitmap = { domElemId:	this.domElemId + ".bitmap",
						width:			this.viewport.width,
						height:			this.viewport.height,
						scale:			factor,
						isScrolling:	false
		}
		backgroundBitmapUrlRequest(this.bitmap.domElemId, factor);
	}

	this.tokenWidth  = Math.floor(this.viewport.width*parent.viewport.width/parent.canvas.width) - 2;
	this.tokenHeight = Math.floor(this.viewport.height*parent.viewport.height/parent.canvas.height) - 2;

	this.canvas.width  = this.viewport.width;
	this.canvas.height = this.viewport.height;
	this.canvas.bounds = {left: 0, top: 0, right: this.viewport.width, bottom: this.viewport.height};

	this.toParent = {
			leftOffset:	parent.canvas.bounds.left,
			leftScale: 	-parent.canvas.width/this.viewport.width,
			topOffset: 	parent.canvas.bounds.top,
			topScale:	-parent.canvas.height/this.viewport.height
	};

	this.canvas.top  = Math.round((parent.canvas.left - this.toParent.leftOffset)/this.toParent.leftScale, 0);
	this.canvas.left = Math.round((parent.canvas.top - this.toParent.topOffset)/this.toParent.topScale, 0);

	return ratio;
}

function uiMapOverviewMoveCanvasDone()
{
	if(this.isMoveCanvasDoneActive)
		return;

	var left = Math.round((this.parent.canvas.left - this.toParent.leftOffset)/this.toParent.leftScale, 0);
	var top  = Math.round((this.parent.canvas.top - this.toParent.topOffset)/this.toParent.topScale, 0);

	// If the canvas is currently at a position that maps to the current overview location, don't do anything
	if(left == this.canvas.left && top == this.canvas.top)
		return;

	this.isMoveCanvasDoneActive = true;

	var left = this.toParent.leftOffset + Math.round(this.canvas.left*this.toParent.leftScale,0);
	var top  = this.toParent.topOffset  + Math.round(this.canvas.top*this.toParent.topScale,0);

	this.parent.moveCanvas(left, top);
	this.isMoveCanvasDoneActive = false;
}

function uiMapOverviewParentCanvasMoved(parentLeft, parentTop)
{
	var left = Math.round((parentLeft - this.toParent.leftOffset)/this.toParent.leftScale, 0);
	var top  = Math.round((parentTop - this.toParent.topOffset)/this.toParent.topScale, 0);
	this.moveCanvas(left, top);
}

//========================================================================
// BEGIN: UIMapItem Class
//========================================================================

	function UIMapItem(parent, data, visuals)
	{
	// INITIALIZE
		uiMapItemInit(this);
		this.parent = parent;
		if(data)
			this.data = data;
		if(visuals)
			this.visuals = visuals;
	}

	function uiMapItemInit(object)
	{
	// INHERIT
		uiElementInit(object);

	// MEMBER VARIABLES
		object.domElemId 		= "uiMapItem." + object.id;
		object.left 			= 0;
		object.top 				= 0;
		object.visuals 			= null;
		object.data 			= null;
		object.isSelected		= false;
		object.isActive			= true;

	// PUBLIC METHODS
		object.build 			= uiMapItemBuildHTML;

	// INTERNAL METHODS
		object.click			= uiMapItemClick;
		object.mouseover 		= uiMapItemMouseover;
		object.mouseout 		= uiMapItemMouseout;
		object.setData 			= uiMapItemSetData;
		object.setVisuals 		= uiMapItemSetVisuals;
		object.setPosition 		= uiMapItemSetPosition;
		object.computePosition 	= uiMapItemComputePosition;
	// STATIC INITIALIZATION
	}

//------------------------------------------------------------------
// PUBLIC CLASS METHODS
//------------------------------------------------------------------

function uiMapItemSetPosition(left, top)
{
	this.left = left;
	this.top  = top;
}

function uiMapItemComputePosition()
{
	this.left = parseInt(this.data.left);
	this.top  = parseInt(this.data.top);
}

function uiMapItemSetData(data)
{
	this.data = data;
	this.resetHTML();
}

function uiMapItemSetVisuals(visuals)
{
	this.visuals = visuals;
	this.resetHTML();
}

//------------------------------------------------------------------
// INTERNAL CLASS METHODS
//------------------------------------------------------------------

function uiMapItemBuildHTML()
{
	this.resetHTML();
	this.addHTML('<div id="' + this.domElemId + '" ');

	if(this.visuals.boxStyle.charAt(0) == '$')		// BEGIN style attribute
		this.addHTML('class=' + this.visuals.boxStyle.substring(1) + ' style="')
	else
		this.addHTML('style="' + this.visuals.boxStyle + '; ');

	this.addHTML('z-index:' + this.data.zIndex + '; ');
	this.addHTML('left: ' + this.left + '; ')
	this.addHTML('top: ' + this.top + '; ');
	this.addHTML('width: ' +  this.visuals.width + '; ')
	this.addHTML('height: ' + this.visuals.height + '; ');
	this.addHTML('background: ' + this.data.backgroundColor + '; ');
	if(!this.isActive)
		this.addHTML('filter: gray alpha(opacity=30); ');
	with(this.visuals.border) {
		if(top)		this.addHTML('border-top: solid ' + top + 'px ' + this.data.trimColor + '; ');
		if(bottom)	this.addHTML('border-bottom: solid ' + bottom + 'px ' + this.data.trimColor + '; ');
	}
	this.addHTML('" ');							// END of style attribute

	if(!this.visuals.ignoreOnClick) {
//		this.addHTML('onClick="GetUiElem(' + this.id + ').click(this,event)" ');
		this.addHTML('onContextMenu="GetUiElem(' + this.id + ').click(this,event);return false;" ');
		this.addHTML('onMousedown="event.cancelBubble=true; " ');
		this.addHTML('onMouseup="event.cancelBubble=true;" ');
	}
	if(!this.visuals.ignoreMouseover) {
		this.addHTML('onMouseover="GetUiElem(' + this.id + ').mouseover(this,event)" ');
		this.addHTML('onMouseout="GetUiElem(' + this.id + ').mouseout(this,event)" ');
	}
	this.addHTML('>\n');

	//Render the text block
	if(this.visuals.height-this.visuals.border.top > 6) {
		if(this.visuals.textStyle.charAt(0) == '$')
			this.addHTML('<span class=' + this.visuals.textStyle.substring(1) + '>')
		else
			this.addHTML('<span style="' + this.visuals.textStyle + '">');

		var text = this.data.text.slice(0,this.visuals.textCount);
		for(var n=0;  n < text.length;  n++) {
			if(n < 2)	// Netscape requires a <div> in order not to wrap around the text
				this.addHTML('<div style="width: 3000px; font-size: ' + (n ? "100%" : "110%") + ';"><b>' + text[n] + '</b></div>\n');
			else
				this.addHTML('<span">' + text[n] + '</span>\n');
		}
		this.addHTML('</span>\n');
	}

	this.addHTML('</div>\n');
}

////////////////////////////////////////////////////////////////////////////////////////////////////
//	Handle mouse related events
////////////////////////////////////////////////////////////////////////////////////////////////////

function uiMapItemClick(domElem, event)
{
	debugger;
}

function uiMapItemMouseover(domElem, event)
{
debugger;
	if(!this.parent || event.ctrlKey)
		return;

	var viewportBounds = GetAbsBounds(this.parent.viewport.domElem);	// absolute body coordinates
	var itemFlyover = this.parent.itemFlyover;
	var visuals = itemFlyover.visuals;

	var top  = GetAbsTop(domElem) - visuals.height - 3;
	var left = GetAbsLeft(domElem)+ Math.round((this.visuals.width-visuals.width)/2,0);

	if(left <= viewportBounds.left)
		left = viewportBounds.left + 1;
	if(left+visuals.width >= viewportBounds.right)
		left = viewportBounds.right - visuals.width - 1;

	itemFlyover.setPosition(left, top);
	itemFlyover.setData(this.data);
	domElem.style.backgroundColor = "#999";
	domElem.style.borderTopColor = "black";

	var flyoverContainer = GetDomElem("global.flyover");
	flyoverContainer.innerHTML = itemFlyover.getHTML();
	flyoverContainer.style.zIndex = FLYOVER_ZINDEX;	// Setting innerHTML seems to erase this
	flyoverContainer.style.display = "block";
	return true;
}

function uiMapItemMouseout(domElem, event)
{
debugger;
	if(!this.parent)
		return;

	var domElem = GetDomElem(this.domElemId);
	domElem.style.backgroundColor = this.data.backgroundColor;
	domElem.style.borderTopColor = this.data.trimColor;

	var flyoverContainer = GetDomElem("global.flyover");
	flyoverContainer.style.display = "none";
	return true;
}

//==========================================================================================/

function UIMapSelection(uiMap)
{
	this.uiMapSelectionInit(uiMap);
}

UIMapSelection.prototype 			= UiPrototype(UIElement);
UIMapSelection.prototype.classId	= "uiMapSelection";

/************************************************************/

UIMapSelection.prototype.uiMapSelectionInit = function(uiMap)
{
	this.uiElementInit();
	this.reset();
	this.windowName = "window";		// Default to the window where this object was created
	this.prevHistory = new Array();
	this.uiMap = uiMap;
}

/************************************************************/
UIMapSelection.prototype.reset = function()
{
	if(this.prevHistory)
		this.prevHistory.push(this.history);

	this.itemCount = 0;
	this.history = new Array();
	this.historyChanged();
}

/************************************************************/
UIMapSelection.prototype.historyChanged = function()
{
	this.resetHTML();
	this.updateTs = new Date().getTime();
}

/************************************************************/
UIMapSelection.prototype.update = function(descr, onlyIfChanged)
{
	var history = {selectedCount: 0, descr: descr, selectedItemsString: uiMap.getItemIdsString(true) };

	var itemData = this.uiMap.itemData;
	this.itemCount = itemData.length;

	for(var n=0;  n < itemData.length;  n++) {
		if(itemData[n].isSelected)
			history.selectedCount++;
	}

	var length = this.history.length;
	if(!onlyIfChanged || (length == 0  && history.selectedCount > 0) ||
			(length > 0 && this.history[length-1].selectedCount != history.selectedCount) )
		this.history.push(history);

	this.historyChanged();
}

/************************************************************/
UIMapSelection.prototype.build = function()
{
	var title = this.uiMap.mapSettings.title;

	this.addHTML('<table>');
	this.addHTML('<tr><td colspan=2"><font class="title">Selection for ' + title + '</font><td>&nbsp;&nbsp;');
	for(var n=0;  n < this.history.length;  n++) {
		this.addHTML('<tr style="margin 2px;">');
		this.addHTML('<td align="right" width="30" style="width: 30px;">&nbsp;' + (n+1) + '.&nbsp;</td>');
		this.addHTML('<td align="left">' + this.history[n].descr + '</td>');
		this.addHTML('<td align="left">&nbsp;&nbsp;(total=' + this.history[n].selectedCount + ')&nbsp;&nbsp;</td>');
		if(false && n == this.history.length-1)
			if(false)
				this.addHTML( '<td><a class="button" href="Javascript:void(0)" onClick="UiCancelEvent(event);'
								+ this.windowName + '.GetUiElem(' + this.id + ').undo()">Undo</a></td>');
			else
				this.addHTML( '<td><input type="button" value="Undo" onClick="UiCancelEvent(event);'
								+ this.windowName + '.GetUiElem(' + this.id + ').undo()"></input></td>');

		this.addHTML('</tr>');
	}
	this.addHTML('</table>');
}

/************************************************************/
UIMapSelection.prototype.undo = function()
{
	if(this.history.length == 0)
		return;

	var history = this.history.pop();

	if(this.history.length == 0) {
		if(this.prevHistory.length > 0)
			this.history = this.prevHistory.pop();
		if(this.history.length > 0)
			this.uiMap.setItemSelectFromString(this.history[this.history.length-1].selectedItemsString);
		else
			this.uiMap.setItemAllSelect(false);
	}
	else
		this.uiMap.setItemSelectFromString(this.history[this.history.length-1].selectedItemsString);

	this.historyChanged();
}

/************************************************************/
UIMapSelection.prototype.setWindowName = function(windowName)
{
	return this.windowName = windowName;
}

/************************************************************/
UIMapSelection.prototype.getItemCount = function()
{
	return this.itemCount;
}

/************************************************************/
UIMapSelection.prototype.getSelectedCount = function()
{
	var n = this.history.length;
	return (n == 0) ? 0 : this.history[n-1].selectedCount;
}

/************************************************************/
UIMapSelection.prototype.getUpdateTs = function()
{
	return this.updateTs;
}

/************************************************************/
UIMapSelection.prototype.isChangedTs = function(ts)
{
	return ts != this.updateTs;
}
