// This JavaScript code embodies the dynamic behavior of the
// international keyboards after they are rendered in a web browser.
// Steve White wrote this.  That is, © 2003 Steve White.

function
browserDOMCompliant()
{
	if( document.getElementById 
	&& document.getElementsByTagName )
	{
		return true;
	}
	return false;
}

function
doJavaScriptVersionComplaining()
{
	if( !browserDOMCompliant() )
	{
		document.write( "This keyboard requires a DOM "
			+ "compliant browser to function.  "
			+ "Your browser is not DOM compliant." );
	}
}

function
getDocumentElementByID( id )
{
	var obj;

	if( document.getElementById )
		obj = document.getElementById( id );	// DOM
	else if( document.all )
		obj = eval( "document.all." + id );	// old IE
	else
		obj = eval( "document." + id );	// old NS

	if( obj && typeof( obj ) == "undefined" )
		obj = null;
	return obj;
}

function
getStyles( obj )
{
	return obj.style ? obj.style: obj;	// IE and DOM : NS4
}

// Event-related stuff

function
stopPropagation( e )
{
	if( e.stopPropagation )
		e.stopPropagation();	// NS and DOM
	else
		window.event.cancelBubble = true; // IE
}

function
isCtrlKey( event )
{
	if( event.modifiers )
		return event.modifiers | CONTROL_MASK;	// old NS
	else
		return event.ctrlKey;	// DOM
}

function
isShiftKey( event )
{
	if( event.modifiers )
		return event.modifiers | SHIFT_MASK;	// old NS
	else
		return event.shiftKey;	// DOM
}

function
isAltKey( event )
{
	if( event.modifiers )
		return event.modifiers | ALT_MASK;	// old NS
	else
		return event.altKey;	// DOM
}

function
isMetaKey( event )
{
	if( event.modifiers )
		return event.modifiers | META_MASK;	// old NS
	else
		return event.metaKey;	// DOM
}


doJavaScriptVersionComplaining();

isMSIE = navigator.appName == "Microsoft Internet Explorer";
isOpera = navigator.appName == "Opera";
isKonqueror = navigator.appName == "Konqueror";

var caps = false;
var shift = false;
var ctrl = false;
var alt = false;
var meta = false;

function
documentToTakeKeyEvents()
{
	// NN 6+ fires KEYPRESS events with keychar == 0 on keypad
	// but KEYDOWN events with usable keychars
	// Konqueror uses this too, but doesn't seem to work.
	sensitize( document, "keydown", doKeyDown, false );
	sensitize( document, "keypress", doKeyPress, false );
	sensitize( document, "keyup", doKeyUp, false );
}

