
// Version of 3/16/09

// Catalogue of functions according to their static dependencies
// At the moment, this script uses nested functions, contrary to advice
// from https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Functions

// [main script]
//	   brSetBrowserTypePNjgw			[unadvertized]
//
// onload = onloadPNjgw					[unadvertized]
//	 onmousemove = brMouseMovePNjgw		[unadvertized]
//		ehMousePausePNjgw				[unadvertized]
//			getEventContext				[unadvertized]
//				cascadedStyle	[unadvertized]
//				setSourceAndDest 		[private]
//					getAnnotation		[private]
//						getAnchor		[private]
//					cacheAltStyles		[private]
//			userAbortsMousePause		[private]
//				window.onmousestay 		[public]
//			removeOldNotesAndSources	[private]
//				deActivateSource		[private]
//			activateNotesAndSourcesPNjgw[unadvertized]
//				activateSourcePNjgw		[unadvertized]
//				getPlacement			[private]
//				getSrcContainer			[private]
//					getBlockLocation	[private]
//					getNoteLocation		[private]
//					getRowLocation		[private]
//				setInitialLeftAndWidth	[private]
//				instantiateNoteSkin		[private]
//					instantiate 		[advertized public]
//				setFinalWidthAndLeft	[private]
//				setFinalTopOffset		[private]

// Low level support routines

//		cascadedStyle
		

// Advertized programatic interface
//	instantiate
//  activateLinkSource
//		getEventContext					[unadvertized]
//		activateSourcePNjgw				[unadvertized]
//  displayPageNote
//		getEventContext					[unadvertized]
//		activateNotesAndSourcesPNjgw	[unadvertized]
//	window.onmousestay

// Debugging tools and other extra stuff
//	brGetElement
//	getZIndex
//	totalLeft
//	//extractId


	var MSIE; 						// browser is MSIE
	brSetBrowserTypePNjgw();		// sets boolean flags MSIE, ie5plus
	
	// user-visible global parameters
	var linkStackMax = 7;			// max number of page notes that can be posted at once
	var minNoteWidth  = 30;			// minimum width for notes with unspecified width
	var mousePauseTolerance = 50;	// number of milliseconds before next mouse pause
	var firstMousePause = 500;		// when to start tracking the mouse
	
	// event -- happens on a mouse pause
	// This is THE data structure advertized accross the application interface
	var nEvent = new Object(); 
	nEvent.noteLink = new Object();
	nEvent.outerLink = new Object();
	var currentLink = new Object();

	// Unadvertized globals begin with "PNjgw"
	var timerIDjgw = 0;					// private to brMouseMovePNjgw()
		
	var linkStackPNjgw = new Array();	// stack of active link sources and associated page notes
	var linkStackTopPNjgw = 0; 			// current stack size
	var preserveToPNjgw = 0;			// tells portion of stack to keep when processing a mouse pause
	var newLinkSourceCountPNjgw = 0;			// used to detect nested link sources
	
	var notePoolPNjgw = new Array();	// pool of postable blank page notes
	var poolIndexPNjgw = 0;
	var defaultNoteSkinPNjgw;			// for omitted note skin, will be first note from note pool

	// z-indexes are manipulated by activateNotesAndSourcesPNjgw and removeOldNotesAndSources
	// This script sets z indexes only on link sources and posted notes 
	// Posted notes and their active link sources have the same z-index:
	//		linkStackPNjgw[i].source.style.zIndex == 
	//			linkStackPNjgw[i].note.style.zIndex = i
	// Unposted notes and inactive link sources have zIndex 0.
	// On exit from the getEventContext,
	//		0 <= linkStackTopPNjgw <= linkStackMax
	// On exit from page note building and removal, 
	//		0 <= linkStackTopPNjgw <= linkStackMax

	// Event handlers; window.onload should be empty
	var prioronloadPNjgw = window.onload;	
	if (prioronloadPNjgw  != null)
      { alert("PageNotes.js needs to be loaded before any application scripts") };
	window.onload = onloadPNjgw;
	
function onloadPNjgw(evt)
{
	// user-visible global parameters
	
	// blank page notes live at the end of the body and are initially not displayed
	allocateNotePoolPNjgw();

	// create a default skin / destination anchor for error recovery:
	defaultNoteSkinPNjgw = allocatePoolNote();
	defaultNoteSkinPNjgw.style.cssText = 
		"display:none; padding: 7; border: 1px solid #000066; " + 
			"color:#000066; background-color:#FFFBDF";
	defaultNoteSkinPNjgw.innerHTML = "<VAR> default skin </VAR>";
	defaultNoteSkinPNjgw.id = "defaultNoteSkinPNjgw";					// named after itself
	
	// Attempted external call for pages that need to handle the onload event:
	if ( prioronloadPNjgw != null) 
	{ try { prioronloadPNjgw(evt) } 
	  catch(e){ } 
	};
	
	// Defer mousemove handler to let initial page notes set by applicatio code display for awhile.
	// Have no idea why mousePauseTolerance*10 is the right time delay!
	timerIDjgw = window.setTimeout(
		setOnMouseMovePNjgw, firstMousePause );
	
	// get the initial cursor position into nEvent; 
	// it appears that in Firefox, onload does not pass an event object
	// this step appears to be unnecessary
	// getEventFieldsPNjgw(evt);
	}

function setOnMouseMovePNjgw()
	{ document.onmousemove = brMouseMovePNjgw }

function brMouseMovePNjgw(evt)
// brMouseMovePNjgw is the primitive mouse event from which mouse pauses are derived.
// While the mouse moves, it keeps clearing and resetting the call to the ehMousePausePNjgw 
// event handler.  When the mouse stops, ehMousePausePNjgw fires after mousePauseTolerance 
// is exceeded. The transient "event" object must be copied so that ehMousePausePNjgw can 
// still see it after the mousemove event has ended. 
{
	// nEvent.srcElement = event.srcElement;
	//nEvent.x = event.x;
	//nEvent.y = event.y;
	
	getEventFieldsPNjgw(evt);
	
	window.clearTimeout(timerIDjgw);
	timerIDjgw = window.setTimeout("ehMousePausePNjgw()", mousePauseTolerance);
}

