Fix errors in IE due to lack of textContent support.
[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             if (!displayText) displayText = cap.text;
116             
117             // Keysym
118             var keysym = null;
119             if (cap.attributes["keysym"])
120                 keysym = parseInt(cap.attributes["keysym"].value);
121
122             // If keysym not specified, get keysym from display text.
123             else if (displayText.length == 1) {
124
125                 var charCode = displayText.charCodeAt(0);
126
127                 if (charCode >= 0x0000 && charCode <= 0x00FF)
128                     keysym = charCode;
129
130                 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
131                     keysym = 0x01000000 | charCode;
132             }
133
134             // Required modifiers for this keycap
135             var reqMod = null;
136             if (cap.attributes["if"])
137                 reqMod = cap.attributes["if"].value.split(",");
138
139
140             // Modifier represented by this keycap
141             var modifier = null;
142             if (cap.attributes["modifier"])
143                 modifier = cap.attributes["modifier"].value;
144             
145
146             // Whether this key is sticky (toggles)
147             // Currently only valid for modifiers.
148             var sticky = false;
149             if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
150                 sticky = true;
151
152             this.getDisplayText = function() {
153                 return displayText;
154             };
155
156             this.getKeySym = function() {
157                 return keysym;
158             };
159
160             this.getRequiredModifiers = function() {
161                 return reqMod;
162             };
163
164             this.getModifier = function() {
165                 return modifier;
166             };
167
168             this.isSticky = function() {
169                 return sticky;
170             };
171
172         }
173
174         var size = null;
175         if (key.attributes["size"])
176             size = parseFloat(key.attributes["size"].value);
177
178         var caps = key.getElementsByTagName("cap");
179         var keycaps = new Array();
180         for (var i=0; i<caps.length; i++)
181             keycaps.push(new Cap(caps[i]));
182
183         var rowKey = document.createElement("div");
184         rowKey.className = "key";
185
186         var keyCap = document.createElement("div");
187         keyCap.className = "cap";
188         rowKey.appendChild(keyCap);
189
190
191         var STATE_RELEASED = 0;
192         var STATE_PRESSED = 1;
193         var state = STATE_RELEASED;
194
195         rowKey.isPressed = function() {
196             return state == STATE_PRESSED;
197         }
198
199         var currentCap = null;
200         function refreshState(modifier) {
201
202             // Find current cap
203             currentCap = null;
204             for (var j=0; j<keycaps.length; j++) {
205
206                 var keycap = keycaps[j];
207                 var required = keycap.getRequiredModifiers();
208
209                 var matches = true;
210
211                 // If modifiers required, make sure all modifiers are active
212                 if (required) {
213
214                     for (var k=0; k<required.length; k++) {
215                         if (!isModifierActive(required[k])) {
216                             matches = false;
217                             break;
218                         }
219                     }
220
221                 }
222
223                 if (matches)
224                     currentCap = keycap;
225
226             }
227
228             rowKey.className = "key";
229
230             if (currentCap.getModifier())
231                 rowKey.className += " modifier";
232
233             if (currentCap.isSticky())
234                 rowKey.className += " sticky";
235
236             if (isModifierActive(currentCap.getModifier()))
237                 rowKey.className += " active";
238
239             if (state == STATE_PRESSED)
240                 rowKey.className += " pressed";
241
242             keyCap.textContent = currentCap.getDisplayText();
243         }
244         rowKey.refreshState = refreshState;
245
246         rowKey.getCap = function() {
247             return currentCap;
248         };
249
250         refreshState();
251
252         // Set size
253         if (size) {
254             rowKey.style.width = getKeySize(size);
255             keyCap.style.width = getCapSize(size);
256         }
257
258
259
260         // Set pressed, if released
261         function press() {
262
263             if (state == STATE_RELEASED) {
264
265                 state = STATE_PRESSED;
266
267                 var keysym = currentCap.getKeySym();
268                 var modifier = currentCap.getModifier();
269                 var sticky = currentCap.isSticky();
270
271                 if (keyPressedHandler && keysym)
272                     keyPressedHandler(keysym);
273
274                 if (modifier) {
275
276                     // If sticky modifier, toggle
277                     if (sticky) 
278                         toggleModifierPressed(modifier);
279
280                     // Otherwise, just set on.
281                     else 
282                         setModifierPressed(modifier);
283
284                     refreshAllKeysState();
285                 }
286                 else
287                     refreshState();
288             }
289
290         }
291         rowKey.press = press;
292
293
294         // Set released, if pressed 
295         function release() {
296
297             if (state == STATE_PRESSED) {
298
299                 state = STATE_RELEASED;
300
301                 var keysym = currentCap.getKeySym();
302                 var modifier = currentCap.getModifier();
303                 var sticky = currentCap.isSticky();
304
305                 if (keyReleasedHandler && keysym)
306                     keyReleasedHandler(keysym);
307
308                 if (modifier) {
309
310                     // If not sticky modifier, release modifier
311                     if (!sticky) {
312                         setModifierReleased(modifier);
313                         refreshAllKeysState();
314                     }
315                     else
316                         refreshState();
317
318                 }
319                 else {
320                     refreshState();
321
322                     // If not a modifier, also release all pressed modifiers
323                     clearModifiers();
324                 }
325
326             }
327
328         }
329         rowKey.release = release;
330
331         // Toggle press/release states
332         function toggle() {
333             if (state == STATE_PRESSED)
334                 release();
335             else
336                 press();
337         }
338
339
340         // Send key press on mousedown
341         rowKey.onmousedown = function(e) {
342
343             e.stopPropagation();
344
345             var modifier = currentCap.getModifier();
346             var sticky = currentCap.isSticky();
347
348             // Toggle non-sticky modifiers
349             if (modifier && !sticky)
350                 toggle();
351
352             // Press all others
353             else
354                 press();
355
356             return false;
357         };
358
359         // Send key release on mouseup/out
360         rowKey.onmouseout =
361         rowKey.onmouseout =
362         rowKey.onmouseup = function(e) {
363
364             e.stopPropagation();
365
366             var modifier = currentCap.getModifier();
367             var sticky = currentCap.isSticky();
368
369             // Release non-modifiers and sticky modifiers
370             if (!modifier || sticky)
371                 release();
372
373             return false;
374         };
375
376         rowKey.onselectstart = function() { return false; };
377
378         return rowKey;
379
380     }
381
382     function Gap(gap) {
383
384         var keyboardGap = document.createElement("div");
385         keyboardGap.className = "gap";
386         keyboardGap.textContent = " ";
387
388         var size = null;
389         if (gap.attributes["size"])
390             size = parseFloat(gap.attributes["size"].value);
391
392         if (size) {
393             keyboardGap.style.width = getKeySize(size);
394             keyboardGap.style.height = getKeySize(size);
395         }
396
397         return keyboardGap;
398
399     }
400
401     function Row(row) {
402
403         var keyboardRow = document.createElement("div");
404         keyboardRow.className = "row";
405
406         var children = row.childNodes;
407         for (var j=0; j<children.length; j++) {
408             var child = children[j];
409
410             // <row> can contain <key> or <column>
411             if (child.tagName == "key") {
412                 var key = new Key(child);
413                 keyboardRow.appendChild(key);
414                 allKeys.push(key);
415             }
416             else if (child.tagName == "gap") {
417                 var gap = new Gap(child);
418                 keyboardRow.appendChild(gap);
419             }
420             else if (child.tagName == "column") {
421                 var col = new Column(child);
422                 keyboardRow.appendChild(col);
423             }
424
425         }
426
427         return keyboardRow;
428
429     }
430
431     function Column(col) {
432
433         var keyboardCol = document.createElement("div");
434         keyboardCol.className = "col";
435
436         var align = null;
437         if (col.attributes["align"])
438             align = col.attributes["align"].value;
439
440         var children = col.childNodes;
441         for (var j=0; j<children.length; j++) {
442             var child = children[j];
443
444             // <column> can only contain <row> 
445             if (child.tagName == "row") {
446                 var row = new Row(child);
447                 keyboardCol.appendChild(row);
448             }
449
450         }
451
452         if (align)
453             keyboardCol.style.textAlign = align;
454
455         return keyboardCol;
456
457     }
458
459
460
461     // Create keyboard
462     var keyboard = document.createElement("div");
463     keyboard.className = "keyboard";
464
465
466     // Retrieve keyboard XML
467     var xmlhttprequest = new XMLHttpRequest();
468     xmlhttprequest.open("GET", url, false);
469     xmlhttprequest.send(null);
470
471     var xml = xmlhttprequest.responseXML;
472
473     if (xml) {
474
475         // Parse document
476         var root = xml.documentElement;
477         if (root) {
478
479             var children = root.childNodes;
480             for (var i=0; i<children.length; i++) {
481                 var child = children[i];
482
483                 // <keyboard> can contain <row> or <column>
484                 if (child.tagName == "row") {
485                     keyboard.appendChild(new Row(child));
486                 }
487                 else if (child.tagName == "column") {
488                     keyboard.appendChild(new Column(child));
489                 }
490
491             }
492
493         }
494
495     }
496
497     var keyPressedHandler = null;
498     var keyReleasedHandler = null;
499
500     keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
501     keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
502
503     // Do not allow selection or mouse movement to propagate/register.
504     keyboard.onselectstart =
505     keyboard.onmousemove   =
506     keyboard.onmouseup     =
507     keyboard.onmousedown   =
508     function(e) {
509         e.stopPropagation();
510         return false;
511     };
512
513     return keyboard;
514
515 };
516