/**
 *
 * KeyManager - singleton: Global document-level keystroke manager
 *
 */
var KeyManager = new function()
{
	this.nHotKeysLevel = 0;
	this.nHotKeysLevelMax = 3;
	this.nHelperOffsetX = 8;
	this.nHelperOffsetY = -11;
	this.nHelperOffsetBelowY = 16;
	this.nHelperOffsetLeftX = -30;
	this.nHelperOffsetLeftY = 4;
	this.rgReleaseKeys = ['textarea', 'input', 'select-one', 'select', 'text', 'file'];
	this.sSnippetKey = '';
	this.oPopupBrowser = null;
	this.oMenuBrowser = null;
	this.oGridBrowser = null;
	this.bBrowsingGrid = false;

	this.fxActivationKey = function(e, s) { return ((e.altKey || e.ctrlKey) && !(window.event && window.event.altKey) && !(e.altKey && e.ctrlKey) && (';' == s || ',' == s)); };
	
	this.rgHotKeys = [ 
		//level 1 hotkeys
		{cKey:this.sSnippetKey, id:"Menu_Snippets", bBrowse:true, nLevel:1}, //leave this as the index-zero element so we can swap out the key
		{cKey:"a", id:"assign", nLevel:1},
		{cKey:"a", id:"assign0", pos:"below", nLevel:1},
		{cKey:"a", id:"assign1", nLevel:1},
		{cKey:"a", id:"idApproveThread", nLevel:1},
		{cKey:"b", id:"idBugFromThread", nLevel:1},
		{cKey:"c", id:"Button_CancelEdit", nLevel:1},
		{cKey:"c", id:"Button_CancelEmail", nLevel:1},
		{cKey:"c", id:"Button_Cancel", nLevel:1},
		{cKey:"d", id:"Menu_Discuss", bBrowse:true, bMenu:true, nLevel:1},
		{cKey:"e", id:"editClosed", nLevel:1},
		{cKey:"e", id:"editClosed0", pos:"below", nLevel:1},
		{cKey:"e", id:"editClosed1", nLevel:1},
		{cKey:"e", id:"edit", nLevel:1},
		{cKey:"e", id:"edit0", pos:"below", nLevel:1},
		{cKey:"e", id:"edit1", nLevel:1},
		{cKey:"e", id:"idThisDiscussionGroup", nLevel:1},
		{cKey:"f", id:"Menu_Filter", bBrowse:true, bMenu:true, nLevel:1},
		{cKey:"g", id:"Menu_LogInOut", bBrowse:true, nLevel:1},
		{cKey:"h", id:"FBHome", pos:"center", nLevel:1},
		{cKey:"i", id:"email", nLevel:1},
		{cKey:"i", id:"email0", pos:"below", nLevel:1},
		{cKey:"i", id:"email1", nLevel:1},
		{cKey:"i", id:"email1", nLevel:1},
		{cKey:"i", id:"idThreadInfo", nLevel:1},
		{cKey:"j", id:"browseGrid", pos:"center", nLevel:1, bNeverFocus:true},
		{cKey:"k", id:"idBEOrderToggleLink", nLevel:1},
		{cKey:"l", id:"Menu_List", nLevelAnchor:1, levelAnchorPos:'left', idLevelContainer:'navBarRow2', bBrowse:true, nLevel:1},
		{cKey:"m", id:"Menu_Email", bBrowse:true, nLevel:1},
		{cKey:"n", id:"Menu_New", bBrowse:true, nLevel:1},
		{cKey:"o", id:"Button_SendEmail", nLevel:1},
		{cKey:"o", id:"Button_OKEdit", nLevel:1},
		{cKey:"o", id:"Button_OK", nLevel:1},
		{cKey:"p", id:"Menu_Prefs", bBrowse:true, nLevel:1},
		{cKey:"q", id:"Button_SendAndCloseEmail", nLevel:1},
		{cKey:"r", id:"resolve", nLevel:1},
		{cKey:"r", id:"resolve0", pos:"below", nLevel:1},
		{cKey:"r", id:"resolve1", nLevel:1},
		{cKey:"r", id:"idOtherTopics", nLevel:1},
		{cKey:"s", id:"searchFor", pos:"center", nLevel:1},
		{cKey:"t", id:"reactivate", nLevel:1},
		{cKey:"t", id:"reactivate0", pos:"below", nLevel:1},
		{cKey:"t", id:"reactivate1", nLevel:1},
		{cKey:"t", id:"idNewTopic", nLevel:1},
		{cKey:"u", id:"reopen", nLevel:1},
		{cKey:"u", id:"reopen0", pos:"below", nLevel:1},
		{cKey:"u", id:"reopen1", nLevel:1},
		{cKey:"u", id:"idUndeleteThread", nLevel:1},
		{cKey:"v", id:"move", nLevel:1},
		{cKey:"v", id:"idReviewHeldThreads", nLevel:1},
		{cKey:"v", id:"idEditReleaseNotes", nLevel:1},
		{cKey:"w", id:"forward", nLevel:1},
		{cKey:"w", id:"forward0", pos:"below", nLevel:1},
		{cKey:"w", id:"forward1", nLevel:1},
		{cKey:"w", id:"idReviewDeletedThreads", nLevel:1},
		{cKey:"x", id:"close", nLevel:1},
		{cKey:"x", id:"close0", pos:"below", nLevel:1},
		{cKey:"x", id:"close1", nLevel:1},
		{cKey:"x", id:"idDeleteThread", nLevel:1},
		{cKey:"x", id:"idKeepThreadDeleted", nLevel:1},
		{cKey:"y", id:"reply", nLevel:1},
		{cKey:"y", id:"reply0", pos:"below", nLevel:1},
		{cKey:"y", id:"reply1", nLevel:1},
		{cKey:"y", id:"idReplyToTopic", nLevel:1},
		{cKey:"z", id:"remind", nLevel:1},
		{cKey:"z", id:"linkSubscribe", nLevel:1},
		{cKey:"z", id:"linkUnsubscribe", nLevel:1},
		{cKey:"?", id:"Menu_Help", pos:"center", nLevel:1},
		{cKey:"!", id:"spam", nLevel:1},
		{cKey:"!", id:"spam0", pos:"below", nLevel:1},
		{cKey:"!", id:"spam1", nLevel:1},
		{cKey:"/", id:"discussSearchFor", nLevel:1},
		{cKey:"[", id:"viewPrevious", nLevel:1},
		{cKey:"]", id:"viewNext", nLevel:1},
		{cKey:"+", id:"idAttachFile", nLevel:1},
		{cKey:"+", id:"idOlderTopics", nLevel:1},

		//level 2 hotkeys
		{cKey:"c", id:"Menu_Clients", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"d", id:"Menu_Departments", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"l", id:"Menu_Licenses", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"m", id:"Menu_Mailboxes", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"p", id:"Menu_Projects", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"r", id:"Menu_Priorities", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"s", id:"Menu_Site", pos:"below", bBrowse:true, nLevel:2},
		{cKey:"u", id:"Menu_Users", pos:"below", nLevelAnchor:2, levelAnchorPos:'left', idLevelContainer:'navBarRow3', bBrowse:true, nLevel:2},

		//level 3 hotkeys
		{cKey:"a", id:"idFilterLinkPopPersonAssignedTo", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"b", id:"idFilterLinkPopOpenClosed", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"c", id:"mainColumnPopupLink", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"d", id:"idFilterLinkPopResolvedInLast", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"e", id:"idFilterLinkPopDueInNext", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"f", id:"idFilterOptFieldsPop", bBrowse:true, bMenu:true, nLevelAnchor:3, idLevelContainer:'idFilterDescription', levelanchorpos:'left', nLevel:3},
		{cKey:"f", id:"idFilterLinkPopRefine", bBrowse:true, bMenu:true, nLevelAnchor:3, idLevelContainer:'idFilterDescription', nLevel:3},
		{cKey:"g", id:"idSwitchGridListLink", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"h", id:"idFilterLinkPopSubscribedBugs", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"i", id:"idFilterLinkPopOpenInLast", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"k", id:"idFilterLinkPopClosedInLast", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"l", id:"idFilterLinkPopClient", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"l", id:"idFilterLinkPopDepartment", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"m", id:"idFilterLinkPopMissingEstimate", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"n", id:"idFilterLinkPopMaxrecords", bBrowse:true, bMenu:true, bMenu:true, nLevel:3},
		{cKey:"o", id:"idFilterLinkPopPersonOpenedBy", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"o", id:"idFilterOptOpenAllPop", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"p", id:"idFilterLinkPopProject", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"q", id:"idFilterLinkPopPriority", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"r", id:"idFilterLinkPopArea", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"s", id:"idFilterLinkPopStatus", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"s", id:"idFilterOptTermPop", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"t", id:"idFilterLinkPopCategory", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"u", id:"idFilterLinkPopCustom1", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"v", id:"idFilterLinkPopCustom2", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"w", id:"idFilterSaveCurrentAs", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"x", id:"idFilterLinkPopFixFor", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"y", id:"idRssLink", bBrowse:true, bMenu:true, nLevel:3},
		{cKey:"z", id:"idFilterLinkPopSentBy", bBrowse:true, bMenu:true, nLevel:3}
	];

	// Initialize the map of browsable hotkey IDs to target elements
	var hkMapInit = {};
	this.hkMap = hkMapInit;
	this.rgHotKeys.foreach(function(_) { if (_.bBrowse) hkMapInit[_.id] = _; });

	this.setSnippetKey = function(sSnippetKeyInit)
	{
		this.sSnippetKey = sSnippetKeyInit;
		this.rgHotKeys[0].cKey = this.sSnippetKey;
	}

	this.registerPopupKey = function( cKeyInit, sId )
	{
		this.rgHotKeys.push({cKey:cKeyInit, id:sId, bPopup:true, nLevel:1});
	}

	this.removePopupKeys = function()
	{
		while (this.rgHotKeys[this.rgHotKeys.length-1].bPopup)
			this.rgHotKeys.pop();
	}

	this.activateHotKeys = function()
	{
		if (0 == this.nHotKeysLevel) return true;

		var bMadeAtLeastOne = false;
		var idLevelContainer = '';
		var bPopupVisible = EditableTableManager.isVisible();

		var keyHelpers = document.createElement('SPAN');
		keyHelpers.id = 'keyHelpers';

		for (var i = 0; i < this.rgHotKeys.length; i++)
		{
			var oKey = this.rgHotKeys[i];
			if (oKey.nLevel == this.nHotKeysLevel && (!bPopupVisible || oKey.bPopup))
			{
				var elHelper = elById(oKey.id);
				var keyHelper = null;
				if (null != elHelper && (!elHelper.getAttribute("keydisabled")) && 
				 	isDisplayed(elHelper) &&
					(null == elHelper.className || ('actionButtonDisabled' != elHelper.className))) 
				{
					var pt = getAbsolutePosition(elHelper);
	
					keyHelper = document.createElement('DIV');
					keyHelper.id = 'keyHelper_' + i;
					keyHelper.className = 'hotkeyHelper';
					keyHelper.style.zIndex = 2;
					var keyToDisp = oKey.cKey.toUpperCase();
					if ("<" == keyToDisp)
					{
						keyToDisp = "&lt;"
					}
					else if (">" == keyToDisp)
					{
						keyToDisp = "&gt;"
					}
					keyHelper.innerHTML = keyToDisp;
					this.positionKeyHelper(keyHelper, pt, elHelper, oKey.pos);
					keyHelpers.appendChild(keyHelper);
					bMadeAtLeastOne = true;
				}
			
			}

			if (null != oKey.nLevelAnchor && this.nHotKeysLevel != oKey.nLevelAnchor && !bPopupVisible)
			{
				var elHelper = elById(oKey.id);
				var keyHelper = null;
				if (null != elHelper && 
				 	isDisplayed(elHelper) && (!elHelper.getAttribute("keydisabled")) && 
					(null == elHelper.className || ('actionButtonDisabled' != elHelper.className))) 
				{
					var pt = getAbsolutePosition(elHelper);
	
					keyHelper = document.createElement('DIV');
					keyHelper.id = 'keyHelper_' + i;
					keyHelper.className = 'hotkeyHelper';
					var keyToDisp = oKey.nLevelAnchor;
					keyHelper.innerHTML = keyToDisp;
					this.positionKeyHelper(keyHelper, pt, elHelper, oKey.levelAnchorPos);
					keyHelpers.appendChild(keyHelper);
				}
			
			}

			if (null != oKey.nLevelAnchor && 
				this.nHotKeysLevel == oKey.nLevelAnchor &&
				isDisplayed(oKey.id))
			{
				idLevelContainer = oKey.idLevelContainer;
			}

		}
		if (bMadeAtLeastOne)
		{
			document.body.appendChild(keyHelpers);
			this.browseMenus(idLevelContainer);
		}

		if (window.opera)
		{
			// case 288595
			// Need to repair focus in Opera, b/c if 3rd level hotkeys are activated
			// via typing '3', the focus may have been broken
			// due to Opera's "Next Frame" keyboard shortcut
			//
			safeFocus(window);
		}

		return bMadeAtLeastOne;
	}

	this.positionKeyHelper = function(keyHelper, pt, elHelper, pos)
	{
		if (keyHelper)
		{
			if (pos && 'center' == pos)
			{
				keyHelper.style.left = pt.x + elHelper.offsetWidth / 2 - 5 + 'px' ;
				keyHelper.style.top = pt.y + 2 + 'px';
			}
			else if (pos && 'below' == pos)
			{
				keyHelper.style.left = pt.x + this.nHelperOffsetX + 'px';
				keyHelper.style.top = pt.y + this.nHelperOffsetBelowY + 'px';
			}
			else if (pos && 'left' == pos)
			{
				keyHelper.style.left = pt.x + this.nHelperOffsetLeftX + 'px';
				keyHelper.style.top = pt.y + this.nHelperOffsetLeftY + 'px';
			}
			else
			{
				keyHelper.style.left = pt.x + this.nHelperOffsetX + 'px';
				keyHelper.style.top = pt.y + this.nHelperOffsetY + 'px';
			}

		}
	}

	this.deactivateHotKeys = function()
	{
		this.nHotKeysLevel = 0;
		this.hideHotKeys();
	}

	this.cleanupOnClick = function()
	{
		this.deactivateHotKeys();
		this.oMenuBrowser = null;
		this.bBrowsingGrid = false;
		if (this.oGridBrowser) this.oGridBrowser.cleanup();
		var elCursor = this.getElCursor();
		if (elCursor) elCursor.style.display = 'none' ;
		return true;
	}

	this.hideHotKeys = function()
	{
		theMgr.unmaskClicks();
		var keyHelpers = elById('keyHelpers');
		if (keyHelpers != null && keyHelpers.parentNode != null)
		{
			keyHelpers.parentNode.removeChild(keyHelpers);
		}
	}

	this.giveFocus = function(el)
	{
		if (null == el)
		{
			return false;
		}
		
		theMgr.hideAllPopups();
		if (true == this.hkMap[el.id].bMenu && el.onclick)
		{
			this.simulateClick(el);
			safeFocus(el);
		}
		else
		{
			theMgr.maskClicks();
			safeFocus(el);
		}
		return true;
	
	}

	this.keystroke = function(e)
	{
		var keyCode = getKeyCode(e);

		if (keyCode >= 16 && keyCode <= 18)  return true; // Ignore shift, ctrl, and alt
		if (keyCode == 191 && !document.all && this.nHotKeysLevel && e.shiftKey) return false; // Firefox will report as 191, THEN 63
		var sKey = String.fromCharCode(KeyboardMap.fix(keyCode));
		var sTypeFocused = "";
		var elFocused = null;
		var nActionLevel = this.nHotKeysLevel;

		// Find the type of the element that has the focus.
		if ( e && e.target && e.target.type )
		{
			sTypeFocused = e.target.type.toLowerCase();
			elFocused = e.target;
		}
		else if (window.event && window.event.srcElement && window.event.srcElement.tagName)
		{
			elFocused = window.event.srcElement;
			sTypeFocused = elFocused.tagName.toLowerCase();
			if (elFocused.type)
			{
				sTypeFocused = elFocused.type.toLowerCase();
			}
			e = window.event;
		}

		if (0 == sKey.length && (e.altKey || e.ctrlKey))
		{
			return true;
		}

		// Whatever you typed, the act of typing takes you out of hotkey mode and removes the hotkey helpers
		if (this.nHotKeysLevel > 0)
		{
			this.hideHotKeys();
		}

		// If you hit the activation key, we will activate the hotkeys
		if (this.fxActivationKey(e, String.fromCharCode(KeyboardMap.fix(keyCode, true))))
		{
			theMgr.hideAllPopups();
			IdPop.hideNow();
			do 
			{
				this.nHotKeysLevel = (this.nHotKeysLevel + 1) % (this.nHotKeysLevelMax + 1);
			} 
			while (!this.activateHotKeys());
			return cancel(e);
		}
		
		// You hit a key. It was not the 'hotkeys' key. So you are not cycling hotkeys.
		this.nHotKeysLevel = 0;

		if (27 == keyCode) //27 is escape
		{ 
			if(null != elFocused && null != elFocused.id && 'idSnippet' == elFocused.id.substring(0,9))
			{
				// If you hit escape, and you're in the snippet helper, we're going to close it nicely and put
				// the focus back in the edit box
				hideSnippetHelper();
				return cancel(e);
			}
			else
			{
				theMgr.hideAllPopups();
				repairFocus(elById("searchFor"));
				if (nActionLevel == 0)
				{
					EditableTableManager.hideAllPanes();
					ShadowManager.hideAllShadows();
				}
				if (this.oMenuBrowser)
				{
					this.oMenuBrowser.cleanup();
					this.oMenuBrowser = null;
				}
				if (this.bBrowsingGrid)
				{
					this.oGridBrowser.cleanup();
					this.bBrowsingGrid = false;
					this.getElCursor().style.display = 'none' ;
				}
			}
		}

		// If the type of the element that has the focus indicates that you actually meant to be typing into that
		// element, then we'll release this keystroke, unless:
		// 1. You're in hotkey mode (that is, you hit the hotkeys key and we're showing you the helpers right now)
		// 2. it was 'Escape', in which case we will take the focus away from that element and cancel the keystroke
		if (0 == nActionLevel)
		{
			if (this.rgReleaseKeys.firstMatch(function(_) { return _ == sTypeFocused; }))
			{
				if (27 == keyCode && elFocused != null && elFocused.blur)
				{
					elFocused.blur();  //generates a bizarre firefox error we can't catch right now
					return cancel(e);
				}
				return true;
			}
		}

		// If an arrow key and you have an oListBrowser, move around or between menus
		if (!e.altKey && !e.ctrlKey && !e.metaKey && ((!window.opera && (33 <= keyCode && 40 >= keyCode)) || 106 == keyCode || 107 == keyCode || 74 == keyCode || 75 == keyCode || 45 == keyCode || 43 == keyCode) )
		{
			if (0 != nActionLevel && this.oGridBrowser && (33 == keyCode || 34 == keyCode || 35 == keyCode || 36 == keyCode || 38 == keyCode || 40 == keyCode))
			{
				var bMove = false;
				if (this.oGridBrowser.elCurrent) bMove = true;
				this.browseGridRows();
				if (40 == keyCode && !bMove) return cancel(e);
			}

			var oListBrowser = null;
			if (null != this.oPopupBrowser && isDisplayed(this.oPopupBrowser.elContainer))
			{
				oListBrowser = this.oPopupBrowser;
			}
			else if (this.bBrowsingGrid && null == this.oMenuBrowser && null != this.oGridBrowser && null != this.oGridBrowser.elContainer)
			{
				oListBrowser = this.oGridBrowser;
			}

			if ( 
				(33 == keyCode && this.oGridBrowser && oListBrowser == this.oGridBrowser && this.oGridBrowser.goNPrevious(this.getGridScrollNum())) ||
				(34 == keyCode && this.oGridBrowser && oListBrowser == this.oGridBrowser && this.oGridBrowser.goNNext(this.getGridScrollNum())) ||
				(35 == keyCode && oListBrowser && oListBrowser.goToLast()) ||
				(36 == keyCode && oListBrowser && oListBrowser.goToFirst()) ||
				(37 == keyCode && this.oMenuBrowser && this.oMenuBrowser.goToPrevious()) ||
				((38 == keyCode || 107 == keyCode || 75 == keyCode) && oListBrowser && oListBrowser.goToPrevious()) ||
				(39 == keyCode && this.oMenuBrowser && this.oMenuBrowser.goToNext()) ||
				((40 == keyCode  || 106 == keyCode || 74 == keyCode) && oListBrowser && oListBrowser.goToNext()) ||
				((38 == keyCode || 40 == keyCode) && this.oMenuBrowser && this.oMenuBrowser.elCurrent && this.oMenuBrowser.elCurrent.onclick && this.oMenuBrowser.elCurrent.onclick())
				)
			{
				return cancel(e);
			}
			else if (this.oMenuBrowser)
			{
				if (this.oPopupBrowser && this.oPopupBrowser.elCurrent && (45 == keyCode || 43 == keyCode))
				{
					// If user typed + or - on the numeric keypad, we want to expand/collapse any
					// subtrees that may be associated with the current popupbrowser selection
					//
					var sNameRefine = null;
					var oFilterRefine = null;
					sNameRefine = this.oPopupBrowser.elCurrent.getAttribute("sNameRefine");
					if (sNameRefine) oFilterRefine = elById("idRefineFilter" + sNameRefine);

					if (oFilterRefine && (43 == keyCode) && oFilterRefine.style.display == "none")
						this.simulateClick(this.oPopupBrowser.elCurrent);
					else if (oFilterRefine && (45 == keyCode) && oFilterRefine.style.display != "none")
						this.simulateClick(this.oPopupBrowser.elCurrent);
				}
				if (0 == nActionLevel)
				{
					return cancel(e);
				}
				else
				{
					this.oMenuBrowser.cleanup();
					this.oMenuBrowser = null;
				}
			}
		}

		// If you're not holding ctrl or meta, and hotkeys are on, see if you hit an available hotkey
		if (!e.metaKey && 0 != nActionLevel)
		{
			if (keyCode > 48 && keyCode <= 48 + this.nHotKeysLevelMax)
			{
				this.nHotKeysLevel = keyCode - 48;
				if (this.activateHotKeys())
				{
					return cancel(e);
				}
			}

			this.oMenuBrowser = null; // If you had an oMenuBrowser, but we're exiting hotkey mode, you don' have one any more.

			var bPopupVisible = EditableTableManager.isVisible();

			// Iterate the hotkeys, figure out if the key you hit was one of them. If so, simulate a click
			// on the UI element to which that hotkey is linked in the rgHotKeys array.
			var key = this.rgHotKeys.firstMatch( function(_) {
						return nActionLevel == _.nLevel &&
							(!bPopupVisible || _.bPopup) && 
					 		(_.cKey == sKey) && 
							isDisplayed(elById(_.id)) &&
				 			!(elById(_.id)).getAttribute("keydisabled")
					});
			if (key)
			{
				var elToClick = elById(key.id);

				if (!key.bNeverFocus) try { 
					if (isDisplayed(elToClick)) safeFocus(elToClick);
				} catch (err) {};
				if (this.simulateClick(elToClick))
				{
					return cancel(e);
				}
				
			}
			// hotkeys were active, so we'll eat your key, per BugzId:270837
			return cancel(e);
		}

		return true;
	}

	this.simulateClick = function(elToClick)
	{
		if (elToClick != null)
		{
			//If we can't see the element, we can't click it.
			if (isDisplayed(elToClick))
			{
				if (document.selection && elToClick.click)
				{
					try { elToClick.click(); } catch (err) {};
					return true;
				}
				else 
				{
					var clicked = false;
					try {  elToClick.click();  clicked = true; } catch (err) {};
					if (!clicked)
					{
						try { if ((!elToClick.onclick || elToClick.onclick()) && elToClick.href) document.location = elToClick.href;} catch (err) {};
					}
					return true;
				}
			}
		}
		return false;
	}

	this.browseMenus = function(idContainerInit)
	{
		this.oMenuBrowser = new ListBrowser(elById(idContainerInit),
							function(el) { return el.id && KeyManager.hkMap[el.id]; },
							function(el) { KeyManager.giveFocus(el); },
							function(el) { ;},
							null,
							null,
							null);
		return false;
	}

	this.browsePopup = function(idContainerInit)
	{
		this.oPopupBrowser = new ListBrowser(elById(idContainerInit),
							function(el) { return null != el.tagName && 'a' == el.tagName.toLowerCase(); },
							function(el) { if (el && el.focus) safeFocus(el); },
							function(el) { ;},
							null,
							function(el) { el.onfocus = function() { null != KeyManager.oPopupBrowser && KeyManager.oPopupBrowser.setElCurrent(this); };
									el.onmouseover = function() { if (!window.opera) safeFocus(this); }; },
							null);
		return false;
	}

	this.browseCheckboxPopup = function(idContainerInit)
	{
		this.oPopupBrowser = new ListBrowser(elById(idContainerInit), 
							function(el) { return null != el.type && 'checkbox' == el.type(); },
							function(el) { safeFocus(el); },
							function(el) { ;},
							null,
							function(el) { el.onfocus = function() { null != KeyManager.oPopupBrowser && KeyManager.oPopupBrowser.setElCurrent(this); };
									el.onmouseover = function() { if (!window.opera) safeFocus(this); }; },
							null);
		return false;
	}

	this.browseGridRows = function()
	{
		if (this.oGridBrowser)
		{
			this.oMenuBrowser = null;
			theMgr.maskClicks();
			this.bBrowsingGrid = true;
			if (this.oGridBrowser.elCurrent)
			{
				setTimeout("KeyManager.oGridBrowser.fxEnter(KeyManager.oGridBrowser.elCurrent);", 1);
			}
			else
			{
				this.oGridBrowser.goToNext();
			}
			return false;
		}
		else
		{
			return true;
		}
	}

	this.getElCursor = function()
	{
		return elById('kbCursor');
	}

	this.gridBrowserGetElPos = function(el)
	{
		if (el.parentNode && 
			el.parentNode.parentNode && 
			el.parentNode.tagName && 
			'tr' == el.parentNode.parentNode.tagName.toLowerCase())
		{
			el = el.parentNode.parentNode;
			while(window.safari && !el.offsetWidth && el.firstChild)
			{
				// Safari can't get offsetHeight/Width of <tr> or <td> elements,
				// so get the next best node available
				el = el.firstChild;
			}
			return el;
		}
		else
		{
			return el;
		}	
	}

	this.gridBrowserPositionCursor = function(el)
	{
		var elPos = this.gridBrowserGetElPos(el); 
		var elCursor = KeyManager.getElCursor();
		var pt = getAbsolutePosition(elPos);
		elCursor.style.display = 'block' ;
		elCursor.style.left = (pt.x - elCursor.offsetWidth - 3) + 'px' ;
		elCursor.style.top = (pt.y + (elPos.offsetHeight - elCursor.offsetHeight)/2) + 'px';
		
	}

	this.getGridScrollNum = function()
	{
		var elPos = this.gridBrowserGetElPos(this.oGridBrowser.elCurrent); 
		return ((window.innerHeight ? window.innerHeight : document.documentElement.clientHeight) / elPos.offsetHeight) - 4;
	}


	this.setupGridBrowser = function()
	{
		if (elById('bugGrid'))
		{
			this.oGridBrowser = new ListBrowser(elById('bugGrid'),
							function(el) { return null != el.type && 'checkbox' == el.type; },

							function(el) { if (window.opera && !('ixBulkBugAll' == el.name)) el.disabled = false; 
									if (null != el.parentNode.parentNode.onmouseover) el.parentNode.parentNode.onmouseover(); 
									safeFocus(el); 
									KeyManager.gridBrowserPositionCursor(el);
									},

							function(el) { if (window.opera && !('ixBulkBugAll' == el.name)) el.disabled = true;
									if (null != el.parentNode.parentNode.onmouseout) el.parentNode.parentNode.onmouseout(); },

							function(el) { return false; },

							null,
							function(el) { if (window.opera && !('ixBulkBugAll' == el.name)) el.disabled = true;
									SelectionManager.removeTempHighlight(getParentRow(el), el); } );
		}
		else if (elById('dlgGrid'))
		{
			this.oGridBrowser = new ListBrowser(elById('dlgGrid'),
							function(el) { return null != el.tagName && 'a' == el.tagName.toLowerCase() && (el.title && Lang.getString("FB_ICON_EDIT") == el.title || Lang.getString("FB_ICON_NEW") == el.title) || (el.className && 'discuss' == el.className ||'discussUndecided' == el.className || 'discussSpam' == el.className || 'discussTopic' == el.className ); },
							function(el) { safeFocus(el); KeyManager.gridBrowserPositionCursor(el); SelectionManager.doTempHighlight(getParentRow(el), null);},
							function(el) { SelectionManager.removeTempHighlight(getParentRow(el), el); },

							function(el) { return false; },

							null,
							function(el) { SelectionManager.removeTempHighlight(getParentRow(el), el); } );
		}
		return false;
	}

}; //End KeyManager singleton