function ehMousePausePNjgw()
// Called from the browser-interface event handler, brMouseOver 
{
	getEventContext(); 
	if (userAbortsMousePause()) return;
	removeOldNotesAndSources();
	switch (newLinkSourceCountPNjgw)
	{
	case 0: 
		break;
	case 1: 
		currentLink = nEvent.noteLink; 
		activateNotesAndSourcesPNjgw();
		break;
	case 2: 
		currentLink = nEvent.outerLink; 
		activateNotesAndSourcesPNjgw();
		currentLink = nEvent.noteLink;
		currentLink.source.hasOuterSource = true;  // static property
		activateNotesAndSourcesPNjgw();
		break;
	default: alert("ehMousePausePNjgw: Too many source nesting levels: " + newLinkSourceCountPNjgw)
	};	
	// IE doesn't need to tell us it's all done loading a page note!
	if (!nEvent.inHref) 
		if (!nEvent.errorFlag) { window.status = " " }

  function userAbortsMousePause()
  {
	if (window.onmousestay != null)
	{
		var rtn = true;
		//try { 
			rtn = window.onmousestay() 
		//} catch(e) 
		//{
		//	nEvent.errorFlag = true;
		//	window.status = 
		//		"Error in user event handler:  onmousestay";
		//	};
		if (!rtn) return true
	};
	return false
  }
  
  function removeOldNotesAndSources()
	{ 
		var i = 0;
		var tmpLink = null;
		for (i = linkStackTopPNjgw; i > preserveToPNjgw; i = i-1)
		{
			tmpLink = linkStackPNjgw[i];
			deActivateSource(tmpLink.source);
			if (tmpLink.note != null)
			{
				tmpLink.note.howOccurs = "";
				tmpLink.note.style.zIndex = 0;
				tmpLink.note.style.display = "none";
				if (tmpLink.anno != null) // a constructed note, as opposed to a positioned note
				{				
					deallocatePoolNote(tmpLink.note);
				}
				else
				{	// restore skin to its original style
					tmpLink.note.style.cssText = tmpLink.note.style.oldCSSText
				}
			};
			linkStackPNjgw[i] = null;
		};
		linkStackTopPNjgw = preserveToPNjgw;

		function deActivateSource(curSource)
		{
			for (attr in curSource.inactiveStyle)
			{
				curSource.style[attr] = curSource.inactiveStyle[attr]
				// curSource.style.removeAttribute(attr, 0) doesn't always work!
			};
			curSource.style.zIndex = 0;
			curSource.howOccurs = "";
		} // deactivateSource
	} // removeOldNotesAndSources
} // ehMousePausePNjgw


