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