1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates and initializes a manager for the given keymap.
 27  * @constructor
 28  * @class
 29  * A keymap manager parses the keymap into a form that is easily used for 
 30  * translating key codes into actions. It also provides some static methods
 31  * that map the available keyboard to key codes, and which qualify certain
 32  * keys as punctuation, etc.
 33  * 
 34  * @author Ross Dargahi
 35  *
 36  * @param {DwtKeyMap}	keyMap the keymap
 37  *
 38  * @private
 39  */
 40 DwtKeyMapMgr = function(keyMap) {
 41 
 42 	var map = this._map = keyMap.getMap();
 43 	this._args = keyMap._args;
 44 	
 45 	// build FSA for each mapping
 46 	this._fsas = {};
 47 	for (var key in map) {
 48 		DBG.println(AjxDebug.DBG3, "building FSA for key: " + key);
 49 		this._fsas[key] = DwtKeyMapMgr.__buildFSA({}, map[key], key);
 50 	}
 51 	DBG.dumpObj(AjxDebug.DBG3, this._fsas);
 52 };
 53 
 54 DwtKeyMapMgr.prototype.toString = function() { return "DwtKeyMapMgr"; };
 55 DwtKeyMapMgr.prototype.isDwtKeyMapMgr = true;
 56 
 57 DwtKeyMapMgr.NOT_A_TERMINAL = -999;
 58 DwtKeyMapMgr.TAB_KEYCODE = DwtKeyEvent.KEY_TAB;
 59 
 60 
 61 /**
 62  * This method will attempt to look up the action code for a given key sequence in
 63  * a given key map. 
 64  * 
 65  * @param {string}		keySeq				key sequence to lookup
 66  * @param {string}		mappingName			keymap name in which to search
 67  * @param {boolean}		forceActionCode		if <code>true</code>, then if the key sequence contains both
 68  * 											a submap and an action code, then return the action code.
 69  * 											If this parameter is false or omitted, then
 70  * 											{@link DwtKeyMapMgr.NOT_A_TERMINAL} will be returned for
 71  * 											a key sequence that contains both a submap and an action code.
 72  * 
 73  * @return {string|number}	the action code for the provided key map name, null if there is no action code
 74  * 		or {@link DwtKeyMapMgr.NOT_A_TERMINAL} if the key sequence is an intermediate
 75  * 		node in the key map (i.e. has a submap)
 76  * 
 77  */
 78 DwtKeyMapMgr.prototype.getActionCode =
 79 function(keySeq, mappingName, forceActionCode) {
 80 	//DBG.println(AjxDebug.DBG3, "Getting action code for: " + keySeq + " in map: " + mappingName);
 81 	var mapping =  this._fsas[mappingName];
 82 
 83 	if (!mapping) {
 84 		DBG.println(AjxDebug.DBG3, "No keymap for: " + mappingName);
 85 		return null;
 86 	}
 87 
 88 	var keySeqLen = keySeq.length;
 89 	var tmpFsa = mapping;
 90 	var key;
 91 	for (var j = 0; j < keySeqLen && tmpFsa; j++) {
 92 		key = keySeq[j];
 93 
 94 		if (!tmpFsa || !tmpFsa[key]) break;
 95 
 96 		if (j < keySeqLen - 1) {
 97 			tmpFsa = tmpFsa[key].subMap;
 98 		}
 99 	}
100 
101 	if (tmpFsa && tmpFsa[key]) {
102 		var binding = tmpFsa[key];
103 		/* If the binding does not have a submap, then it must have an action code
104 		 * so return it. Else if the binding does not have an action code (i.e. it
105 		 * has a submap only) or if forceActionCode is false, then return DwtKeyMapMgr.NOT_A_TERMINAL
106 		 * since we are to behave like an intermediate node. Else return the action code. */
107 		if (!binding.subMap || forceActionCode) {
108 			var inherited = this.__getInheritedActionCode(keySeq, mapping, forceActionCode);
109             //if keyMap not available then return the inherited keyMap.
110             return inherited == DwtKeyMapMgr.NOT_A_TERMINAL ? DwtKeyMapMgr.NOT_A_TERMINAL : ( binding.actionCode || inherited );
111 		} else {
112 			return DwtKeyMapMgr.NOT_A_TERMINAL;
113 		}
114 	} else {
115 		return this.__getInheritedActionCode(keySeq, mapping, forceActionCode);
116 	}
117 };
118 
119 /**
120  * Returns the action for the given map and key sequence.
121  * 
122  */
123 DwtKeyMapMgr.prototype.getAction =
124 function(mapName, keySeq) {
125 	return this._map[mapName][keySeq];
126 };
127 
128 /**
129  * Returns the key sequences associated with the given map and action.
130  */
131 DwtKeyMapMgr.prototype.getKeySequences =
132 function(mapName, action) {
133 	var keySeqs = [];
134 	for (var ks in this._map[mapName]) {
135 		if (this._map[mapName][ks] == action) {
136 			keySeqs.push(ks);
137 		}
138 	}
139 	return keySeqs;
140 };
141 
142 /**
143  * Allow the programmatic setting of a key sequence mapping for a given map
144  * 
145  * @param {string} 			mapName map name to affect
146  * @param {string} 			keySeq the key sequence to set
147  * @param {string|number} action the action code for the key sequence
148  */
149 DwtKeyMapMgr.prototype.setMapping =
150 function(mapName, keySeq, action) {
151 	this._map[mapName][keySeq] = action;
152 };
153 
154 /**
155  * Allow the programatting removal of a key sequence mapping for a given map
156  * 
157  * @param {string} mapName map name to affect
158  * @param {string} keySeq the key sequence to remove
159  */
160 DwtKeyMapMgr.prototype.removeMapping =
161 function(mapName, keySeq) {
162 	delete this._map[mapName][keySeq];
163 };
164 
165 /**
166  * Replace the key sequence for a given action in a keymap 
167  * 
168  * @param {string} mapName map name to affect
169  * @param {string} oldKeySeq the key sequence to replace
170  * @param {string} newKeySeq the new key sequence
171  */
172 DwtKeyMapMgr.prototype.replaceMapping =
173 function(mapName, oldKeySeq, newKeySeq) {
174 	var action = this._map[mapName][oldKeySeq];
175 	if (!action) return;
176 	this.removeMapping(mapName, oldKeySeq);
177 	this.setMapping(mapName, newKeySeq, action);
178 };
179 
180 DwtKeyMapMgr.prototype.setArg =
181 function(mapName, action, arg) {
182 	if (!this._args[mapName]) {
183 		this._args[mapName] = {};
184 	}
185 	this._args[mapName][action] = arg;
186 };
187 
188 DwtKeyMapMgr.prototype.removeArg =
189 function(mapName, action) {
190 	delete this._args[mapName][action];
191 };
192 
193 DwtKeyMapMgr.prototype.getArg =
194 function(mapName, action) {
195 	return this._args[mapName] ? this._args[mapName][action] : null;
196 };
197 
198 /**
199  * Reloads a given keymap
200  * 
201  * @param {string} mapName Name of the keymap to reload
202  */
203 DwtKeyMapMgr.prototype.reloadMap =
204 function(mapName) {
205 	this._fsas[mapName] = DwtKeyMapMgr.__buildFSA({}, this._map[mapName], mapName);
206 };
207 
208 /**
209  * Returns a list of maps that the given map inherits from.
210  *
211  * @param {string} mapName Name of the keymap to reload
212  */
213 DwtKeyMapMgr.prototype.getAncestors =
214 function(mapName, list) {
215     list = list || [];
216     var subMap = this._fsas[mapName];
217     var parents = subMap && subMap.inherit;
218     if (parents && parents.length) {
219         for (var i = 0; i < parents.length; i++) {
220             list.push(parents[i]);
221             list = this.getAncestors(parents[i], list);
222         }
223     }
224     return list;
225 };
226 
227 /**
228  * Returns true if the given element accepts text input.
229  * 
230  * @param element	[Element]		DOM element
231  */
232 DwtKeyMapMgr.isInputElement =
233 function(element) {
234 	if (!element) { return false; }
235 	// Check designMode in case we're in an HTML editor iframe
236 	var dm = element.ownerDocument ? element.ownerDocument.designMode : null;
237 	if (dm && (dm.toLowerCase() == "on")) { return true; }
238 
239 	var tag = element.tagName.toUpperCase();
240 	return (tag == "INPUT" || tag == "TEXTAREA");
241 };
242 
243 DwtKeyMapMgr.__buildFSA =
244 function(fsa, mapping, mapName) {
245 	for (var i in mapping) {
246 		// check for inheritance from other maps (in CSV list)
247 		if (i == DwtKeyMap.INHERIT) {
248 			fsa.inherit = mapping[i].split(/\s*,\s*/);
249 			continue;
250 		}
251 		 
252 		var keySeq = i.split(DwtKeyMap.SEP);
253 		var keySeqLen = keySeq.length;
254 		var tmpFsa = fsa;
255 		for (var j = 0; j < keySeqLen; j++) {
256 			var key = keySeq[j];
257 			//DBG.println(AjxDebug.DBG3, "Processing: " + key);
258 			
259 			if (!tmpFsa[key]) {
260 				tmpFsa[key] = {};	// first time visiting this key
261 			}
262 
263 			if (j == keySeqLen - 1) {
264 				/* We are at the last key in the sequence so we can bind the
265 				 * action code to it */
266 				//DBG.println(AjxDebug.DBG3, "BINDING: " + mapping[i]);
267 				tmpFsa[key].actionCode = mapping[i];
268 			} else {
269 				/* We have more keys in the sequence. If our subMap is null,
270 				 * then we need to create it to hold the new key sequences */
271 				if (!tmpFsa[key].subMap) {
272 					tmpFsa[key].subMap = {};
273 					//DBG.println(AjxDebug.DBG3, "NEW SUBMAP");
274 				}
275 					
276 				tmpFsa = tmpFsa[key].subMap;
277 			}			
278 		}
279 	}
280 	return fsa;
281 };
282 
283 DwtKeyMapMgr.prototype.__getInheritedActionCode =
284 function(keySeq, mapping, forceActionCode) {
285 	if (mapping.inherit && mapping.inherit.length) {
286 		var actionCode = null;
287 		var len = mapping.inherit.length;
288 		for (var i = 0; i < len; i++) {
289 			DBG.println(AjxDebug.DBG3, "checking inherited map: " + mapping.inherit[i]);
290 			actionCode = this.getActionCode(keySeq, mapping.inherit[i], forceActionCode);
291 			if (actionCode != null) {
292 				return actionCode;
293 			}
294 		}
295 	}
296 	return null;
297 };
298 
299 /**
300  * Returns true if the given key event has a modifier which makes it nonprintable.
301  * 
302  * @param ev	[Event]		key event
303  */
304 DwtKeyMapMgr.hasModifier =
305 function(ev) {
306 	return (ev.altKey || ev.ctrlKey || ev.metaKey);
307 };
308