function getEventContext()
// just looking - no changes to the link sources or page notes yet
{
	nEvent.noteLink.source = null;
	nEvent.noteLink.annoRef = "";	// unadvertized, used only to get .anno
	nEvent.noteLink.skinRef = "";	// unadvertized, used only to get .skin
	nEvent.noteLink.anno = null;
	nEvent.noteLink.skin = null;
	nEvent.noteLink.note = null;
	nEvent.outerLink.source = null;
	nEvent.outerLink.annoRef = "";	// unadvertized, used only to get .anno
	nEvent.outerLink.skinRef = "";	// unadvertized, used only to get .skin
	nEvent.outerLink.anno = null;
	nEvent.outerLink.skin = null;
	nEvent.outerLink.note = null;
	nEvent.inHref = false;
	newLinkSourceCountPNjgw = 0;
	preserveToPNjgw = 0;
	var node = nEvent.target;
	while (true)
	// bubble up, looking for an already posted link source or page note, also any new link sources:
	{
		if (!node.tagName) break;  // have run off the side and claimed the whole document as the target
		if(node.tagName == "BODY") break;

		//if (node.getAttribute("Id") == "1") { xxx; };
		if (node.getAttribute('href')) { nEvent.inHref = true };
		if (node.howOccurs == "note")  // posted page note, happens at most once
		{
			preserveToPNjgw = parseInt(node.style.zIndex);	
			//  nEvent.noteLink.source == null, not currently over a link source
			nEvent.noteLink.note = linkStackPNjgw[preserveToPNjgw].note;
			nEvent.noteLink.anno = linkStackPNjgw[preserveToPNjgw].anno;
			nEvent.noteLink.skin = linkStackPNjgw[preserveToPNjgw].skin;
			break;
		};
		if (node.howOccurs == "source") // activated source
		{
			preserveToPNjgw = parseInt(node.style.zIndex); 
			// set link information for the benefit of the application interface:
			if (newLinkSourceCountPNjgw == 0 )  // first link source encountered
			{
				nEvent.noteLink.source = linkStackPNjgw[preserveToPNjgw].source;
				nEvent.noteLink.anno = linkStackPNjgw[preserveToPNjgw].anno;
				nEvent.noteLink.skin = linkStackPNjgw[preserveToPNjgw].skin;
				nEvent.noteLink.note = linkStackPNjgw[preserveToPNjgw].note;
			}
			else  // second encountereed, must be an outer link source
			{
				nEvent.outerLink.source = linkStackPNjgw[preserveToPNjgw].source;
				nEvent.outerLink.anno = linkStackPNjgw[preserveToPNjgw].anno;
				nEvent.outerLink.skin = linkStackPNjgw[preserveToPNjgw].skin;
				nEvent.outerLink.note = linkStackPNjgw[preserveToPNjgw].note;	
			};	
			if (cascadedStyle(node, "placement") == "") break;
			if (cascadedStyle(node, "placement").indexOf("cursor") == -1) break;  
			preserveToPNjgw -= 1;  // tear down page note so it can move with the cursor
		  	// then fall through to rebuild
		}; 
		{
//			var tmpFlag = false;
//			if (node.nref != null)  { tmpFlag = true }						// signaled by nref property
//			else 
//				if (cascadedStyle(node, "nref") != "") { tmpFlag = true }		// signaled by nref attribute
//			else if (cascadedStyle(node, "nskin") != "") { tmpFlag = true };	// signaled by nskin attribute
//			if (tmpFlag)
			var curAnnoRef = cascadedStyle(node, "nref");
			var curSkinId = cascadedStyle(node, "nskin");
			if (curAnnoRef || curSkinId)
			{
				newLinkSourceCountPNjgw += 1;
				if (newLinkSourceCountPNjgw == 1) // outer source
				{
					nEvent.noteLink.source = node;
					nEvent.noteLink.annoRef = curAnnoRef;  // if empty, the skin is the annotation
					nEvent.noteLink.skinRef = curSkinId;   // if empty, the default skin is used
				}
				else // == 2
				{
					nEvent.outerLink.source = node;
					nEvent.outerLink.annoRef = curAnnoRef;
					nEvent.outerLink.skinRef = curSkinId;
				}
			} 	
		};
		node = node.parentNode; 
	};  // end while loop
	switch (newLinkSourceCountPNjgw)
	{
	case 0:  // no new link sources found
		break;	
	case 1: // found 1 source
		currentLink = nEvent.noteLink; setSourceAndDest()
		break;	
	case 2:
		currentLink = nEvent.outerLink; setSourceAndDest();
		currentLink = nEvent.noteLink; setSourceAndDest();
		break;
	default:
		alert("getEventContext: Too many nesting levels: " + newLinkSourceCountPNjgw)
	}
	
	
	function setSourceAndDest()
 	{
	if (currentLink.source.howOccurs === undefined) cacheAltStyles(); // first pass
 	// set .skin:
    var newSkinId = currentLink.skinRef;
	if (newSkinId) 
	{
		if (newSkinId === "pending")
		{ 	currentLink.skin = null; // currentLink.anno is also null at this point
			return // note link will be used only for source activation
		}; 		
		currentLink.skin = document.getElementById(newSkinId);
	}
	else   
	{   // lost opportunity for duplicate Id checking
		currentLink.skin = defaultNoteSkinPNjgw // simple, presumably deliberate omission
	};
	if (currentLink.skin == null)
	{
		nEvent.errorFlag = true;
		window.status = 
			"The note skin, '" + newSkinId + "', was not found.";
		currentLink.skin = defaultNoteSkinPNjgw;
	};
	// set .anno:
	currentLink.anno = getAnnotation(currentLink.annoRef);
	
	function getAnnotation(annoRef)
	{
		if (annoRef == "") 
		{	// omitted nref, uninstantiated note skin will be used as the annotation
			return null
		};	
		if (annoRef == "pending")
		{	// link activation only, neither annotation nor note skin play their usual roles
			currentLink.skin = null; 
			return null
		};   	
		if (annoRef.slice(0,1) == "#") 	
		{	// typical Id selector, strip off the "#"
			var annoId = annoRef.slice(1) 
		  	var anno = document.getElementById(annoId);
		  	if (anno != null) return anno;
		  	nEvent.errorFlag = true;
		  	window.status = 'nref error: Id selector "' + annoRef + '" not found';
			return null
		}; 
		try 
		{ 
			// this = currentLink.source; // not allowed
			var tmp = annoRef.replace(/\bthis\b/, "currentLink.source");  
			var linkNref = eval(tmp) 
		}
		catch(e){
			nEvent.errorFlag = true; 
	  		window.status = 'nref error: "' + annoRef + '" is not a valid annotation reference';
	  		return null 
		};
		if (typeof(linkNref) == "string")  // annoRef evaluated to an Id selector?
			if (linkNref.slice(0,1) == "#") 	
			{	// typical local URL, strip off the "#"
				var annoId = linkNref.slice(1) 
			  	var anno = document.getElementById(annoId);
			  	if (anno != null) return anno;
			  	nEvent.errorFlag = true;
			  	window.status = 'nref error: Id selector "' + annoId + '" not found';
				return null
			};
	  	if (typeof linkNref === "object")  
			if (linkNref)
	  			if (linkNref.nodeType == 1) return linkNref;	// The general case, finally!
		nEvent.errorFlag = true; 
	  	window.status = 'nref error: "' + annoRef + '" is not a valid annotation reference';
	  	return null 
	}  // end getAnnotation	
  }  // end setSourceAndDest
  
  function cacheAltStyles()
  {	
	  var activeStyle = null;  // only do this once per link source
	  //if (currentLink.source.currentStyle == null) return; // IE bug evasion!
	  var tmp = cascadedStyle(currentLink.source, "active");  // get active style for link source
	  if (tmp != "") 
	  {
		  // Tolerate a final semicolon, quote each item, and replace semicolons with commas
		  //tmp = tmp.replace(/;\s*\}\s*/, "}");
		  //tmp = tmp.replace(/\s*;\s*/g, "', '").replace(/\s*:\s*/g, "': '"); 
		  //tmp = tmp.replace(/\{\s*/, "{'").replace(/\s*\}/, "'}");
		  // Change CSS attributes to javascript properties, without modifying value names:
		  //var j = tmp.search(/-[^,]*:/) + 1;
		  //while (j > 0) 
		  //{ 
			//  tmp = tmp.slice(0, j-1) + tmp.slice(j,j+1).toUpperCase() + tmp.slice(j+1);
			//  j = tmp.search(/-[^,]*:/) + 1;
		  //};
		  //try { eval("activeStyle = " + tmp) }
		  //catch(e) { 
			//	nEvent.errorFlag = true; 
			//	window.status = "Invalid active style designator: " + tmp;
			//  	return false
		  //};
		  activeStyle = string2Atributes(tmp);
		  var inactiveStyle = new Object();
		  for (attr in activeStyle)
			  { inactiveStyle[attr] = cascadedStyle(currentLink.source, attr) };
		  currentLink.source.inactiveStyle = inactiveStyle;
		  currentLink.source.activeStyle = activeStyle
	  }
  } // end cacheAltStyles
  
} // end getEventContext