//Class ListBrowser
function ListBrowser(elContainerInit, fxTestInit, fxEnterInit, fxLeaveInit, fxDeadEndInit, fxDoToEachInit, fxOnCleanInit)
{
	this.fxTest = fxTestInit;
	this.fxEnter = fxEnterInit;
	this.fxLeave = fxLeaveInit;
	this.fxDoToEach = fxDoToEachInit;
	this.fxDeadEnd = fxDeadEndInit;
	this.fxOnClean = fxOnCleanInit;
	this.elContainer = elContainerInit;
	this.elCurrent = null;
	this.debug = false;

	// IMPORTANT!
	// After we def all of the functions, we call doFxToEach, see the end of this constructor

	this.setElCurrent = function(el)
	{
		if (isUnderNode(el, this.elContainer))
		{
			this.elCurrent = el;
		}
	}

	this.doFxToEach = function()
	{
		if (null == this.fxDoToEach)
		{
			return;
		}

		var elSet = this.findNextUnder(null);
		var elNext = null;
		while (null != elSet)
		{
			this.fxDoToEach(elSet);
			elNext = this.findNextUnder(elSet);
			if (elSet == elNext)
			{
				elSet = null;
			}
			else
			{
				elSet = elNext;
			}
		}
	}

	this.traverse = function(bForward, n)
	{
		var elToLeave = this.elCurrent;
		var elNew;
		for (var i =0; i < n; i++)
		{
			elNew = (bForward ? this.findNextUnder(this.elCurrent) : this.findPreviousUnder(this.elCurrent));
			if (elNew == this.elCurrent)
			{
				if (null == this.fxDeadEnd)
				{
					elNew = (bForward ? this.findNextUnder(null) : this.findPreviousUnder(null));
				}
				else
				{
					if (null != elToLeave) this.fxLeave(elToLeave);
					this.fxEnter(this.elCurrent);
					return this.fxDeadEnd();
				}
			}
			this.elCurrent = elNew;
		}
		if (null != elToLeave) this.fxLeave(elToLeave);
		this.fxEnter(this.elCurrent);
		return true;
	}

	this.goToNext = function ()
	{
		return this.traverse(true, 1);
	}

	this.goToPrevious = function()
	{
		return this.traverse(false, 1);
	}

	this.goNNext = function (n)
	{
		return this.traverse(true, n);
	}

	this.goNPrevious = function(n)
	{
		return this.traverse(false, n);
	}

	this.goToFirst = function()
	{
		this.dropCurrent();
		return this.traverse(true, 1);
	}

	this.goToLast = function()
	{
		this.dropCurrent();
		return this.traverse(false, 1);
	}
	
	this.dropCurrent = function()
	{
		if (this.elCurrent)
		{ 
			this.fxLeave(this.elCurrent);
			this.elCurrent = null;
		}
	}

	this.findNextUnder = function(elStart)
	{
		return this.findUnder(elStart, function(el) { return el.nextSibling }, function(el) { return el.firstChild });
	}

	this.findPreviousUnder = function(elStart)
	{
		return this.findUnder(elStart, function(el) { return el.previousSibling }, function(el) { return (document.selection ? el.childNodes[el.childNodes.length - 1] : el.lastChild) });
	}

	this.findUnder = function(elStart, fxNext, fxChild)
	{
		if (null == this.elContainer)
		{
			// no container, so we can't find anything
			return null;
		}

		var el = null;
		var moved = false;
		var justAscended = false;
		if (null != elStart)
		{
			// We have a container and an element, so we're good to go.
			el = elStart;
		}
		else if (null != fxChild(this.elContainer))
		{
			// Have a container, but no element, so find the first element in the container
			el = fxChild(this.elContainer);
			// If this element satisfies the function, we can return it
			moved = true;
		}
		else
		{
			// Container has no children, so we're not going to be able to find an element
			return null;
		}

		while (null != el)
		{
			if (null != el && this.fxTest(el) && moved && !justAscended)
			{
				//found a node that fits the test
				if (this.debug) alert ('found');
				return el;
			}
			else if (null != el.tagName && null!= el.style && 'none' != el.style.display && null != fxChild(el) && !justAscended)
			{
				//descend
				if (this.debug) alert ('descend');
				el = fxChild(el);
				moved = true;
				justAscended = false;
			}
			else if (null != fxNext(el))
			{
				//get next
				if (this.debug) alert ('next');
				el = fxNext(el);
				moved = true;
				justAscended = false;
			}
			else if (document.getParent(el) != this.elContainer)
			{
				//ascend
				if (this.debug) alert ('ascend');
				el = document.getParent(el);
				moved = true;
				justAscended = true;
			}
			else
			{
				if (this.debug) alert ('dead end');
				//dead end, can't go to next, descend, or ascend
				el = null;
			}
		}

		// Couldn't find another element, so return input element
		return elStart;
	}
	
	this.cleanup = function()
	{
		if (this.elCurrent && this.elCurrent.blur)
		{
			try {this.elCurrent.blur();} catch(err) {};
		}
		if (this.fxOnClean)
		{
			this.fxOnClean(this.elCurrent);
		}
	}

	this.doFxToEach();
} // End class ListBrowser


