OLD | NEW |
| (Empty) |
1 /* This Source Code Form is subject to the terms of the Mozilla Public | |
2 * License, v. 2.0. If a copy of the MPL was not distributed with this | |
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
4 | |
5 Cu.import("resource://gre/modules/Services.jsm"); | |
6 | |
7 let validModifiers = Object.create(null); | |
8 validModifiers.ACCEL = null; | |
9 validModifiers.CTRL = "control"; | |
10 validModifiers.CONTROL = "control"; | |
11 validModifiers.SHIFT = "shift"; | |
12 validModifiers.ALT = "alt"; | |
13 validModifiers.META = "meta"; | |
14 | |
15 let bindingsKeys = null; | |
16 (function() | |
17 { | |
18 let request = new XMLHttpRequest(); | |
19 request.open("GET", "chrome://global/content/platformHTMLBindings.xml"); | |
20 request.addEventListener("load", () => | |
21 { | |
22 bindingsKeys = request.responseXML.getElementsByTagName("handler"); | |
23 }); | |
24 request.send(); | |
25 })(); | |
26 | |
27 | |
28 /** | |
29 * Sets the correct value of validModifiers.ACCEL. | |
30 */ | |
31 function initAccelKey() | |
32 { | |
33 validModifiers.ACCEL = "control"; | |
34 try | |
35 { | |
36 let accelKey = Services.prefs.getIntPref("ui.key.accelKey"); | |
37 if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_CONTROL) | |
38 validModifiers.ACCEL = "control"; | |
39 else if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_ALT) | |
40 validModifiers.ACCEL = "alt"; | |
41 else if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_META) | |
42 validModifiers.ACCEL = "meta"; | |
43 } | |
44 catch(e) | |
45 { | |
46 Cu.reportError(e); | |
47 } | |
48 } | |
49 | |
50 exports.KeySelector = KeySelector; | |
51 | |
52 /** | |
53 * This class provides capabilities to find and use available keyboard shortcut | |
54 * keys. | |
55 * @param {ChromeWindow} window the window where to look up existing shortcut | |
56 * keys | |
57 * @constructor | |
58 */ | |
59 function KeySelector(window) | |
60 { | |
61 this._initExistingShortcuts(window); | |
62 } | |
63 KeySelector.prototype = | |
64 { | |
65 /** | |
66 * Map listing existing shortcut keys as its keys. | |
67 * @type Object | |
68 */ | |
69 _existingShortcuts: null, | |
70 | |
71 /** | |
72 * Sets up _existingShortcuts property for a window. | |
73 */ | |
74 _initExistingShortcuts: function(/**ChromeWindow*/ window) | |
75 { | |
76 if (!validModifiers.ACCEL) | |
77 initAccelKey(); | |
78 | |
79 this._existingShortcuts = Object.create(null); | |
80 | |
81 let keys = Array.prototype.slice.apply(window.document.getElementsByTagName(
"key")); | |
82 if (bindingsKeys) | |
83 keys.push.apply(keys, bindingsKeys); | |
84 for (let i = 0; i < keys.length; i++) | |
85 { | |
86 let key = keys[i]; | |
87 let keyData = | |
88 { | |
89 shift: false, | |
90 meta: false, | |
91 alt: false, | |
92 control: false, | |
93 char: null, | |
94 code: null | |
95 }; | |
96 | |
97 let keyChar = key.getAttribute("key"); | |
98 if (keyChar && keyChar.length == 1) | |
99 keyData.char = keyChar.toUpperCase(); | |
100 | |
101 let keyCode = key.getAttribute("keycode"); | |
102 if (keyCode && "DOM_" + keyCode.toUpperCase() in Ci.nsIDOMKeyEvent) | |
103 keyData.code = Ci.nsIDOMKeyEvent["DOM_" + keyCode.toUpperCase()]; | |
104 | |
105 if (!keyData.char && !keyData.code) | |
106 continue; | |
107 | |
108 let keyModifiers = key.getAttribute("modifiers"); | |
109 if (keyModifiers) | |
110 for (let modifier of keyModifiers.toUpperCase().match(/\w+/g)) | |
111 if (modifier in validModifiers) | |
112 keyData[validModifiers[modifier]] = true; | |
113 | |
114 let canonical = [keyData.shift, keyData.meta, keyData.alt, keyData.control
, keyData.char || keyData.code].join(" "); | |
115 this._existingShortcuts[canonical] = true; | |
116 } | |
117 }, | |
118 | |
119 /** | |
120 * Selects a keyboard shortcut variant that isn't already taken, | |
121 * parses it into an object. | |
122 */ | |
123 selectKey: function(/**String*/ variants) /**Object*/ | |
124 { | |
125 for (let variant of variants.split(/\s*,\s*/)) | |
126 { | |
127 if (!variant) | |
128 continue; | |
129 | |
130 let keyData = | |
131 { | |
132 shift: false, | |
133 meta: false, | |
134 alt: false, | |
135 control: false, | |
136 char: null, | |
137 code: null, | |
138 codeName: null | |
139 }; | |
140 for (let part of variant.toUpperCase().split(/\s+/)) | |
141 { | |
142 if (part in validModifiers) | |
143 keyData[validModifiers[part]] = true; | |
144 else if (part.length == 1) | |
145 keyData.char = part; | |
146 else if ("DOM_VK_" + part in Ci.nsIDOMKeyEvent) | |
147 { | |
148 keyData.code = Ci.nsIDOMKeyEvent["DOM_VK_" + part]; | |
149 keyData.codeName = "VK_" + part; | |
150 } | |
151 } | |
152 | |
153 if (!keyData.char && !keyData.code) | |
154 continue; | |
155 | |
156 let canonical = [keyData.shift, keyData.meta, keyData.alt, keyData.control
, keyData.char || keyData.code].join(" "); | |
157 if (canonical in this._existingShortcuts) | |
158 continue; | |
159 | |
160 return keyData; | |
161 } | |
162 | |
163 return null; | |
164 } | |
165 }; | |
166 | |
167 /** | |
168 * Creates the text representation for a key. | |
169 * @static | |
170 */ | |
171 KeySelector.getTextForKey = function (/**Object*/ key) /**String*/ | |
172 { | |
173 if (!key) | |
174 return null; | |
175 | |
176 if (!("text" in key)) | |
177 { | |
178 key.text = null; | |
179 try | |
180 { | |
181 let stringBundle = Services.strings.createBundle("chrome://global-platform
/locale/platformKeys.properties"); | |
182 let parts = []; | |
183 if (key.control) | |
184 parts.push(stringBundle.GetStringFromName("VK_CONTROL")); | |
185 if (key.alt) | |
186 parts.push(stringBundle.GetStringFromName("VK_ALT")); | |
187 if (key.meta) | |
188 parts.push(stringBundle.GetStringFromName("VK_META")); | |
189 if (key.shift) | |
190 parts.push(stringBundle.GetStringFromName("VK_SHIFT")); | |
191 if (key.char) | |
192 parts.push(key.char.toUpperCase()); | |
193 else | |
194 { | |
195 let stringBundle2 = Services.strings.createBundle("chrome://global/local
e/keys.properties"); | |
196 parts.push(stringBundle2.GetStringFromName(key.codeName)); | |
197 } | |
198 key.text = parts.join(stringBundle.GetStringFromName("MODIFIER_SEPARATOR")
); | |
199 } | |
200 catch (e) | |
201 { | |
202 Cu.reportError(e); | |
203 return null; | |
204 } | |
205 } | |
206 return key.text; | |
207 }; | |
208 | |
209 /** | |
210 * Tests whether a keypress event matches the given key. | |
211 * @static | |
212 */ | |
213 KeySelector.matchesKey = function(/**Event*/ event, /**Object*/ key) /**Boolean*
/ | |
214 { | |
215 if (event.defaultPrevented || !key) | |
216 return false; | |
217 if (key.shift != event.shiftKey || key.alt != event.altKey) | |
218 return false; | |
219 if (key.meta != event.metaKey || key.control != event.ctrlKey) | |
220 return false; | |
221 | |
222 if (key.char && event.charCode && String.fromCharCode(event.charCode).toUpperC
ase() == key.char) | |
223 return true; | |
224 if (key.code && event.keyCode && event.keyCode == key.code) | |
225 return true; | |
226 return false; | |
227 }; | |
OLD | NEW |