function string2Atributes(tmp)
{
	tmp = tmp.replace(/;\s*\}\s*/, "}");
	tmp = tmp.replace(/\s*;\s*/g, "', '").replace(/\s*:\s*/g, "': '"); 
	tmp = tmp.replace(/\{\s*/, "{'").replace(/\s*\}/, "'}");
	// Change CSS attributes to javascript properties, without modifying value names:
	var j = tmp.search(/-[^,]*:/) + 1;
	while (j > 0) 
	{ 
		tmp = tmp.slice(0, j-1) + tmp.slice(j,j+1).toUpperCase() + tmp.slice(j+1);
		j = tmp.search(/-[^,]*:/) + 1;
	};
	var rtmpPNjgw;
	try {
		eval("rtmpPNjgw = " + tmp) }
	catch(e){				
		nEvent.errorFlag = true; 
		window.status = "Invalid active style designator: " + tmp;
		return false
		  };
	return rtmpPNjgw;
}


function activateNotesAndSourcesPNjgw() 
{	
	if (!activateSourcePNjgw()) return;
	if (currentLink.skin == null) return; // link is used only to activate source
	
	var placement = new Object();			// placement data from the placement style attribute
	var srcContainer = new Object();		// location of the source container
	var noteLocation = new Object();		// intended or actual location of the page note 
											// has fields left, right, top, bottom, width, height
	getPlacement();							// Decide how to interpret the placement attribute
	getSrcContainer();						// Learn where the source container is
	
	if (currentLink.anno == null)			// positioined page note
	{
		currentLink.note = currentLink.skin // uninstantiated skin - use as is
		currentLink.note.style.oldCSSText = currentLink.skin.style.cssText;
		currentLink.note.style.position = "absolute";
		currentLink.note.style.zIndex = linkStackTopPNjgw;
		// Override currentStyle.marginTop later during vertical placement:
		if (currentLink.skin.style.marginTop == "" ) 
			currentLink.skin.style.marginTop = "0px"; 
	}
	else  // constructed page note from note pool
	{
		currentLink.note = allocatePoolNote(); 
   		currentLink.note.style.cssText = currentLink.skin.style.cssText;
	};
   	currentLink.note.howOccurs = "note";

   	
	//	1.	Set container.left and container.width, don't set note left or note width yet
		setInitialLeftAndWidth();	// items 1 and 2 above//	
	//	2.	If intended style width IS NOT preset (i.et., "auto"):
	//			set left to intended value to facilate auto calculation of actual width
	//		Else width IS preset (e.g., 415px):
	//	3.	Instantiate the note skin with the annotation, if appropriate
		instantiateNoteSkin();	
	//			set left to 0 to prevent unnecessary narrowing
	//	4.	If actual width exceeds maximum width, reduce as needed
	//	5.	Reset left to intended value, adjusting as needed
	
	
	setFinalWidthAndLeft();	// items 4 and 5
	setFinalTopOffset();
	linkStackPNjgw[linkStackTopPNjgw].note = currentLink.note;
	

	// For positioned page notes, the skin's style width field will be read and then trashed 
	// So either save it for next time or restore it from last time
	
	// skin fields currently set by this script:
		//currentLink.skin.style.left = currentLink.note.style.Left; // in pixels
		//currentLink.skin.style.visibility = visible;  // shouldn't be necessary
		//currentLink.skin.style.display = "block";
		//currentLink.skin.style.position = "absolute";
		//currentLink.skin.style.width = currentLink.note.offsetWidth;
		//currentLink.skin.style.top = currentLink.note.style.top + "px";
		//currentLink.skin.style.zIndex = linkStackTopPNjgw;
		//currentLink.skin.howOccurs = "note";
	
//most of the remaining functions are local to activateNotesAndSourcesPNjgw

  function getPlacement()
  {
	placement.indent = 0;
	placement.vIndent = 0;
	placement.vLayout = "below";
	placement.srcContainerSpec = "source";
	placement.setInPlace = false;
	
	var placementArgs = cascadedStyle(currentLink.source, "placement");
	if (placementArgs != "")
	{
		placementArgs = placementArgs + " etc"; // prevent overrunning the array
		var placementArray = placementArgs.toLowerCase().split(/\s+/);
		var tmp = null;
		for (i = 0; i < placementArray.length -1; i++)
		{
			switch (placementArray[i])
			{
			case "above": 
			case "below": 
				placement.vLayout = placementArray[i];
				tmp = parseInt(placementArray[i+1], 10);
				if (tmp === tmp) { placement.vIndent = tmp; i++ }; // have a number!
				break;
			case "indent":
				tmp = parseInt(placementArray[i+1], 10);
				if (tmp === tmp) { placement.indent = tmp; i++ };
				break;
			case "source":
			case "source-line":
			case "source-row":
			case "source-block":
			case "note":
			case "note-line":
			case "cursor":
				placement.srcContainerSpec = placementArray[i];
				break;
			default: 
				nEvent.errorFlag = true; 
				window.status = "Bad note placement: '" + placementArray[i] + "'";
			}
		}
	}
  } // getPlacement

  function getSrcContainer()
  {
	var tmpLocation = new Object();	// block containing the link source
	var rowLocation = new Object();
	switch (placement.srcContainerSpec) 
	{
	default: // "source", null
		var srcc = currentLink.source.getBoundingClientRect();
		srcContainer.left = srcc.left;
		srcContainer.right = srcc.right;
		srcContainer.top = srcc.top;
		srcContainer.bottom = srcc.bottom;
		srcContainer.left -=  2; // fudge factors
		getBlockLocation();
		srcContainer.right = tmpLocation.right;
		break;
	case "source-block":
		getBlockLocation();
		srcContainer = tmpLocation;
		break;
	case "source-line":
//		srcContainer = currentLink.source.getBoundingClientRect();
		var srcc = currentLink.source.getBoundingClientRect();
		srcContainer.left = srcc.left;
		srcContainer.right = srcc.right;
		srcContainer.top = srcc.top;
		srcContainer.bottom = srcc.bottom;
		getBlockLocation();
		srcContainer.left = tmpLocation.left;
		srcContainer.right = tmpLocation.right;
		break;
	case "note":
		getNoteLocation();
		srcContainer = tmpLocation;
		break;
	case "note-line":
//		srcContainer = currentLink.source.getBoundingClientRect();
		var srcc = currentLink.source.getBoundingClientRect();
		srcContainer.left = srcc.left;
		srcContainer.right = srcc.right;
		srcContainer.top = srcc.top;
		srcContainer.bottom = srcc.bottom;
		getNoteLocation();
		srcContainer.left = tmpLocation.left;
		srcContainer.right = tmpLocation.right;
		break;
	case "source-row":
		getRowLocation();
		srcContainer = tmpLocation;
		break;
	case "cursor":
		srcContainer.left = nEvent.pageX - 2;
		srcContainer.right = srcContainer.left + 10; 	// not currently used
		srcContainer.top = nEvent.clientY - 2;			// assymetry needed for quirks mode!
		srcContainer.bottom = srcContainer.top + 22;
		break;
	}  // end get sourceContainer

	function getBlockLocation()
	{
		var tmpNode = currentLink.source;
		while (	cascadedStyle(tmpNode, "display") !== "block" ) 
				{ tmpNode = tmpNode.parentNode };
//		tmpLocation = tmpNode.getBoundingClientRect();
		var tmpl = tmpNode.getBoundingClientRect();
		tmpLocation.left = tmpl.left;
		tmpLocation.right = tmpl.right;
		tmpLocation.top = tmpl.top;
		tmpLocation.bottom = tmpl.bottom;
		tmpLocation.bottom -= 1;	tmpLocation.top -= 1;	// fudge factors
		tmpLocation.left -=  2; tmpLocation.right -= 2;	// more fudge factors!
		return;
	} // end getBlockLocation
	
	function getNoteLocation()
	{
		getBlockLocation(); /* debug */
		var nd = currentLink.source;
		tmpLocation.top = 0;
		tmpLocation.left = 0;
		tmpLocation.right = 0;
		tmpLocation.bottom = 0;
		if (nd == null) { 
			nEvent.errorFlag = true; window.status = "getNoteLocation: Null link source" 
		};
		while ((nd.howOccurs !== "note") )
		{
			if (nd.tagName == "BODY")
			{
				getBlockLocation();
				return
			};
			nd = nd.parentNode; 
		};
//		tmpLocation = nd.getBoundingClientRect();
		var tmpl = nd.getBoundingClientRect();
		tmpLocation.left = tmpl.left;
		tmpLocation.right = tmpl.right;
		tmpLocation.top = tmpl.top;
		tmpLocation.bottom = tmpl.bottom;
		tmpLocation.bottom -= 3;	// fudge factor
		if (MSIE)
		{
			tmpLocation.left -=  2; tmpLocation.right -= 2;  tmpLocation.top -= 1; // MSIE 7 fudge factors
		}
		else
		{
			tmpLocation.top += 1
		};
		return;
	} // end getNoteLocation	
	
	function getRowLocation()
	{
		getBlockLocation(); /* debug */
		var nd = currentLink.source;
		tmpLocation.top = 0;
		tmpLocation.left = 0;
		tmpLocation.right = 0;
		tmpLocation.bottom = 0;
		if (nd == null) { 
			nEvent.errorFlag = true; window.status = "getNoteLocation: Null link source" 
		};
		while ((nd.tagName != "TR"))
		{
			if (nd.tagName == "BODY")
			{
				getBlockLocation();
				//noteLocation = tmpLocation;
				return
			};
			nd = nd.parentNode; 
		};
//		tmpLocation = nd.getBoundingClientRect();
		var tmpl = nd.getBoundingClientRect();
		tmpLocation.left = tmpl.left;
		tmpLocation.right = tmpl.right;
		tmpLocation.top = tmpl.top;
		tmpLocation.bottom = tmpl.bottom;
		tmpLocation.bottom -= 2; tmpLocation.top -= 1;	// fudge factors
		tmpLocation.left -=  2; tmpLocation.right -= 2;		// more fudge factors!
		return;
	} // end getRowLocation
  }  // end getSrcContainer

  
  function setInitialLeftAndWidth()
  // Set left offset and page note width
  {
	noteLocation.left = srcContainer.left + placement.indent;
	var marginLeft = cascadedStyle(currentLink.skin, "margin-left");
	if (marginLeft !== "auto" )
	{
		var lm = parseInt(marginLeft);
		currentLink.note.style.marginLeft = "0px";
		noteLocation.left += lm;
	};
	
	var maxWidth = 0;
	var	skinWidth = cascadedStyle(currentLink.skin, "width");
	if ( skinWidth.slice(-1) == "%")
	{
		var maxWidthPrcnt = parseInt("0" + skinWidth, 10);
		maxWidth = defaultMaxWidth() * maxWidthPrcnt/100;
	}
	else if (skinWidth == "auto")
	{
		maxWidth = defaultMaxWidth();
	}
	else  // fixed width
	{
		maxWidth = parseInt(skinWidth);
	};
	noteLocation.width = maxWidth;
  } // end setInitialLeftAndWidth

  function defaultMaxWidth()
  {
	var wdth = srcContainer.right - srcContainer.left;
	if (wdth < minNoteWidth ) { wdth = minNoteWidth  };
	return wdth
  } // end defaultMaxWidth

  function instantiateNoteSkin()
  {    
	if (currentLink.anno != null)	
	{	// constructed page note
		instantiate(currentLink.note, currentLink.skin, currentLink.anno)
	};
	currentLink.note.style.display = "block";
	currentLink.note.style.position = "absolute";
	currentLink.note.style.zIndex = linkStackTopPNjgw;
  } // instantiateNoteSkin

  function setFinalWidthAndLeft()
  {
	var actualWidth = currentLink.note.offsetWidth;
	var bodyMarginRight = parseInt(cascadedStyle(document.body, "margin-right"));
	
	if (actualWidth > noteLocation.width)
	{	// forced narrowing of page note
		currentLink.note.style.width = noteLocation.width.toString(10);
		actualWidth = currentLink.note.offsetWidth; 
	}
	else if (currentLink.note.offsetWidth != 0) 
	{	// prevent spurious narrowing due to, e.g., padding errors
		currentLink.note.style.width = actualWidth // coerced to a string
	}
	else
	{
		nEvent.errorFlag = true;
		window.status = "Error: '" + currentLink.note.id + "' is a hidden, uninstantiated note skin!"
	};
	
    if (currentLink.skin.style.textAlign == "right")
    // "right" has to be reinterpred because the page note has been narrowed to fit its content
	{
		noteLocation.right = srcContainer.right;
		noteLocation.left = noteLocation.right - actualWidth;
	}
	else if (currentLink.skin.style.textAlign == "center")
    // "center" has to be reinterpred because the page note has been narrowed to fit its content
	{
		var leftOverSpace = defaultMaxWidth() - actualWidth;
		noteLocation.left += leftOverSpace/2;
	}
	else  // extra precaution to make sure we're on the page at least - unneeded?
	{
		noteLocation.right = noteLocation.left + actualWidth;
		if (noteLocation.right > document.body.clientWidth  - bodyMarginRight)
		{
			noteLocation.right = document.body.clientWidth - bodyMarginRight; 
			noteLocation.left = noteLocation.right -  actualWidth;
		}
	};
	if (noteLocation.left  < bodyMarginRight) {noteLocation.left = bodyMarginRight};
	currentLink.note.style.left = noteLocation.left + "px";	// final horizontal placement
  } // setFinalWidthAndLeft

  function setFinalTopOffset()
  {
	// Explicitly include the top margin in the calculations, so that it doesn't
	// prevent the page note from flowing off the top or bottom of the screen
	var marginTop = cascadedStyle(currentLink.skin, "margin-top");
	if (marginTop == "auto" ) marginTop = "0px"; 
	var tm = parseInt(marginTop, 10);
	placement.vIndent += tm;
	currentLink.note.style.marginTop = "0px";

	var verticalOffset;
	var iScreenTop = scrollTop() -2;
	if (window.innerHeight) 
		var tmpHeight = window.innerHeight // Firefox
	else
		var tmpHeight = document.documentElement.offsetHeight; // Internet Explorer
	var iScreenBottom = tmpHeight  + scrollTop();
//	var iScreenBottom = document.body.offsetHeight  + scrollTop() -6; // worked for IE quirks mode
	noteLocation.height = currentLink.note.offsetHeight;
	if (placement.vLayout == "above")
	{	// try placing above
		verticalOffset = scrollTop() - placement.vIndent;
		noteLocation.bottom = srcContainer.top + verticalOffset;	
		noteLocation.top = noteLocation.bottom - noteLocation.height;
		// prevent page note from going off top of screen:
		if (noteLocation.top < iScreenTop)  
		{	// place page note below term being defined, if possible:
			verticalOffset = scrollTop() + placement.vIndent;
			noteLocation.top = srcContainer.bottom + verticalOffset;	
			noteLocation.bottom = noteLocation.top + noteLocation.height;
			if (noteLocation.bottom > iScreenBottom)
			{	// prevent page note from flowing off bottom, if possible
				noteLocation.bottom = iScreenBottom - 4;
				noteLocation.top = noteLocation.bottom - noteLocation.height;
			};
			if (noteLocation.top < iScreenTop) 
			{	// last resort: make sure first part is readable
				noteLocation.top = iScreenTop + 4;
				noteLocation.bottom = noteLocation.top + noteLocation.height;
			}
		}
	}
	else  // placement.vLayout = "below" 
	{
		verticalOffset = scrollTop() + placement.vIndent;
		noteLocation.top = srcContainer.bottom + verticalOffset;	
		noteLocation.bottom = noteLocation.top + noteLocation.height;
		// prevent page note from going off bottom of screen:

		if (noteLocation.bottom > iScreenBottom)  
		{	// place page note above term being annotated, if possible:
			verticalOffset = scrollTop() - placement.vIndent;
			noteLocation.bottom = srcContainer.top + verticalOffset;
			noteLocation.top = noteLocation.bottom - noteLocation.height;
			if (noteLocation.top < iScreenTop)
			{	// last resort: make sure page note doesn't float above top of screen
				noteLocation.top = iScreenTop + 4;
				noteLocation.bottom = noteLocation.top + noteLocation.height;
			}
		}
	};
	currentLink.note.style.top = noteLocation.top + "px";
  } // end setFinalTopOffset

} // end of activateNotesAndSourcesPNjgw