/* Compensates for new and old ways of attaching events to objects.  See
	http://www.w3.org/TR/DOM-Level-3-Events/events.html
	http://devedge-temp.mozilla.org/library/manuals/2000/javascript/1.3/reference/handlers.html
	(note later versions of the JS docs ignore Events
	
	http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/dhtml_reference_entry.asp
	http://msdn.microsoft.com/library/default.asp?url=/workshop/author/dhtml/reference/methods/attachevent.asp
*/
function
sensitize( obj, eventname, listener, something )
{
	if( obj.addEventListener )	// DOM Events
	{
//self.status = "adding listener " + eventname + " to " + obj.getAttribute( "id" );
self.status = "adding listener " + eventname + " to " + obj;
		obj.addEventListener( eventname, listener, something );
	}
	else if( obj.attachEvent )	// IE5+, Konqueror?
	{
		// MS documentation above shows this as a method, but examples only have it
		// as a sort of raw function (or function of this)
		// and the listeners don't take any arguments
		obj.attachEvent( "on" + eventname, listener );
	}
	else	// IE7
	{
		// Opera 4 doesn't support key events
		// <http://www.opera.com/opera4/specs.html>
		// this is only a few: 
		// file:///home/aux/manuals/JavaScript/ClientReference_1.3/index.htm
		// in JScript, must qualify with window to avoid conflict with event keyword
		var type = null;
		if( eventname == 'abort' )
			type = event.Abort;
		else if( eventname == 'blur' )
			type = Event.Blur;
		else if( eventname == 'change' )
			type = Event.Change;
		else if( eventname == 'click' )
			type = Event.Click;
		else if( eventname == 'dblclick' )
			type = Event.DblClick;
		else if( eventname == 'dragdrop' )
			type = Event.DragDrop;
		else if( eventname == 'error' )
			type = Event.Error;
		else if( eventname == 'focus' )
			type = Event.Focus;
		else if( eventname == 'keydown' )
			type = window.event.KeyDown;
		else if( eventname == 'keypress' )
			type = window.event.KeyPress;
		else if( eventname == 'keyup' )
			type = Event.KeyUp;
		else if( eventname == 'load' )
			type = Event.Load;
		else if( eventname == 'mousedown' )
			type = Event.MouseDown;
		else if( eventname == 'mousemove' )
			type = Event.MouseMove;
		else if( eventname == 'mouseout' )
			type = Event.MouseOut;
		else if( eventname == 'mouseover' )
			type = Event.MouseOver;
		else if( eventname == 'mouseup' )
			type = Event.MouseUp;
		else if( eventname == 'reset' )
			type = Event.Reset;
		else if( eventname == 'resize' )
			type = Event.Resize;
		else if( eventname == 'select' )
			type = Event.Select;
		else if( eventname == 'submit' )
			type = Event.Submit;
		else if( eventname == 'unload' )
			type = Event.Unload;
		if( type == null )
			alert( "no event named " + eventname );
		obj.captureEvents( type );	// NS 4
	}
}

/* see http://www.w3.org/TR/DOM-Level-2-Events/events.html */
/* need to do attachEvent (with 'onclick' etc) for IE 
see http://developer.mozilla.org/en/docs/DOM:element.addEventListener */
/* Add mouse event handlers to each key.
   This can be done in the HTML, but doing it here has the efect of keeping
   all the JavaScript together, and avoids repetition of commands for each
   key.

   Would like to have written nicer code, just looking at certain td elements,
   but bugs in various browsers prevented this.

   Konqueror bug:  make table with mult rows, call childNodes...get 1 child
   	Is because it adds a tbody element.  Is that DOM?

   Firefox and Opera bugs
   	as above, call firstChild, but nextSibling returns null
	(can't reproduce this in simple example)
*/
function
addMouseHandlersToKeys()
{
	var table = getDocumentElementByID( 'keyboard' );

	if( table != null )
	{
		var tds = table.getElementsByTagName( 'td' );

		for( var j = 0; j < tds.length; j++ )
		{
			var c = tds.item( j );

			if( c.getAttribute && c.getAttribute( 'id' ) )
			{
//				sensitize( c, "mouseover", doHover, false );

				sensitize( c, "click", doClick, false );

				sensitize( c, "mousedown", doMouseDown, false );

				sensitize( c, "mouseup", doRelease, false );

				sensitize( c, "mouseout", doRelease, false );
			}
		}
	}
}

documentToTakeKeyEvents();
addMouseHandlersToKeys();

function
activateAltKeys( active )
{
	var table = getDocumentElementByID( 'keyboard' );

	if( table != null )
	{
		var keyblks = table.getElementsByTagName( 'table' );
		var nkeyblks = keyblks.length;

		for( var j = 0; j < nkeyblks; j++ )
		{
			var cell = keyblks.item( j );
			var blocks = cell.getElementsByTagName( 'td' );
			var nblocks = blocks.length;
			for( var k = 0; k < nblocks; k++ )
			{
				var block = blocks.item( k );
				var altblk = blocks.item( 2 );
				var sty = getStyles( altblk );
				if( active )
					sty.color = 'purple';
				else
					sty.color = 'silver';
			}
		}
	}
}


// Windows
//     Backspace takes to prev. page (NS7 and IE6)
//     clicking on it is sluggish (both)
//     Alt key (left) reacts, gets stuck.  right doesn't work
//     menu key makes quote key react
//     win key has no effect.
//
//     Might want a 'clear' button.
// IE 06
//     Enter key 'dings'

