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