function activateSourcePNjgw()
{	  
	if (currentLink == null) { return false};	
	if (currentLink.source == null) { return false};
	if (currentLink.source.howOccurs == "source")  { return false};  // already activated 
	if (linkStackTopPNjgw >= linkStackMax) { return false};
	linkStackTopPNjgw = linkStackTopPNjgw + 1;
	currentLink.source.style.zIndex = linkStackTopPNjgw;
	currentLink.source.howOccurs = "source";
	linkStackPNjgw[linkStackTopPNjgw] = new Object();
	linkStackPNjgw[linkStackTopPNjgw].source = currentLink.source;
	linkStackPNjgw[linkStackTopPNjgw].anno = currentLink.anno;
	linkStackPNjgw[linkStackTopPNjgw].skin = currentLink.skin;
	var activeStyle = currentLink.source.activeStyle;
	for (attr in activeStyle) 
		{ currentLink.source.style[attr] = activeStyle[attr] };
	return true
}

function instantiate(node, skin, annotation)
{
	// Assumptions: tags are in upper case, no carriage returns in .outerHTML.
	// Methodology: both skin and content are copied; changes made to the page-note node
	// do not propagate back to the skin or content, which must be separately updated.
	var inStr = skin.innerHTML;
	var varCount = skin.getElementsByTagName("var").length;  
	var actualParam; 
	var outStr;
	if (typeof(annotation) != "object")  // string annotations not supported
	{
		nEvent.errorFlag = true;
		window.status = 
			"instantiate: " + typeof(annotation) + " string annotations are not supported";
		node.innerHTML = inStr;
		return;
	};
	if (varCount == 1) // single-variable skin
	{  
		actualParam = annotation.innerHTML;
		outStr = inStr.replace(/<var>[\s\S]*<\/var>/i, actualParam);
		node.innerHTML = outStr;
		return;
	};
	if (varCount == 0)
	{
		node.innerHTML = inStr; 
		return
	};
	// multi-variable skin
	var childCount = annotation.childNodes.length;
	var actuals = new Array();
	var actualsCount = 0;
	var tmpNode;
	for (var i=0; i < childCount; i++)
	{
		tmpNode = annotation.childNodes[i];
		if (tmpNode.nodeType == 1 ) // element node
		{
			actuals[actualsCount] = tmpNode; actualsCount++;
		}
	};

	var i;
	var j;
	var k;
	var variable;
	var paramIndex;
	var piTmp;
	outStr = "";
	for (i = 0;  i < varCount; i++)
	{
		j = inStr.search(/<var>/i);
		k = inStr.search(/<\/var>/i);
		outStr += inStr.slice(0, j);
		variable = inStr.slice(j + 5, k);  // get the variable name
		inStr = inStr.slice(k + 6);
		paramIndex = i;
		piTmp = parseInt(variable);
		if (!isNaN(piTmp)) paramIndex = piTmp;
		if (paramIndex >= actualsCount) // includes the case where actualsCount == 0
		{
			nEvent.errorFlag = true;
			window.status = 
				"instantiate: missing actual parameter for " + skin.id + " skin";
			node.innerHTML = skin.innerHTML;
			return;
		};
		actualParam = outerHTML(actuals[paramIndex]);
		outStr += actualParam
	};
	outStr += inStr;
	node.innerHTML = outStr;
	return;

	function outerHTML(node)
	{
		if (MSIE) return node.outerHTML;	
		var rslt = "<" + node.tagName + attributes(node) + ">";
		rslt += node.innerHTML + "</" + node.tagName + ">";
		return rslt
	
		function attributes(node)  // only works properly in Firefox
		{
			var attrs = "";
			if (node.hasAttributes())
			{
				for (var i=node.attributes.length-1; i>=0; i--)  // note reverse order
				{
					var nam = node.attributes[i].name;
					var val = node.attributes[i].value;
					attrs = attrs + " " + nam + "=\"" + val + "\""
				}
			};
			return attrs
		} // end attributes
	} // end outerHTML
} // end instantiate