function
doHover( e )
{
//	if( arguments.length == 0 )	// IE < 6 isn't DOM 2 compliant.
//		e = document.event;	// So we twirl a bit
//	var styles = getStyles( self );
//	styles.cursor = "pointer";
}

function
getKeyContaining( elt )
{
	while( elt )
	{
		var id = null;
		if( elt.getAttribute )
			id = elt.getAttribute( 'id' );

		if( id && id.substr( 0, 4 ) == 'Key-' )
		{
			return elt;
		}
		if( elt.parentElement )
			elt = elt.parentElement;	// MS only?
		else
			elt = elt.parentNode;	// DOM
	}
	return null;
}

function
doClick( e )
{
	if( arguments.length == 0 )	// IE < 6 isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit
	// Note JScrip srcElement gives the element on which the mouse was clicked,
	// even if it is not the one to which the event listener was attached.
	var targ = getEventTarget( e );
	var key = getKeyContaining( targ );
	
	if( key )
	{
		var id = key.getAttribute( 'id' );
		if( id )
		{
			var val = id.substr( 4 );
			if( !specialKeyActOnText( val, true ) )
				textKeyActOnText( val, true );
		}
	}
}

function
getEventTarget( evt )
{
	return evt.srcElement ? evt.srcElement : evt.target;	// IE : DOM
}

function
doMouseDown( e )
{
	if( arguments.length == 0 )	// IE < 6 isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit
	var targ = getEventTarget( e );

	var key = getKeyContaining( targ );

	if( key )
	{
		var id = key.getAttribute( 'id' );

		if( id )
		{
			var val = id.substr( 4 );
			displayKeyDown( val );
		}
	}
}

function
doRelease( e )
{
	if( arguments.length == 0 )	// IE < 6 isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit
	var targ = getEventTarget( e );
	var key = getKeyContaining( targ );
	
	if( key )
	{
		var id = key.getAttribute( 'id' );
		if( id )
		{
			var val = id.substr( 4 );
			displayKeyRelease( val );
		}
	}
}

function
displayKeyByNameVal( name, val )
{
	var obj = getDocumentElementByID( "Key-" + name );

	if( obj == null )
		return false;

	setKeyAppearanceDown( obj, val );
}

function
displayKeyDown( name )
{
	if( name == "Caps" )
	{
		caps = !caps;
		displayKeyByNameVal( name, caps );
	}
	else
	{
		if( name == "Alt" )
		{
			displayKeyByNameVal( "Alt-left", true );
			displayKeyByNameVal( "Alt-right", true );
			activateAltKeys( true );
		}
		else if( name == "Ctrl" )
		{
			displayKeyByNameVal( "Ctrl-left", true );
			displayKeyByNameVal( "Ctrl-right", true );
		}
		else if( name == "Shift" )
		{
			displayKeyByNameVal( "Shift-left", true );
			displayKeyByNameVal( "Shift-right", true );
		}
		else
		{
			displayKeyByNameVal( name, true );
		}
	}
}

function
displayKeyRelease( name )
{
	if( name == "Caps" )	// Caps key stays down until next press
		return;
	else
	{
		if( name == "Alt" )
		{
			displayKeyByNameVal( "Alt-left", false );
			displayKeyByNameVal( "Alt-right", false );
			activateAltKeys( false );
		}
		else if( name == "Ctrl" )
		{
			displayKeyByNameVal( "Ctrl-left", false );
			displayKeyByNameVal( "Ctrl-right", false );
		}
		else if( name == "Shift" )
		{
			displayKeyByNameVal( "Shift-left", false );
			displayKeyByNameVal( "Shift-right", false );
		}
		else
		{
			displayKeyByNameVal( name, false );
		}
	}
}

	// If want to have more control of text, use the method
	// TextArea.createTextRange().  One can also use
	// TextArea.getSelection().createRange()
	// appendData( "text" )
	// deleteData( offset, count )
	// insertData( offset "text" )
	// replaceData( offset, count, "text" )
	// substringData( offset, count )
	
var theText = new String();
var entryPoint = 0;

