Relicensed as Mozilla/LGPL/GPL.
[guacamole-common-js.git] / src / main / resources / oskeyboard.js
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacamole-common-js.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 // Guacamole namespace
39 var Guacamole = Guacamole || {};
40
41 /**
42  * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file,
43  * this object will download and use the XML to construct a clickable on-screen
44  * keyboard with its own key events.
45  * 
46  * @constructor
47  * @param {String} url The URL of an XML keyboard layout file.
48  */
49 Guacamole.OnScreenKeyboard = function(url) {
50
51     var allKeys = new Array();
52     var modifierState = new function() {};
53
54     function getKeySize(size) {
55         return (5*size) + "ex";
56     }
57
58     function getCapSize(size) {
59         return (5*size - 0.5) + "ex";
60     }
61
62     function clearModifiers() {
63
64         // Send key release events for all pressed modifiers
65         for (var k=0; k<allKeys.length; k++) {
66
67             var key = allKeys[k];
68             var cap = key.getCap();
69             var modifier = cap.getModifier();
70
71             if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
72                 key.release();
73
74         }
75
76     }
77
78     function setModifierReleased(modifier) {
79         if (isModifierActive(modifier))
80             modifierState[modifier]--;
81     }
82
83     function setModifierPressed(modifier) {
84         if (modifierState[modifier] == null)
85             modifierState[modifier] = 1;
86         else
87             modifierState[modifier]++;
88     }
89
90     function isModifierActive(modifier) {
91         if (modifierState[modifier] > 0)
92             return true;
93
94         return false;
95     }
96
97     function toggleModifierPressed(modifier) {
98         if (isModifierActive(modifier))
99             setModifierReleased(modifier);
100         else
101             setModifierPressed(modifier);
102     }
103
104     function refreshAllKeysState() {
105         for (var k=0; k<allKeys.length; k++)
106             allKeys[k].refreshState();
107     }
108
109     function Key(key) {
110
111         function Cap(cap) {
112
113             // Displayed text
114             var displayText = cap.textContent;
115             
116             // Keysym
117             var keysym = null;
118             if (cap.attributes["keysym"])
119                 keysym = parseInt(cap.attributes["keysym"].value);
120
121             // If keysym not specified, get keysym from display text.
122             else if (displayText.length == 1) {
123
124                 var charCode = displayText.charCodeAt(0);
125
126                 if (charCode >= 0x0000 && charCode <= 0x00FF)
127                     keysym = charCode;
128
129                 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
130                     keysym = 0x01000000 | charCode;
131             }
132
133             // Required modifiers for this keycap
134             var reqMod = null;
135             if (cap.attributes["if"])
136                 reqMod = cap.attributes["if"].value.split(",");
137
138
139             // Modifier represented by this keycap
140             var modifier = null;
141             if (cap.attributes["modifier"])
142                 modifier = cap.attributes["modifier"].value;
143             
144
145             // Whether this key is sticky (toggles)
146             // Currently only valid for modifiers.
147             var sticky = false;
148             if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
149                 sticky = true;
150
151             this.getDisplayText = function() {
152                 return cap.textContent;
153             };
154
155             this.getKeySym = function() {
156                 return keysym;
157             };
158
159             this.getRequiredModifiers = function() {
160                 return reqMod;
161             };
162
163             this.getModifier = function() {
164                 return modifier;
165             };
166
167             this.isSticky = function() {
168                 return sticky;
169             };
170
171         }
172
173         var size = null;
174         if (key.attributes["size"])
175             size = parseFloat(key.attributes["size"].value);
176
177         var caps = key.getElementsByTagName("cap");
178         var keycaps = new Array();
179         for (var i=0; i<caps.length; i++)
180             keycaps.push(new Cap(caps[i]));
181
182         var rowKey = document.createElement("div");
183         rowKey.className = "key";
184
185         var keyCap = document.createElement("div");
186         keyCap.className = "cap";
187         rowKey.appendChild(keyCap);
188
189
190         var STATE_RELEASED = 0;
191         var STATE_PRESSED = 1;
192         var state = STATE_RELEASED;
193
194         rowKey.isPressed = function() {
195             return state == STATE_PRESSED;
196         }
197
198         var currentCap = null;
199         function refreshState(modifier) {
200
201             // Find current cap
202             currentCap = null;
203             for (var j=0; j<keycaps.length; j++) {
204
205                 var keycap = keycaps[j];
206                 var required = keycap.getRequiredModifiers();
207
208                 var matches = true;
209
210                 // If modifiers required, make sure all modifiers are active
211                 if (required) {
212
213                     for (var k=0; k<required.length; k++) {
214                         if (!isModifierActive(required[k])) {
215                             matches = false;
216                             break;
217                         }
218                     }
219
220                 }
221
222                 if (matches)
223                     currentCap = keycap;
224
225             }
226
227             rowKey.className = "key";
228
229             if (currentCap.getModifier())
230                 rowKey.className += " modifier";
231
232             if (currentCap.isSticky())
233                 rowKey.className += " sticky";
234
235             if (isModifierActive(currentCap.getModifier()))
236                 rowKey.className += " active";
237
238             if (state == STATE_PRESSED)
239                 rowKey.className += " pressed";
240
241             keyCap.textContent = currentCap.getDisplayText();
242         }
243         rowKey.refreshState = refreshState;
244
245         rowKey.getCap = function() {
246             return currentCap;
247         };
248
249         refreshState();
250
251         // Set size
252         if (size) {
253             rowKey.style.width = getKeySize(size);
254             keyCap.style.width = getCapSize(size);
255         }
256
257
258
259         // Set pressed, if released
260         function press() {
261
262             if (state == STATE_RELEASED) {
263
264                 state = STATE_PRESSED;
265
266                 var keysym = currentCap.getKeySym();
267                 var modifier = currentCap.getModifier();
268                 var sticky = currentCap.isSticky();
269
270                 if (keyPressedHandler && keysym)
271                     keyPressedHandler(keysym);
272
273                 if (modifier) {
274
275                     // If sticky modifier, toggle
276                     if (sticky) 
277                         toggleModifierPressed(modifier);
278
279                     // Otherwise, just set on.
280                     else 
281                         setModifierPressed(modifier);
282
283                     refreshAllKeysState();
284                 }
285                 else
286                     refreshState();
287             }
288
289         }
290         rowKey.press = press;
291
292
293         // Set released, if pressed 
294         function release() {
295
296             if (state == STATE_PRESSED) {
297
298                 state = STATE_RELEASED;
299
300                 var keysym = currentCap.getKeySym();
301                 var modifier = currentCap.getModifier();
302                 var sticky = currentCap.isSticky();
303
304                 if (keyReleasedHandler && keysym)
305                     keyReleasedHandler(keysym);
306
307                 if (modifier) {
308
309                     // If not sticky modifier, release modifier
310                     if (!sticky) {
311                         setModifierReleased(modifier);
312                         refreshAllKeysState();
313                     }
314                     else
315                         refreshState();
316
317                 }
318                 else {
319                     refreshState();
320
321                     // If not a modifier, also release all pressed modifiers
322                     clearModifiers();
323                 }
324
325             }
326
327         }
328         rowKey.release = release;
329
330         // Toggle press/release states
331         function toggle() {
332             if (state == STATE_PRESSED)
333                 release();
334             else
335                 press();
336         }
337
338
339         // Send key press on mousedown
340         rowKey.onmousedown = function(e) {
341
342             e.stopPropagation();
343
344             var modifier = currentCap.getModifier();
345             var sticky = currentCap.isSticky();
346
347             // Toggle non-sticky modifiers
348             if (modifier && !sticky)
349                 toggle();
350
351             // Press all others
352             else
353                 press();
354
355             return false;
356         };
357
358         // Send key release on mouseup/out
359         rowKey.onmouseout =
360         rowKey.onmouseout =
361         rowKey.onmouseup = function(e) {
362
363             e.stopPropagation();
364
365             var modifier = currentCap.getModifier();
366             var sticky = currentCap.isSticky();
367
368             // Release non-modifiers and sticky modifiers
369             if (!modifier || sticky)
370                 release();
371
372             return false;
373         };
374
375         rowKey.onselectstart = function() { return false; };
376
377         return rowKey;
378
379     }
380
381     function Gap(gap) {
382
383         var keyboardGap = document.createElement("div");
384         keyboardGap.className = "gap";
385         keyboardGap.textContent = " ";
386
387         var size = null;
388         if (gap.attributes["size"])
389             size = parseFloat(gap.attributes["size"].value);
390
391         if (size) {
392             keyboardGap.style.width = getKeySize(size);
393             keyboardGap.style.height = getKeySize(size);
394         }
395
396         return keyboardGap;
397
398     }
399
400     function Row(row) {
401
402         var keyboardRow = document.createElement("div");
403         keyboardRow.className = "row";
404
405         var children = row.childNodes;
406         for (var j=0; j<children.length; j++) {
407             var child = children[j];
408
409             // <row> can contain <key> or <column>
410             if (child.tagName == "key") {
411                 var key = new Key(child);
412                 keyboardRow.appendChild(key);
413                 allKeys.push(key);
414             }
415             else if (child.tagName == "gap") {
416                 var gap = new Gap(child);
417                 keyboardRow.appendChild(gap);
418             }
419             else if (child.tagName == "column") {
420                 var col = new Column(child);
421                 keyboardRow.appendChild(col);
422             }
423
424         }
425
426         return keyboardRow;
427
428     }
429
430     function Column(col) {
431
432         var keyboardCol = document.createElement("div");
433         keyboardCol.className = "col";
434
435         var align = null;
436         if (col.attributes["align"])
437             align = col.attributes["align"].value;
438
439         var children = col.childNodes;
440         for (var j=0; j<children.length; j++) {
441             var child = children[j];
442
443             // <column> can only contain <row> 
444             if (child.tagName == "row") {
445                 var row = new Row(child);
446                 keyboardCol.appendChild(row);
447             }
448
449         }
450
451         if (align)
452             keyboardCol.style.textAlign = align;
453
454         return keyboardCol;
455
456     }
457
458
459
460     // Create keyboard
461     var keyboard = document.createElement("div");
462     keyboard.className = "keyboard";
463
464
465     // Retrieve keyboard XML
466     var xmlhttprequest = new XMLHttpRequest();
467     xmlhttprequest.open("GET", url, false);
468     xmlhttprequest.send(null);
469
470     var xml = xmlhttprequest.responseXML;
471
472     if (xml) {
473
474         // Parse document
475         var root = xml.documentElement;
476         if (root) {
477
478             var children = root.childNodes;
479             for (var i=0; i<children.length; i++) {
480                 var child = children[i];
481
482                 // <keyboard> can contain <row> or <column>
483                 if (child.tagName == "row") {
484                     keyboard.appendChild(new Row(child));
485                 }
486                 else if (child.tagName == "column") {
487                     keyboard.appendChild(new Column(child));
488                 }
489
490             }
491
492         }
493
494     }
495
496     var keyPressedHandler = null;
497     var keyReleasedHandler = null;
498
499     keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
500     keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
501
502     // Do not allow selection or mouse movement to propagate/register.
503     keyboard.onselectstart =
504     keyboard.onmousemove   =
505     keyboard.onmouseup     =
506     keyboard.onmousedown   =
507     function(e) {
508         e.stopPropagation();
509         return false;
510     };
511
512     return keyboard;
513
514 };
515