/***********************
QuickChange CORE

Author: Emir Plicanic
Version: 2.0
Updated: 03/24/2011
Webstie: www.emirplicanic.com
***********************/

/* 
If using in development environment i.e "http://localhost:888/MAMP/SiteName", 
you may need to update the path to  "http://"+window.location.hostname+":8888/SiteName/" 
*/
var qc_pathname = "http://"+window.location.hostname+"/";

	
/*--------------------DO NOT CHANGE ANYTHING BELOW THIS LINE--------------------------------*/

$(function() { 	
	
	//Load and execute jquery DIALOG UI only if not already in use
	if(typeof dialog == 'undefined'){
		var load_QC_ui=document.createElement("script");
		load_QC_ui.setAttribute("type", "text/javascript");
		load_QC_ui.setAttribute("src", qc_pathname+"qchange/js/jquery-ui-latest.min.js");
		document.getElementsByTagName("head")[0].appendChild(load_QC_ui);
		$.getScript(qc_pathname+"qchange/js/jquery-ui-latest.min.js");
		
	}
	
	//Load and execute jquery FORM plugin if not already loaded
	if(typeof ajaxSubmit == 'undefined'){
		var load_QC_ui=document.createElement("script");
		  load_QC_ui.setAttribute("type", "text/javascript");
		  load_QC_ui.setAttribute("src", qc_pathname+"qchange/js/jquery.form.js");
		  document.getElementsByTagName("head")[0].appendChild(load_QC_ui);
		  $.getScript(qc_pathname+"qchange/js/jquery.form.js");
		
	}
	//Load and execute jquery jGROWEL plugin if not already loaded
	if(typeof jGrowl == 'undefined'){
		  var load_QC_ui=document.createElement("script");
		  load_QC_ui.setAttribute("type", "text/javascript");
		  load_QC_ui.setAttribute("src", qc_pathname+"qchange/js/jquery.jgrowel.js");
		  document.getElementsByTagName("head")[0].appendChild(load_QC_ui);
		  $.getScript(qc_pathname+"qchange/js/jquery.jgrowel.js");	
	}
	

	
	//DO NOT CHANGE. THIS VARIABLE is set via AJAX
	var qc_defaults = "";//array 0=language, 1=defaultpage, 2=default extension
	
	//see if user is logged in													 
	$.ajax({
	  url: qc_pathname+"qchange/ajax.php",
	  data: "action=getlogin",
	  async: true,
	  success: function(msg){
						  if(msg == 'loggedin'){
							load_QC_styles(qc_pathname);
							load_QC_editor(qc_pathname);
						  }else{
							$("body .tiny_QC_mce").each(function(){
								var tmceid = $(this).attr('id');
								tinyMCE.execCommand('mceRemoveControl', false, tmceid);
							});
							$("#login_QC_dialog form").remove();
							$(".qc-edit").unbind('click');
						  }
					  }
	});
	

	$(document).bind('keydown', 'Alt+Ctrl+e',function (evt){
		  login_to_QC(qc_pathname);
		  return false; 
	});
	
	//get defaults
	$.ajax({
	  url: qc_pathname+"qchange/ajax.php",
	  data: "action=defaults",
	  async: true,
	  success: function(msg){
		  
		  		var find_QC_error = msg.search("ERROR");
	
				  if(msg.search(/ERROR/i)){
				  
				  qc_defaults = msg.split(",");
				  lang = qc_defaults[0];
				  
				  create_QC_Cookie('QC_defaults',msg,1);
				  	
					var load_QC_ui=document.createElement("script");
					load_QC_ui.setAttribute("type", "text/javascript");
					load_QC_ui.setAttribute("src", qc_pathname+"qchange/langs/"+lang+".js");
					document.getElementsByTagName("head")[0].appendChild(load_QC_ui);
					$.getScript(qc_pathname+"qchange/langs/"+lang+".js");
			
				  }else{
					 alert(msg);
				  }
			  }
	});


var login_to_QC = function(qc_pathname){
			if($("head #load_QC_jqUI_css").length < 1){  
			  var qccss=document.createElement("link");
			  qccss.setAttribute("rel", "stylesheet");
			  qccss.setAttribute("type", "text/css");
			  qccss.setAttribute("href", qc_pathname+"qchange/css/jquery-ui-latest.css");
			  qccss.setAttribute("id", "load_QC_jqUI_css");
			  document.getElementsByTagName("head")[0].appendChild(qccss);
		   }
		  
		  login_QC(qc_pathname);	
};

$(".qc-relogin").live("click", function(){
	login_QC(qc_pathname);
	return false;
});

//function login form
var login_QC = function (qc_pathname){
	//close and remove any opened login dialogs
	try{
	$("#login_QC_dialog form").remove();
	$(".ui-dialog").dialog('destroy');
	$("#login_QC_dialog").remove();
	}catch(e){}
    //see if user is logged in													 
	var msg = $.ajax({
	  url: qc_pathname+"qchange/ajax.php",
	  data: "action=getlogin",
	  async: false
	}).responseText;
	 //set default dialog title 
	 var dialogtitle = QC_loginform.Login;
	 
	 //add dialog to the DOM
	 $("body").append('<div id="login_QC_dialog"></div>');
	 
	 //if not logged in display login form otherwise show logout dialog
	 if(msg=="loggedin"){
		 $("#login_QC_dialog").append('<form action="'+qc_pathname+'qchange/ajax.php" method="post" enctype="application/x-www-form-urlencoded" name="loginform" id="QC_loginform"><div><p>'+QC_loginform.logout_msg+'</p><input type="hidden" name="action" id="action" value="logout" /></div></form>'); 
		 dialogtitle = QC_loginform.Logout;
	 }else{
		 $("#login_QC_dialog").append('<form action="'+qc_pathname+'qchange/ajax.php" method="post" enctype="application/x-www-form-urlencoded" name="loginform" id="QC_loginform"><div><label for="u" style="display:block;font-size:12px;float:none;clear:both;position:static;">'+QC_loginform.label_username+'</label><input type="text" name="u" id="u" class="ui-widget-content" /></div><div><label for="p" style="display:block;font-size:12px;float:none;clear:both;position:static;">'+QC_loginform.label_password+'</label><input type="password" name="p" id="p" class="ui-widget-content" /><input type="hidden" name="action" id="action" value="login" /></div></form>'); 
	 }
	 
	 //create UI dialog
	 $("#login_QC_dialog").dialog({
			bgiframe: true,
			width: 300,
			height:  'auto',
			modal: true,
			title: dialogtitle,
			close: function(){					
				$("#login_QC_dialog form").remove();
				$(this).dialog('destroy');
	
			},
			buttons: [{
			text : QC_loginform.button_ok,
				click : function() {
					//Login
					 //check if logged in
					 var u = $("#QC_loginform #u").val();
					 var p = $("#QC_loginform #p").val();
					 
					 if(dialogtitle==QC_loginform.Logout){
						var dialogaction = "logout";	 
					 }else{
						var dialogaction = "login";
					 }
					 msg = $.ajax({
						  url: qc_pathname+"qchange/ajax.php",
						  data: "action="+dialogaction.toLowerCase()+"&u="+u+"&p="+p, 
						  success: function(msg){
							  if(msg == 'loggedin'){
								load_QC_styles(qc_pathname);
								load_QC_editor(qc_pathname);
								
							  }else if(msg == "error"){
								 $("#login_QC_dialog form").html('<p>'+QC_loginform.login_msg_error+'<br /> <a href="#relogin" class="qc-relogin">'+QC_loginform.login_msg_error_link+'</a>.</p>'); 
								  
							  }else{
								$("body .tiny_QC_mce").each(function(){
									var tmceid = $(this).attr('id');
									tinyMCE.execCommand('mceRemoveControl', false, tmceid);
								});
								
								//LOGGING OUT. DO Clean up.
								//unload and close all open dialogs
								$("#login_QC_dialog form, #load_QC_jqUI_css, #editor_QC_dialog, #load_QC_css").remove();
								$("#login_QC_dialog").dialog('destroy');
								$("#editor_QC_dialog").dialog('destroy');
								$(".qc-edit").unbind('click');
								//$(".qc-edit").removeClass('ui-state-highlight');
							  }
						  }
					 }).responseText;
					
				}
			},
			{
			text : QC_loginform.button_cancel,
				click: function() {
					$("#login_QC_dialog form").remove();
					$(this).dialog('destroy');
					
				}
			
		}]
	
	 });
	 
};

function load_QC_styles(qc_pathname){
	//check if CSS and JS is loaded
	
	try{
		if($("head #load_QC_jqUI_css").length < 1){  
		  var qccss=document.createElement("link");
		  qccss.setAttribute("rel", "stylesheet");
		  qccss.setAttribute("type", "text/css");
		  qccss.setAttribute("href", qc_pathname+"qchange/css/jquery-ui-latest.css");
		  qccss.setAttribute("id", "load_QC_jqUI_css");
		  document.getElementsByTagName("head")[0].appendChild(qccss);
		}
		if($("head #load_QC_css").length < 1){  
		  var qcuicss=document.createElement("link");
		  qcuicss.setAttribute("rel", "stylesheet");
		  qcuicss.setAttribute("type", "text/css");
		  qcuicss.setAttribute("href", qc_pathname+"qchange/css/qc_css.css");
		  qcuicss.setAttribute("id", "load_QC_css");
		  document.getElementsByTagName("head")[0].appendChild(qcuicss);
		}
	}catch(e){}
}

function load_QC_editor(qc_pathname){
	//close opened login dialogs
	try{
		if($("#login_QC_dialog").length > 0){
			$("#login_QC_dialog form").remove();
			$("#login_QC_dialog").dialog('destroy');
		}
	}catch(e){}
	//get filename
	var filename = return_QC_document(qc_pathname);
	
	$(".qc-edit").live("click", function(){
											 
			//add dialog to the DOM
			$("body").append('<div id="editor_QC_dialog"></div>');
			//get current div
			//var editsection = $(this).attr("id");
			//get index
			var editsection = $("body .qc-edit").index(this);

			//load selected content
			var content = $(this).html();
			
			//check if div
			var thiselement = this.tagName;
			var casechanged=thiselement.toLowerCase();
							
			var QC_tiny_mce_class = 'style="width: 692px; height:450px;" class="tiny_QC_mce"';				   
			//dont use tiny_mce if tag is not one of following
			if(casechanged !="div" && casechanged !="section" && casechanged !="article"  && casechanged !="aside"  && casechanged !="header"  && casechanged !="footer"  && casechanged !="hgroup"){
				QC_tiny_mce_class = '';
			}
			var QC_subtabs_nav = '<div id="qc_subtabs"><ul><li><a href="#qc_content_tab">'+QC_editorform.tab_edit+'</a></li><li><a href="#qc_meta_tab">'+QC_editorform.tab_meta+'</a></li><li><a href="#qc_files_tab">'+QC_editorform.tab_files+'</a></li><li><a href="#qc_code_tab">'+QC_editorform.tab_code+'</a></li></ul>';
			var QC_title = $("head title").text();
			var QC_keywords = $('head meta[name=keywords]').attr("content");
			var QC_description = $('head meta[name=description]').attr("content");
			var QC_full_html = $.ajax({
						  			url: qc_pathname+"qchange/ajax.php",
						  			data: "action=currenthtml&file="+filename,
	  								async: false
								}).responseText;
			var QC_meta_tags = '<ul class="qc_form_fields"><li><label>'+QC_editorform.lbl_meta_filename+'</label><input type="text" name="newfile" id="newfile" value="'+filename+'" /></li><li><input  style="width: 15px;" type="checkbox" name="makenewfile" id="makenewfile" value="createnewfile" style="margin-left:10px;" /> <span class="QC_newfile_label">'+QC_editorform.lbl_meta_newfile+'</span></li><li><label>'+QC_editorform.lbl_meta_title+'</label><input type="text" name="newtitle" id="newtitle"  value="'+QC_title+'" /></li><li><label>'+QC_editorform.lbl_meta_keywords+'</label><textarea name="newkeywords" id="newkeywords" rows="5" cols="50">'+QC_keywords+'</textarea></li><li><label>'+QC_editorform.lbl_meta_desc+'</label><textarea name="newdesc" id="newdesc" rows="5" cols="50">'+QC_description+'</textarea></li></ul>';
			
			var QC_file_tab = '<div id="qc_files_tab"><div id="QC_sidebar"></div></div>';
			
			
			var QC_tiny_form_html = '<form id="qc_form" name="qc_form" action="'+qc_pathname+'qchange/ajax.php" method="post"><div id="qc_content_tab"><textarea id="elm1" name="elm1" rows="20" cols="50" '+QC_tiny_mce_class+'">'+content+'</textarea><input type="hidden" name="action" id="action" value="save"><input type="hidden" name="id" id="id" value="'+editsection+'"><input type="hidden" name="file" id="file" value="'+filename+'"></div><div id="qc_meta_tab">'+QC_meta_tags+'</div><div id="qc_code_tab"><span class="QC_newfile_label"><strong>'+filename+'</strong> ('+QC_editorform.inst_code_tab+')</span><textarea name="newhtml" id="newhtml" rows="5" cols="50">'+QC_full_html+'</textarea><input type="hidden" name="codefile" id="codefile" value="'+filename+'"></div>'+QC_file_tab+'</div></form>';
			//add form to the dialog
			$("#editor_QC_dialog").append(QC_subtabs_nav+QC_tiny_form_html);
			//create tabs
			var $tabs = $( "#qc_subtabs").tabs();
			load_QC_filetree(3, 'preview');
			$tabs.tabs("select", 0);
			
			//add code mirror
			$.getScript(qc_pathname+'qchange/js/CodeMirror/js/codemirror.js', function() {
				var editor = CodeMirror.fromTextArea('newhtml', {
					parserfile: "parsexml.js",
					stylesheet: qc_pathname+'qchange/js/CodeMirror/css/xmlcolors.css',
					path: qc_pathname+'qchange/js/CodeMirror/js/',
					continuousScanning: 500,
					lineNumbers: true,
					onChange: function (n) { $('#newhtml').val(editor.getCode()); }
				});
			});
			
			//show dialog
			var QC_OK_button = QC_editorform.button_ok;
			$("#editor_QC_dialog").dialog({
				bgiframe: true,
				width: 740,
				height:  'auto',
				modal: true,
				title: QC_editorform.title,
				close: function(){					
					$("body .tiny_QC_mce").each(function(){
						var tmceid = $(this).attr('id');
						tinyMCE.execCommand('mceRemoveControl', false, tmceid);
					});
					$("#editor_QC_dialog form, #editor_QC_dialog").remove();
					$(this).dialog('destroy');
				},
				buttons: [{
				text: QC_editorform.button_save,
					click: function() {
						//check if tinymce or textarea
						var updatedHTML = '';
						if($(".tiny_QC_mce").length > 0){
							updatedHTML = $('#elm1').html();
						}else{
							updatedHTML = $('#elm1').val();	
						}
							
						$('#elm1').val(updatedHTML);
						
						//submit content
						submit_QC_form();
						$(".qc-edit:eq("+editsection+")").html(updatedHTML);		
						//close dialog
						$("body .tiny_QC_mce").each(function(){
							var tmceid = $(this).attr('id');
							tinyMCE.execCommand('mceRemoveControl', false, tmceid);
						});
						$("#editor_QC_dialog form, #editor_QC_dialog").remove();
						$(this).dialog('destroy');
					}
				},{
				text: QC_editorform.button_cancel,
				click: function() {
					$("body .tiny_QC_mce").each(function(){
						var tmceid = $(this).attr('id');
						tinyMCE.execCommand('mceRemoveControl', false, tmceid);
					});
					$("#editor_QC_dialog form, #editor_QC_dialog").remove();
					$(this).dialog('destroy');
				}
			}]

			});
			
			
			//get tinymcy script and execute it			   
			$.getScript(qc_pathname+"qchange/tiny_mce/jquery.tinymce.js", function(){
			  
			  $('textarea.tiny_QC_mce').tinymce({
					// Location of TinyMCE script
					script_url : qc_pathname+'qchange/tiny_mce/tiny_mce.js',
					forced_root_block:false,
					// General options
					theme : "advanced",
					skin : "o2k7",
					plugins : "safari,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template",
			
					// Theme options
					theme_advanced_buttons1 : "cleanup,pastetext,pasteword,|,bold,italic,underline,separator,justifyleft,justifycenter,justifyright, justifyfull,bullist,numlist,undo,redo,|,formatselect,|,link,unlink,|,anchor,image,media,|,charmap,replace,code",
					theme_advanced_buttons2 : "",
					theme_advanced_buttons3 : "",
					theme_advanced_buttons4 : "",
					theme_advanced_toolbar_location : "top",
					theme_advanced_toolbar_align : "left",
					theme_advanced_statusbar_location : "bottom",
					theme_advanced_resizing : true,
					extended_valid_elements : "hgroup[id|class],section[id|class],article[id|class],header[id|class],footer[id|class],aside[id|class]",
			
					// Example content CSS (should be your site CSS)
					content_css : qc_pathname+'qchange/css/qc_editor.css'
				});
			});//end tinymce
			
			
			

	return false;											 
	});//end click event
}

function submit_QC_form(){ 
		var options = { 
        	success: show_QC_response  // post-submit callback 
        }; 
								 
		// submit the form 
		$('#qc_form').ajaxSubmit(options); 
		// return false to prevent normal browser submit and page navigation 
		return false; 
	//});
}
function show_QC_response(responseText, statusText)  {
	 var response = responseText.split(";;");
	 var msg = responseText;
	 
	 if(response.length < 1){
		msg = 	responseText; 
	 }else{
		msg = response[0]; 
	 }
	 if( msg != 'Saved'){
		$.jGrowl(QC_editorform.saved_txt_error+": "+responseText);
	 }else{
		$.jGrowl(QC_editorform.saved_txt);	 
		if(response[1] != response[2]){
			window.location =  qc_pathname+response[2];	
		}
	 }
}
function return_QC_document(qc_pathname) {
		//set default file name to empty
		var defaultfilename = "";
		//get current URL
		var file_name = "http://"+window.location.hostname+window.location.pathname;
		
		//remove domain name part
        var end = (file_name.indexOf("?") == -1) ? file_name.length : file_name.indexOf("?");
        var file = file_name.substring(file_name.indexOf("/", 7)+1, end);
		
		//remove any parameters	
		var cleanfile = file.replace(/#/g, '');
		var noparams = cleanfile.split("?");
		var filewithpath = noparams[0];
		
		var nm =  read_QC_Cookie('QC_defaults');
		//see if folder or file
		if(filewithpath == ""){
			if(filewithpath.lastIndexOf("/", 0) == "-1"){
				//if folder get default filename
				
				var qc_nm = nm.split(",");
				defaultfilename = qc_nm[1];
			}
		}
		
		var extension = filewithpath.substr(filewithpath.lastIndexOf('.') + 1);
		var file_ext = "";
		if(extension == "" || extension == filewithpath){
			//var ext =  read_QC_Cookie('QC_defaults');
			var qc_ext = nm.split(",");
			var file_ext = "."+qc_ext[2];	
		}
		var extens=(/[.]/.exec(defaultfilename)) ? /[^.]+$/.exec(defaultfilename) : undefined;
		if(extens == "undefined"){
			return filewithpath+defaultfilename+file_ext;
		}else{
			return filewithpath+defaultfilename;
		}
	
		
}


//Cookies
function create_QC_Cookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function read_QC_Cookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

function erase_QC_Cookie(name) {
	createCookie(name,"",-1);
}




});

//file management
function delete_QC_folder(dir){
	var path = dir;
	var action = "deletefile";
	var formdata = 'action='+action+'&path='+path;
	if(path !=""){
		$.ajax({
		   type: "POST",
		   url: qc_pathname+'qchange/ajax.php',
		   data: formdata,
		   success: function(responseText){
			 if(responseText != 'Deleted'){
				$.jGrowl(responseText);
			 }else{
				/*$("#QC_content_editor").html("");
				$("#QC_path .QC_path_text").text("");*/
				$.jGrowl("Deleted");	 
			 }
		   }
		});
	}else{
		$.jGrowl("File/Folder not found. Please refresh the browser!");	
	}
	load_QC_filetree(3, 'delete');
	$("#qc_subtabs").tabs("select", 3);
	return false;
}

function load_QC_filetree(index, loadswitch){
	$.getScript(qc_pathname+'qchange/js/jqueryFileTree.js', function() {
		try{
			$('#QC_sidebar').fileTree({ root: "../", script: qc_pathname+'qchange/ajax.php?action=filetree', multiFolder:false }, function(file) { 
			 
			  var file = file.replace("../","");
				  var html = $.ajax({
					url: qc_pathname+'qchange/ajax.php',
					data: "action=currenthtml&file="+file,
					async: false
					}).responseText;
					//$('#newfile').val(file);
					$('#codefile').val(file);
					$("#newhtml").text(html);
					
					$(".CodeMirror-wrapping").remove();
					$.getScript(qc_pathname+'qchange/js/CodeMirror/js/codemirror.js', function() {
						var editor = CodeMirror.fromTextArea('newhtml', {
							parserfile: "parsexml.js",
							stylesheet: qc_pathname+'qchange/js/CodeMirror/css/xmlcolors.css',
							path: qc_pathname+'qchange/js/CodeMirror/js/',
							continuousScanning: 500,
							lineNumbers: true,
							onChange: function (n) { $('#newhtml').val(editor.getCode()); }
						});
					});
				$("#qc_subtabs").tabs("select", 3);
				$(".QC_newfile_label strong").text(file);
				
			 
			});
		}catch(e){}
	});
}
	

/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/

(function(jQuery){

jQuery.hotkeys = {
version: "0.8",

specialKeys: {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
},

shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
}
};

function keyHandler( handleObj ) {
// Only care when a possible input has been specified
if ( typeof handleObj.data !== "string" ) {
return;
}

var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" ");

handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
event.target.type === "text") ) {
return;
}

// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
character = String.fromCharCode( event.which ).toLowerCase(),
key, modif = "", possible = {};

// check combinations (alt|ctrl|shift+anything)
if ( event.altKey && special !== "alt" ) {
modif += "alt+";
}

if ( event.ctrlKey && special !== "ctrl" ) {
modif += "ctrl+";
}

// TODO: Need to make sure this works consistently across platforms
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta+";
}

if ( event.shiftKey && special !== "shift" ) {
modif += "shift+";
}

if ( special ) {
possible[ modif + special ] = true;

} else {
possible[ modif + character ] = true;
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;

// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if ( modif === "shift+" ) {
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}

for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}

jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});

})( jQuery );