function
insertStringInText( str )
{
	var textDisplay = getDocumentElementByID( "display" );
	var origLength = theText.length;
	var head = theText.substr( 0, entryPoint );
	var tail = theText.substr( entryPoint, origLength - entryPoint );
	theText = head + str + tail;
	entryPoint += str.length;
	textDisplay.value = theText;
}

function
deleteCurrentPosInText()
{
	var textDisplay = getDocumentElementByID( "display" );
	var origLength = theText.length;
	var head = theText.substr( 0, entryPoint );
	var headLength = head.length;
	var tail = theText.substr( entryPoint, origLength - entryPoint );
	if( headLength > 0 )
	{
		if( headLength > 1	// DOS two-character line endings
		&& head.charCodeAt( headLength - 1 ) == 10
		&& head.charCodeAt( headLength - 2 ) == 13 )
		{
			head = head.substring( 0, headLength - 2 );
			entryPoint -= 2;
		}
		else
		{
			head = head.substring( 0, headLength - 1 );
			entryPoint -= 1;
		}
		theText = head + tail;
		textDisplay.value = theText;
	}
}

function
moveEntryPoint( pos )
{
	var length = theText.length;
	entryPoint += pos;
	entryPoint = Math.min( length, entryPoint );
	entryPoint = Math.max( 0, entryPoint );
}

function
setEntryPoint( pos )
{
	var length = theText.length;
	entryPoint = pos;
	entryPoint = Math.min( length, entryPoint );
	entryPoint = Math.max( 0, entryPoint );
}

function
textLength()
{
	return theText.length;
}

function
specialKeyActOnText( val, press )
{
	if( val == "Caps" )
	{
		return true;
	}
	else if( val.indexOf( 'Shift' ) != -1
		|| val.indexOf( 'Alt' ) != -1
		|| val.indexOf( 'Ctrl' ) != -1
		|| val == 'meta'
		|| val == 'menu')
	{
		if( val.indexOf( 'Shift' ) != -1 )
			shift = true;
		if( val.indexOf( 'Alt' ) != -1 )
			alt = true;
		return true;
	}
	else if( val == 'ArrowLeft' )
	{
		moveEntryPoint( -1 );
		return true;
	}
	else if( val == 'ArrowRight' )
	{
		moveEntryPoint( 1 );
		return true;
	}
	else if( val == 'ArrowUp' )
	{
		return true;
	}
	else if( val == 'ArrowDown' )
	{
		return true;
	}
	else if( val == 'Delete' )
	{
		deleteCurrentPosInText();
		return true;
	}
	else if( val == 'Home' )
	{
		setEntryPoint( 0 );
		return true;
	}
	else if( val == 'End' )
	{
		setEntryPoint( textLength() );
		return true;
	}
	return false;
}

function
textKeyActOnText( val, press )
{
	if( val == 'space' )
	{
		insertStringInText( ' ' );
		return true;
	}
	else if( val == 'Backspace' )
	{
		if( press )
			deleteCurrentPosInText();
		return true;
	}
	else if( val == 'Enter' )
	{
		if( press )
			insertStringInText( '\n' );
		return true;
	}
	else if( val == 'Tab' )
	{
		if( press )
			insertStringInText( '\t' );
		return true;
	}
	else if( val.length > 0 )
	{
		insertStringInText( getKeyText( val ) );
		return true;
	}
	else
		return false;
}

function
getKeyText( val )
{
	// Theory here is that the key td item contains a single table
	// with four items, of which the odd-numbered ones are the
	// characters to be typed

	// Opera 6 fails here because it thinks the data
	// is another HTMLTdElement...
	if( val.length == 0 )
		return '';

	var obj = getDocumentElementByID( 'Key-' + val );
	if( obj != null )
	{
		var block = ( caps || shift ? 1 : 3 );
		block = alt ? 2 : block;
		var node = obj.getElementsByTagName( 'td' ).item( block )
				.firstChild;
		var data = '';
		if( node )
			data = node.data;
			// permit a non-breaking space before data for spacing
		if( data.length > 1 && data.charCodeAt( 0 ) == 160 )
			return data.substring( 1 );
		else
			return data;
	}
	else if( val == 'Esc'
		|| val == 'Pause' 
		|| val == 'Insert' 
		|| val == 'NumLk' || val == 'ScrLk' 
		|| val == 'PgUp' || val == 'PgDn' 
		|| val == 'prev' || val == 'next' 
		|| val[0] == 'F' )
		return '';
	else
		alert( "Not found: 'Key-" + val + "'" );
	return '';
}