function activateLinkSource(srcId)
{
	if (typeof(srcId) != "string")
	{
		nEvent.errorFlag = true;
		window.status = "displayPageNote:  object Id argument required";		
		activateLinkSource = false;
		return
	};
	var src = document.getElementById(srcId);
	if (src == null)
	{
		nEvent.errorFlag = true;
		window.status = "displayPageNote:  undefined object Id";		
		activateLinkSource = false;
		return
	};
	nEvent.target = src; 
	getEventContext();
	activateSourcePNjgw();
}

function displayPageNote(srcId)
{
	if (typeof(srcId) != "string")
	{
		nEvent.errorFlag = true;
		window.status = "displayPageNote:  object Id argument required";		
		activateLinkSource = false;
		return
	};
	var src = document.getElementById(srcId);
	if (src == null)
	{
		nEvent.errorFlag = true;
		window.status = "displayPageNote:  undefined object Id";		
		activateLinkSource = false;
		return
	};
	nEvent.target = src;
	getEventContext();
	activateNotesAndSourcesPNjgw()
	
}

//	function getSelectorId(anchr)
//	{
//		var anno = anchr.getAttribute('href');
//		if (anno.slice(0,1) == "#") return anno;
//		// Perhaps we have a local href with a nonlocal name ... 
//		var re = "^" + document.location.href.replace(/#.*/, "");
//		var docHref = new RegExp(re);
//		var selId = anchr.replace(docHref, "");
//		if (selId.slice(0,1) == "#") return selId; 	
//		return "";
//	}

	

