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