function
setKeyAppearanceDown( obj, down )
{
	var borderStyle = ( down ? "inset" : "outset" );
	var style = getStyles( obj );
	style.borderStyle = borderStyle;
}

function
doKeyDown( e )
{
	if( arguments.length == 0 )	// IE<6 isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit

	// These attributes are false unless a key is pressed while the
	// modifier is down.  The event of the modifier key itself always 
	// Also this may be IE specific
	// http://www.findmeat.org/tutorials/javascript/x290496.htm
	// meta = e.modifiers | META_MASK;

	shift = isShiftKey( e );
	ctrl = isCtrlKey( e );
	alt = isAltKey( e );
	meta = isMetaKey( e );

	var keyString = keyToString( e, false );
	var keyTaken = specialKeyActOnText( keyString, false );

// self.status = "doKeyDown  keycode:" + e.keyCode + " charCode:" + e.charCode + " which:" + e.which + "\n"
// + "shift ctrl alt " + shift + " " + ctrl + " " + alt + " " + keyString +  " " + keyTaken;
	displayKeyDown( keyString );

	if( !keyTaken )
		keyTaken = textKeyActOnText( keyString, false );

	if( keyTaken )
			stopPropagation( e );

	return !keyTaken;	/* false prevents browser from acting on key */
}

function
doKeyPress( e )
{
	// IE6 doesn't do KeyPress. It just repeats KeyDown
	var keyTaken = false;
	// onKeyPress event keyCode property is zero for alphanumeric charas,
	// non-zero for others, and results in a Unicode value for the char.
	// whereas in onKeyDown and onKeyUp, keyCode identifies the key.
	// Or at least that was the case for some version of NS.
	if( arguments.length == 0 )	// IE<6 isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit

	var keyString = keyToString( e, true );
	if( keyString.length > 0 )
	{
		keyTaken = specialKeyActOnText( keyString, true );

		 if( !keyTaken )
			keyTaken = textKeyActOnText( keyString, true );
// self.status = "doKeyPress keyCode:" + e.keyCode + " charCode:" + e.charCode +" which:" + e.which;
	}

	if( keyTaken )
		stopPropagation( e );
	return !keyTaken;	/* false prevents browser from acting on key */
}

function
doKeyUp( e )
{
	if( arguments.length == 0 )	// IE isn't DOM 2 compliant.
		e = document.event;	// So we twirl a bit

	displayKeyRelease( keyToString( e, false ) );
	stopPropagation( e );
	return false;
}

function
getKeyCode( e )
{
	// There's also ctrlLeft, altLeft, shiftLeft
	if( document.getElementById )
		return e.keyCode;	// DOM, IE4+ NS6+
	else if( e.which )
		return e.which;	// NS4	
	else
		return e.charCode; // Older IE
}

// Make string identifying keys that correspond to printable ASCII characters:
// quote, backtick, and backslash are bad things to put in JavaScript literals
// Typically there are two values for upper and lower case.  We want to
// flatten that.
//
// Always returns something.
function ASCII_keystring( key )
{
	switch( key )
	{
	case 34:	// "  note same as PgDn navigation key
	case 39:	// '
		return 'quote';
	case 58:	// :
	case 59:	// ;
		return 'semicolon';
	case 45:	// -
	case 95:	// _
		return '-';
	case 91:	// [
	case 123:	// {
		return 'lbrak';
	case 92:	// \
	case 124:	// |
		return 'backslash';
	case 93:	// ]
	case 125:	// }
		return 'rbrak';
	case 96:	// `
	case 126:	// ~
		return 'backtick';
	case 43:	// +
	case 61:	// =
		return 'equal';
	case 44:	// ,
	case 60:	// <
		return 'comma';
	case 46:	// .	same as Delete key
	case 62:	// >
		return '.';
	case 47:	// /
	case 63:	// ?
		return 'slash';
	default:
		var val = String.fromCharCode( key );
		return val.toUpperCase();
	}
}