// //////////////////////////////////////////////////////////////////////////////////
// Browser-dependent support functions

function cascadedStyle(elem, attr)
// This function is a essentially a decamelized variant of MSIE's currentStyle property. 
// This function tests for order of precedence per MSIE's currentStyle definition, 
// which may conflict with user direction due to ignorance or carelessness.
// The current compromise is to honor user direction but complain about it.
{
	if (elem.currentStyle)  // MSIE
	{
		var rtnAttr = elem.currentStyle[camelize(attr)];
		var rtnProp = elem.getAttribute(attr)
	}
	else
	{	
		var calcStyle = document.defaultView.getComputedStyle(elem, null);
		var rtnAttr = calcStyle.getPropertyValue(attr)
		var rtnProp = elem.getAttribute(attr)
	};
	if (rtnAttr && rtnProp && (rtnAttr != rtnProp))
	{	
		var msg = "Error.  Replace property '" + attr + "=\"" + rtnProp + "\" '  with inline style declaration";
		if (elem.className)
			msg = "Class '" + elem.className + "' conflicts with property '" + attr + "=\"" + rtnProp + "\" '";
		nEvent.errorFlag = true; 
		window.status = msg;
	};
	if (rtnProp) return rtnProp; // violates standards but not without complaint
	if (rtnAttr) return rtnAttr;
	return "";

	function camelize(prop)
	{
		var tmp = prop;
		j = tmp.search(/-/) + 1;
		if (j > 0)
		{
			tmp = tmp.slice(0, j-1) + tmp.slice(j,j+1).toUpperCase() + tmp.slice(j+1)
		};
		return tmp
	}  // end camalize
	
} // end cascadedStyle

