1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Creates and loads a key map. 27 * @constructor 28 * @class 29 * This class provides the basic keyboard mappings for {@link Dwt} components. The 30 * key bindings are taken from the class AjxKeys, which is populated from a 31 * properties file. The identifiers used in the properties file must match 32 * those used here. 33 * 34 * @author Ross Dargahi 35 * 36 * @param {boolean} subclassInit if <code>true</code>, the sub-class will initialize 37 * 38 */ 39 DwtKeyMap = function(subclassInit) { 40 if (subclassInit) { return }; 41 42 this._map = {}; 43 this._args = {}; 44 this._checkedMap = {}; // cache results of _checkMap() 45 46 this._load(this._map, AjxKeys); 47 48 DwtKeyMap.MOD_ORDER[DwtKeyMap.ALT] = 1; 49 DwtKeyMap.MOD_ORDER[DwtKeyMap.CTRL] = 2; 50 DwtKeyMap.MOD_ORDER[DwtKeyMap.META] = 3; 51 DwtKeyMap.MOD_ORDER[DwtKeyMap.SHIFT] = 4; 52 }; 53 54 DwtKeyMap.prototype.isDwtKeyMap = true; 55 DwtKeyMap.prototype.toString = function() { return "DwtKeyMap"; }; 56 57 // This is how modifiers need to appear in *.keycode values 58 DwtKeyMap.ALT = 'Alt'; 59 DwtKeyMap.CTRL = 'Ctrl'; 60 DwtKeyMap.META = 'Meta'; 61 DwtKeyMap.SHIFT = 'Shift'; 62 63 DwtKeyMap.deserialize = 64 function(keymap) { 65 alert("DwtKeyMap.deserialize: NOT IMPLEMENTED"); 66 }; 67 68 DwtKeyMap.serialize = 69 function(keymap) { 70 alert("DwtKeyMap.serialize: NOT IMPLEMENTED"); 71 }; 72 73 DwtKeyMap.MAP_DIALOG = "dialog"; 74 DwtKeyMap.MAP_OPTION_DIALOG = "optionDialog"; 75 DwtKeyMap.MAP_BUTTON = "button"; 76 DwtKeyMap.MAP_LIST = "list"; 77 DwtKeyMap.MAP_MENU = "menu"; 78 DwtKeyMap.MAP_EDITOR = "editor"; 79 DwtKeyMap.MAP_TOOLBAR_HORIZ = "toolbarHorizontal"; 80 DwtKeyMap.MAP_TOOLBAR_VERT = "toolbarVertical"; 81 DwtKeyMap.MAP_TAB_VIEW = "tabView"; 82 DwtKeyMap.MAP_TREE = "tree"; 83 84 85 // Returns true if the given key is a modifier. The list of modifier keys is 86 // taken from the AjxKeys properties file. 87 DwtKeyMap.IS_MODIFIER = {}; 88 89 // Order filled in by DwtKeyMapMgr._processKeyDefs() 90 DwtKeyMap.MOD_ORDER = {}; 91 92 // Key names 93 DwtKeyMap.ARROW_DOWN = "ArrowDown"; 94 DwtKeyMap.ARROW_LEFT = "ArrowLeft"; 95 DwtKeyMap.ARROW_RIGHT = "ArrowRight"; 96 DwtKeyMap.ARROW_UP = "ArrowUp"; 97 DwtKeyMap.BACKSLASH = "Backslash"; 98 DwtKeyMap.BACKSPACE = "Backspace"; 99 DwtKeyMap.COMMA = "Comma"; 100 DwtKeyMap.DELETE = "Del"; 101 DwtKeyMap.END = "End"; 102 DwtKeyMap.ENTER = "Enter"; 103 DwtKeyMap.ESC = "Esc"; 104 DwtKeyMap.HOME = "Home"; 105 DwtKeyMap.PGDOWN = "PgDown"; 106 DwtKeyMap.PGUP = "PgUp"; 107 DwtKeyMap.SEMICOLON = "Semicolon"; 108 DwtKeyMap.SPACE = "Space"; 109 DwtKeyMap.TAB = "Tab"; 110 111 // Action codes 112 DwtKeyMap.ADD_SELECT_NEXT = "AddNext"; 113 DwtKeyMap.ADD_SELECT_PREV = "AddPrevious"; 114 DwtKeyMap.CANCEL = "Cancel"; 115 DwtKeyMap.COLLAPSE = "Collapse"; 116 DwtKeyMap.DBLCLICK = "DoubleClick"; 117 DwtKeyMap.DELETE = "Delete"; 118 DwtKeyMap.EXPAND = "Expand"; 119 DwtKeyMap.GOTO_TAB = "GoToTab"; 120 DwtKeyMap.HEADER1 = "Header1"; 121 DwtKeyMap.HEADER2 = "Header2"; 122 DwtKeyMap.HEADER3 = "Header3"; 123 DwtKeyMap.HEADER4 = "Header4"; 124 DwtKeyMap.HEADER5 = "Header5"; 125 DwtKeyMap.HEADER6 = "Header6"; 126 DwtKeyMap.INSERT_LINK = "InsertLink"; 127 DwtKeyMap.JUSTIFY_CENTER = "CenterJustify"; 128 DwtKeyMap.JUSTIFY_LEFT = "LeftJustify"; 129 DwtKeyMap.JUSTIFY_RIGHT = "RightJustify"; 130 DwtKeyMap.NEXT = "Next"; 131 DwtKeyMap.NEXT_TAB = "NextTab"; 132 DwtKeyMap.NO = "No"; 133 DwtKeyMap.PAGE_UP = "PageUp"; 134 DwtKeyMap.PAGE_DOWN = "PageDown"; 135 DwtKeyMap.PARENTMENU = "ParentMenu"; 136 DwtKeyMap.PREV = "Previous"; 137 DwtKeyMap.PREV_TAB = "PreviousTab"; 138 DwtKeyMap.SELECT_ALL = "SelectAll"; 139 DwtKeyMap.SELECT = "Select"; 140 DwtKeyMap.SELECT_CURRENT = "SelectCurrent"; 141 DwtKeyMap.SELECT_FIRST = "SelectFirst"; 142 DwtKeyMap.SELECT_LAST = "SelectLast"; 143 DwtKeyMap.SELECT_NEXT = "SelectNext"; 144 DwtKeyMap.SELECT_PREV = "SelectPrevious"; 145 DwtKeyMap.SUBMENU = "SubMenu"; 146 DwtKeyMap.SWITCH_MODE = "SwitchMode"; 147 DwtKeyMap.TEXT_BOLD = "Bold"; 148 DwtKeyMap.TEXT_ITALIC = "Italic"; 149 DwtKeyMap.TEXT_UNDERLINE = "Underline"; 150 DwtKeyMap.TEXT_STRIKETHRU = "Strikethru"; 151 DwtKeyMap.YES = "Yes"; 152 153 DwtKeyMap.GOTO_TAB_RE = new RegExp(DwtKeyMap.GOTO_TAB + "(\\d+)"); 154 155 DwtKeyMap.JOIN = "+"; // Modifier join character 156 DwtKeyMap.SEP = ","; // Key separator 157 DwtKeyMap.INHERIT = "INHERIT"; // Inherit keyword. 158 159 DwtKeyMap.prototype.getMap = 160 function() { 161 return this._map; 162 }; 163 164 /** 165 * Converts a properties representation of shortcuts into a hash. The 166 * properties version is actually a reverse map of what we want, so we 167 * have to swap keys and values. Handles platform-specific shortcuts, 168 * and inheritance. The properties version is made available via a 169 * servlet. 170 * 171 * @param {hash} map the hash to populate with shortcuts 172 * @param {hash} keys the properties version of shortcuts 173 * 174 * @private 175 */ 176 DwtKeyMap.prototype._load = 177 function(map, keys) { 178 179 // preprocess for platform-specific bindings, and sanitize for misuse of {modifier} in keycode 180 var curPlatform = AjxEnv.platform.toLowerCase(); 181 for (var propName in keys) { 182 var parts = propName.split("."); 183 var last = parts[parts.length - 1]; 184 // if we find the right platform-specific binding, promote it to be the main one 185 if (last === "win" || last === "mac" || last === "linux") { 186 if (last === curPlatform) { 187 var baseKey = parts.slice(0, parts.length - 1).join("."); 188 keys[baseKey] = keys[propName]; 189 } 190 keys[propName] = null; 191 } 192 // clean up in case someone put something like {ctrl} in keycode rather than display 193 var propValue = AjxStringUtil.trim(keys[propName]); 194 if (parts[2] && parts[2] === 'keycode' && propValue && propValue.indexOf('{') !== -1) { 195 keys[propName] = propValue.replace(/\{(\w)(\w+)\}/g, function(m, first, rest) { 196 return first.toUpperCase() + rest; 197 }); 198 } 199 } 200 201 for (var propName in keys) { 202 var propValue = AjxStringUtil.trim(keys[propName]); 203 if (!propValue || (typeof keys[propName] != "string")) { continue; } 204 var parts = propName.split("."); 205 var field = parts[parts.length - 1]; 206 var isMap = (parts.length == 2); 207 var action = isMap ? null : parts[1]; 208 if (parts[0] == "keys") { 209 this._processKeyDef(action, field, propValue); 210 continue; 211 } 212 if (field != DwtKeyMap.INHERIT && field != "keycode") { continue; } 213 var mapName = parts[0]; 214 if ((this._checkedMap[mapName] === false) || 215 (!this._checkedMap[mapName] && !this._checkMap(mapName))) { continue; } 216 if (!map[mapName]) { 217 map[mapName] = {}; 218 } 219 if (!this._checkAction(mapName, action)) { continue; } 220 var keySequences = propValue.split(/\s*;\s*/); 221 for (var i = 0; i < keySequences.length; i++) { 222 var ks = this._canonicalize(keySequences[i]); 223 if (field == DwtKeyMap.INHERIT) { 224 var parents = ks.split(/\s*,\s*/); 225 var parents1 = []; 226 for (var p = 0; p < parents.length; p++) { 227 parents1[p] = parents[p]; 228 } 229 map[mapName][parts[1]] = parents1.join(","); 230 } else if (field == "keycode") { 231 map[mapName][ks] = action; 232 } 233 } 234 } 235 }; 236 237 /** 238 * Returns true if this map is valid. This class always returns true, 239 * but subclasses may override to do more checking. 240 * 241 * @param {string} mapName the name of map 242 * 243 * @private 244 */ 245 DwtKeyMap.prototype._checkMap = 246 function(mapName) { 247 var result = true; 248 this._checkedMap[mapName] = result; 249 return result; 250 }; 251 252 /** 253 * Checks if this action is valid. This class always returns <code>true</code>, 254 * but subclasses may override to do more checking. 255 * 256 * @param {string} mapName the name of map 257 * @param {string} action the action to check 258 * @param {boolean} <code>true</code> if this action is valid. 259 * @private 260 */ 261 DwtKeyMap.prototype._checkAction = 262 function(mapName, action) { 263 return true; 264 }; 265 266 /** 267 * Sets up constants for a modifier key as described in a properties file. 268 * 269 * @param {string} key the ctrl, alt, shift, or meta 270 * @param {string} field the display or keycode 271 * @param {string|number} value the property value 272 * 273 * @private 274 */ 275 DwtKeyMap.prototype._processKeyDef = 276 function(key, field, value) { 277 if (!key || !field || !value) { return; } 278 if (field == "keycode") { 279 DwtKeyMap.IS_MODIFIER[value] = true; 280 } 281 }; 282 283 /** 284 * Ensures a predictable order for the modifiers in a key sequence: 285 * <pre> 286 * Alt Ctrl Meta Shift 287 * </pre> 288 * 289 * Example: "Shift+Ctrl+U" will be transformed into "Ctrl+Shift+U" 290 * 291 * @param {String} ks the key sequence 292 * 293 * @private 294 */ 295 DwtKeyMap.prototype._canonicalize = 296 function(ks) { 297 var keys = ks.split(DwtKeyMap.SEP); 298 var result = []; 299 for (var i = 0; i < keys.length; i++) { 300 var key = keys[i]; 301 var parts = key.split(DwtKeyMap.JOIN); 302 if (parts.length > 2) { 303 var mods = parts.slice(0, parts.length - 1); 304 mods.sort(function(a, b) { 305 var sortA = DwtKeyMap.MOD_ORDER[a] || 0; 306 var sortB = DwtKeyMap.MOD_ORDER[b] || 0; 307 return Number(sortA - sortB); 308 }); 309 mods.push(parts[parts.length - 1]); 310 result.push(mods.join(DwtKeyMap.JOIN)); 311 } else { 312 result.push(key); 313 } 314 } 315 return result.join(","); 316 }; 317