// Returns empty string if no match found.
// ASCII_keystring and navigation_keystring should never occur in the
// same event.  They share some key c0des.
function navigation_keystring( key )
{
	switch( key )
	{
		case 33:
			return 'PgUp';
		case 34:	// same as quote
			return 'PgDn';
		case 35:
			return 'End';
		case 36:
			return 'Home';
		case 37:
			return 'ArrowLeft';
		case 38:
			return 'ArrowUp';
		case 39:
			return 'ArrowRight';
		case 40:
			return 'ArrowDown';
		case 45:
			return 'Insert';
		case 46:	// same as period .
			return 'Delete';
		default:
			return '';
	}
}

// Returns empty string if no match found.
// works on some systems (probably not MSIE)
function F_keystring( key )
{
	if( key >= 112 && key <= 122 )
		return 'F' + ( key - 111 );

	return '';
}
/**
keyPressed events are said (in the JavaScript Bible) to set the keyCode
to 0 for character keys, and to a key code for others.  This seems to
be the Mozilla behaviour.  keyDown events give a Unicode representation
for any key event.  But nobody agrees.

@param key either a keyDown character code or a keyPress keyCode
@param press if from a keyPressed event, true.  else false
*/
function
keyToString( e, press )
{
	var key = getKeyCode( e );
// Alt key: Makes event code 18.  e.altKey is false when Alt key pressed,
// true when other key pressed with Alt key down, true when Alt key released
// win key: Makes event code 0. 

// Shift key: Makes no event.  e.shiftKey true when other key pressed
// Ctrl key: Makes no event.  e.shiftKey true when other key pressed
// XHTML doesn't like =, -, [, ], ;, ,, / in id attributes

// Reason for two entries is that some keys result in different values
// if Shift is held down
	switch( key )
	{
	case 8:		// ASCII BS ('\b')
		return 'Backspace';
	case 9:		// ASCII TAB ('\t')
		return 'Tab';
	case 13:	// ASCII CR ('\r')
		return 'Enter';
	case 16:
		return 'Shift';
	case 17:
		return 'Ctrl';
	case 18:
		return 'Alt';
	case 19:
		return 'Pause';
	case 20:
		return 'Caps';
	case 27:	// ASCII ESC
		return 'Esc';
	case 32:	// ASCII SPACE
		return 'space';
	case 127:	// ASCII DEL
		return 'Delete';
	case 144:
		return 'NumLk';
	case 145:
		return 'ScrLk';
	case 146:
		return 'ScrLk';
	case 166:	// ThinkPad key
		return 'prev';
	case 167:	// ThinkPad key
		return 'next';
	}
	// if Windows: these happen in both IE6 NS7 
	switch( key )
	{
		case 192:	// NS7 & IE6
			return 'backtick';
		case 219:	// NS7 & IE6
			return 'lbrak';
		case 221:	// NS7 & IE6
			return 'rbrak';
		case 220:	// NS7 & IE6
			return 'backslash';
	}

	if( isOpera || isKonqueror )
	{
		var mod;
		// Opera defines only e.which, set to 0 for certin
		// navigation keys. but no charCode is defined
		// Konqueror defines both, but uses charCode as Opera
		// used which, and which as a copy of the keyCode (I think)
		if( typeof( e.charCode ) == "number" )
			mod = e.charCode;	// Konqueror
		else
			mod = e.which;		// Opera
		if( press )
		{
			if( mod == 0 )
			{
				str = F_keystring( key );
				if( str.length > 0 )
					return str;

				return navigation_keystring( key );
			}
			else
			{
				// This is the wierd bit.  These normal shifted
				// keys are only represented at keyPress.
				if( shift )
					switch( key )
					{
					case 33:	// !
						return '1';
					case 64:	// @
						return '2';
					case 35:	// #
						return '3';
					case 36:	// $
						return '4';
					case 37:	// %
						return '5';
					case 94:	// ^
						return '6';
					case 38:	// &
						return '7';
					case 42:	// *
						return '8';
					case 40:	// (
						return '9';
					case 41:	// )
						return '0';
					}
				else
				{
				// can't use  navigation_keystring( key );
				// e.g. 34 is shift-quote
					switch( key )
					{
					case 35:
						return 'End';
					case 36:
						return 'Home';
					}
				}
			}
		}
		else
		{
			/* On Windows, the same Opera keys shift-1 to shift-0
			give keyDown values of 49 50 51 52 53 54 55 56 57 48
			WHY?
			*/
			if( key >= 48 && key <= 57 && shift )
				return '';
			switch( key )
			{
			// Opera quote conundrum:  right arrow fires on keyPress, 
			// but then emits a keyDown 39, with no other modifiers.
			// how to distinguish this keyDown 39 from a quote key?
			case 0:
				return 'Caps';
//			case 45:	// makes no keyPress
//				return 'Insert'  // how to distinguish from -?;
//			case 46:	// makes no keyPress
//				return 'Delete';  // how to distinguish from .?
			case 36:	// handle these in keypress
				return '';
			case 33:	// !
				return '';
			case 64:	// @
				return '';
			case 35:	// #
				return '';
			case 36:	// $
				return '';
			case 37:	// %
				return '';
			case 94:	// ^
				return '';
			case 38:	// &
				return '';
			case 42:	// *
				return '';
			case 40:	// (
				return '';
			case 41:	// )
				return '';
			}

			return ASCII_keystring( key );
		}
		
	}
	else if( isMSIE )
	{
		// Horrible.  MSIE7 period key makes a keyDown event with
		// keyCode 190 which is wierd anyway, but then it follows it
		// with a keyPress with keyCode 46, which is normally Delete.
		if( !press )
		{
			var str = navigation_keystring( key );
			if( str.length > 0 )
				return str;

			str = F_keystring( key );
			if( str.length > 0 )
				return str;

			switch( key )
			{
			case 0:
				return 'meta';
			case 186:
				return 'semicolon';
			case 187:
				return 'equal';
			case 189:
				return '-';
			case 188:
				return 'comma';
			case 190:
				return '.';
			case 191:
				return 'slash';
			case 222:
				return 'quote';
			default:
				var val = String.fromCharCode( key );
				return val.toUpperCase();
			}
		}
	}
	else if( press )	// NS, Mozilla, Firefox
	{
		if( key == 0 )
			return 'meta';

		var str = navigation_keystring( key );
		if( str.length > 0 )
			return str;
		str = F_keystring( key );
		if( str.length > 0 )
			return str;

		return ASCII_keystring( key );
	}
	else
	{
		var str = F_keystring( key );
		if( str.length > 0 )
			return str;
		switch( key )
		{
		case 0:
			return 'meta';
		case 18:
			if( alt )	// FIXME global variable
				return 'Alt';
			break;
		case 109:
			return '-';	// keyDown otherwise confused with M
		case 188:	// NS & IE
			return 'comma';
		case 190:	// NS & IE
			return '.';
		case 191:	// NS & IE
			return 'slash';
		case 222:	// NS & IE
			return 'quote';
		case 39:	// do not handle ArrowRight
			return '';
		case 37:	// do not handle ArrowLeft
			return '';
		case 35:	// do not handle End
			return '';
		case 36:	// do not handle Home
			return '';
		case 45:	// do not handle Insert
			return '';
		case 46:	// do not handle Delete
			return '';
		default:
			return ASCII_keystring( key );
		}
	}
	return '';
}
/*
function
doText()
{
	//This is just a Mozilla bug that you can't save a generated
	// window 
	var display = getDocumentElementByID( "display" );
	newwin = self.open( "", document.title + ".txt" );
	newdoc = newwin.document.open( "text/plain" );
	newdoc.writeln( display.value );
	newdoc.close();
}

function
eventWithCtrlKey( e )
{
	if( document.all || document.getElementById )
	{
		return e.ctrlKey;
	}
	else
	{
		return e.modifiers & Event.CONTROL_MASK;
	}
}
*/