var KeyboardMap = new function()
{
	
	this.fix = function(n, fWithoutCtrl)
	{
		if (!(window.ie && navigator.userLanguage)) return n;
		var mapCurrent = this.map[navigator.userLanguage];
		if (!mapCurrent) mapCurrent = this.map['en-us']; // hegemony fallback
		var nMask = 0;
		if (window.event.shiftKey) nMask += 1;
		if (!fWithoutCtrl && (window.event.ctrlKey)) nMask += 2;
		if (window.event.altKey) nMask += 4;
		var sIdent = 'n' + n + 's' + nMask;
        	if (mapCurrent[sIdent])
		{
			return mapCurrent[sIdent];
		}
        	else
		{
			return n;
		}
	}
}

KeyboardMap.map = {};
KeyboardMap.map['en-us'] = { n49s1:33, n222s1:34, n51s1:35, n52s1:36, n53s1:37, n55s1:38, n222s0:39, n57s1:40, n48s1:41, n56s1:42, n187s1:43, n188s0:44, n189s0:45, n190s0:46, n191s0:47, n186s1:58, n186s0:59, n188s1:60, n187s0:61, n190s1:62, n191s1:63, n50s1:64, n219s0:91, n220s0:92, n221s0:93, n54s1:94, n189s1:95, n192s0:96, n65s0:97, n66s0:98, n67s0:99, n68s0:100, n69s0:101, n70s0:102, n71s0:103, n72s0:104, n73s0:105, n74s0:106, n75s0:107, n76s0:108, n77s0:109, n78s0:110, n79s0:111, n80s0:112, n81s0:113, n82s0:114, n83s0:115, n84s0:116, n85s0:117, n86s0:118, n87s0:119, n88s0:120, n89s0:121, n90s0:122, n219s1:123, n220s1:124, n221s1:125, n192s1:126, n8s2:127, n0s0:0 };