function brSetBrowserTypePNjgw()
{ 
	// Decide browser version
	var appName = navigator.appName;
	if (appName == "Microsoft Internet Explorer")
	{
		MSIE = true;
		var browser = navigator.appVersion.split(/;\s*/)[1];
		var browserVersion = browser.replace(/.*\s+/, "");
		var browserName = browser.replace(/\s+.*/, "");
		if (browserName == "MSIE" & parseFloat(browserVersion) >= 5.5) 
		{ ie5plus = true;  }
	}
	else
	{ 
		MSIE = false;
		ie5plus = false;
	}
}

function getEventFieldsPNjgw(e) 
// Provide browser-independent presentation of browser-dependent info 
{ 
	if (e) 
	{
		nEvent.target = e.target;
		// nEvent.srcElement = e.target;
	}
	else // Internet Explorer
	{
		e = window.event; 
		nEvent.target = e.srcElement;
		// nEvent.srcElement = e.srcElement;
	};
	// works on IE6, FF, Moz, Opera7:
	nEvent.clientX = e.clientX; 
	nEvent.clientY = e.clientY;
	if (e.pageX )
	{   // works on FF, Moz, Opera7
		nEvent.pageX = e.pageX;
		nEvent.pageY = e.pageY;
	}
	else // assumes (e.clientX && e.clientY)
	{
		 nEvent.pageX = e.clientX + scrollLeft();
		 nEvent.pageY = e.clientY + scrollTop();
	}
}

function scrollTop()
{   // In standards mode, Firefox responds to the first term, and MSIE to the second term
	// Based on info at http://www.quirksmode.org/js/doctypes.html,
	// and at http://dunnbypaul.net/js_mouse/
	return document.body.scrollTop + document.documentElement.scrollTop
}


// This concept needs to be integrated into the code!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
function scrollLeft()
{   // In standards mode, Firefox responds to the first term, and MSIE to the second term
	// Based on info at http://www.quirksmode.org/js/doctypes.html,
	// and at http://dunnbypaul.net/js_mouse/
	return document.body.scrollLeft + document.documentElement.scrollLeft
}

// ///////////////////////////////////////////////////////////////////////////////////////
//
// pool abstraction
// Page notes have to be part of the document body in order to work right.
// Consequently, they don't automatically garbage collect when you quit working with them
// This necessitates explicit allocation and deallocation of page note nodes

function allocateNotePoolPNjgw()
{
	var oDiv;
	for (i = 0; i <= linkStackMax; i++)
	{
		oDiv=document.createElement("div");
		document.body.appendChild(oDiv);  // could use ownerDocument instead and support for IE5
		oDiv.innerText = " " /* unfortunately, this is sometimes visible */;
		oDiv.howOccurs = "note";
		oDiv.style.display = "none";
		notePoolPNjgw[i] = oDiv
	}
};

function allocatePoolNote()
{
	//pooled notes are stored clean
	if (poolIndexPNjgw > 7) 
	{	
		ErrorFlag = true;
		window.status = "Page Note Pool is emty"
		return false
	};
	var node = notePoolPNjgw[poolIndexPNjgw]
	poolIndexPNjgw += 1;
	return node;
}

function deallocatePoolNote(note)
{
	poolIndexPNjgw -= 1;
//	note.Style.ccstext = "display:none";  // like cleaning rental boats on return
//	note.innerHTML = "";
	if (poolIndexPNjgw < 0) 
	{
		errorFlag = true;
		window.status = "Page pool overflowed";
		return false
	};
	note.style.cssText = "display:none";  // erase all note-dependent attributes
	note.innerHTML = "" 
	notePoolPNjgw[poolIndexPNjgw] = note; // redundant
	note = null;  // not really necessary except for clarity and for debugging
}



// /////////////////////////////////////////////////////////////////////////////////////////////
// Other extra stuff
//	extractId
//	getZIndex
//	totalLeft


//function extractId(node)
//{
//	var st = node.id;
//	if (st === "") {  st = node.innerText;  } // the typical case
//	st = st.toLowerCase()
//	st = st.replace(/(^\s*)|(\s*$)/g, "");  // remove leading and trailing white space
//	st = st.replace(/\s+/g, "");    // remove embeded white space
//	return "#" + st;
//}

function getZIndex(nd) // not needed?
{
	var lnd = nd;
	while (lnd.style.zIndex == "auto") 
	{
		lnd = lnd.parentNode;
		if (lnd.tagName == "BODY") { return 0 }
	};
	return lnd.style.zIndex;
}

function totalLeft(nd)
{
	var lft = 0; 
	leftCount = 0;
	//if (nd == null) { return "NaN" };
	if (nd == null) { return 0 };
	while (nd.tagName !== "BODY") 
	{
		leftCount += 1;
		lft += nd.offsetLeft; 
		nd = nd.offsetParent; // about twice as efficient as nd.parentElement
	};
	return lft
}

