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