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