Resizable elements.
[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 scaledElements = [];
52
53     function ScaledElement(element, width, height, scaleFont) {
54
55         this.width = width;
56         this.height = height;
57
58         this.scale = function(pixels) {
59             element.style.width      = width  * pixels + "px";
60             element.style.height     = height * pixels + "px";
61
62             if (scaleFont) {
63                 element.style.lineHeight = height * pixels + "px";
64                 element.style.fontSize   = pixels + "px";
65             }
66         }
67
68     }
69
70     // For each child of element, call handler defined in next
71     function parseChildren(element, next) {
72
73         var children = element.childNodes;
74         for (var i=0; i<children.length; i++) {
75
76             // Get child node
77             var child = children[i];
78
79             // Do not parse text nodes
80             if (!child.tagName)
81                 continue;
82
83             // Get handler for node
84             var handler = next[child.tagName];
85
86             // Call handler if defined
87             if (handler)
88                 handler(child);
89
90             // Throw exception if no handler
91             else
92                 throw new Error(
93                       "Unexpected " + child.tagName
94                     + " within " + element.tagName
95                 );
96
97         }
98
99     }
100
101     // Create keyboard
102     var keyboard = document.createElement("div");
103     keyboard.className = "guacamole-keyboard";
104
105     // Retrieve keyboard XML
106     var xmlhttprequest = new XMLHttpRequest();
107     xmlhttprequest.open("GET", url, false);
108     xmlhttprequest.send(null);
109
110     var xml = xmlhttprequest.responseXML;
111
112     if (xml) {
113
114         function parse_row(e) {
115             
116             var row = document.createElement("div");
117             row.className = "guacamole-keyboard-row";
118
119             parseChildren(e, {
120                 
121                 "column": function(e) {
122                     row.appendChild(parse_column(e));
123                 },
124                 
125                 "gap": function parse_gap(e) {
126
127                     // Get attributes
128                     var gap_size = e.attributes["size"];
129
130                     // Create element
131                     var gap = document.createElement("div");
132                     gap.className = "guacamole-keyboard-gap";
133
134                     // Set gap size
135                     var gap_units = 1;
136                     if (gap_size)
137                         gap_units = parseFloat(gap_size.value);
138
139                     scaledElements.push(new ScaledElement(gap, gap_units, gap_units));
140                     row.appendChild(gap);
141
142                 },
143                 
144                 "key": function parse_key(e) {
145                     
146
147                     // Get attributes
148                     var key_size = e.attributes["size"];
149
150                     // Create element
151                     var key_element = document.createElement("div");
152                     key_element.className = "guacamole-keyboard-key";
153                     
154                     // Create cap
155                     var cap_element = document.createElement("div");
156                     cap_element.className = "guacamole-keyboard-cap";
157                     key_element.appendChild(cap_element);
158
159                     // Set key size
160                     var key_units = 1;
161                     if (key_size)
162                         key_units = parseFloat(key_size.value);
163
164                     parseChildren(e, {
165                         "cap": function cap(e) {
166
167                             // Get attributes
168                             var required = e.attributes["if"];
169                             var modifier = e.attributes["modifier"];
170                             var keysym   = e.attributes["keysym"];
171                             var sticky   = e.attributes["sticky"];
172                             
173                             // Get content of key cap
174                             var content = e.textContent;
175                             
176                             // If no requirements, then show cap by default
177                             if (!required) {
178                                 cap_element.textContent = content;
179                             }
180
181                         }
182                     });
183
184                     scaledElements.push(new ScaledElement(key_element, key_units, 1, true));
185                     row.appendChild(key_element);
186
187                 }
188                 
189             });
190
191             return row;
192
193         }
194
195         function parse_column(e) {
196             
197             var col = document.createElement("div");
198             col.className = "guacamole-keyboard-column";
199
200             var align = col.attributes["align"];
201
202             if (align)
203                 col.style.textAlign = align.value;
204
205             // Columns can only contain rows
206             parseChildren(e, {
207                 "row": function(e) {
208                     col.appendChild(parse_row(e));
209                 }
210             });
211
212             return col;
213
214         }
215
216
217         // Parse document
218         var keyboard_element = xml.documentElement;
219         if (keyboard_element.tagName != "keyboard")
220             throw new Error("Root element must be keyboard");
221
222         // Get attributes
223         if (!keyboard_element.attributes["size"])
224             throw new Error("size attribute is required for keyboard");
225         
226         var keyboard_size = parseFloat(keyboard_element.attributes["size"].value);
227         
228         parseChildren(keyboard_element, {
229             
230             "row": function(e) {
231                 keyboard.appendChild(parse_row(e));
232             },
233             
234             "column": function(e) {
235                 keyboard.appendChild(parse_column(e));
236             }
237             
238         });
239
240     }
241
242     // Do not allow selection or mouse movement to propagate/register.
243     keyboard.onselectstart =
244     keyboard.onmousemove   =
245     keyboard.onmouseup     =
246     keyboard.onmousedown   =
247     function(e) {
248         e.stopPropagation();
249         return false;
250     };
251
252
253     this.onkeypressed  = null;
254     this.onkeyreleased = null;
255
256     this.getElement = function() {
257         return keyboard;
258     };
259
260     this.resize = function(width) {
261
262         // Get pixel size of a unit
263         var unit = width / keyboard_size;
264
265         // Resize all scaled elements
266         for (var i=0; i<scaledElements.length; i++) {
267             var scaledElement = scaledElements[i];
268             scaledElement.scale(unit)
269         }
270
271     };
272
273 };
274
275 Guacamole.OnScreenKeyboard.Key = function() {
276
277     /**
278      * Width of the key, relative to the size of the keyboard.
279      */
280     this.size = 1;
281
282     /**
283      * Whether this key is currently pressed.
284      */
285     this.pressed = false;
286
287     /**
288      * An associative map of all caps by modifier.
289      */
290     this.caps = {};
291
292 }
293
294 Guacamole.OnScreenKeyboard.Cap = function(text, keycode, modifier) {
295     
296     /**
297      * Modifier represented by this keycap
298      */
299     this.modifier = 0;
300     
301     /**
302      * The text to be displayed within this keycap
303      */
304     this.text = text;
305
306     /**
307      * The keycode this cap sends when its associated key is pressed/released
308      */
309     this.keycode = keycode;
310
311     // Set modifier if provided
312     if (modifier) this.modifier = modifier;
313     
314 }