source: OpenRLabs-Git/web2py/applications/rlabs/static/js/guacamole-common-js/modules/OnScreenKeyboard.js

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100644
File size: 30.7 KB
Line 
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20var Guacamole = Guacamole || {};
21
22/**
23 * Dynamic on-screen keyboard. Given the layout object for an on-screen
24 * keyboard, this object will construct a clickable on-screen keyboard with its
25 * own key events.
26 *
27 * @constructor
28 * @param {Guacamole.OnScreenKeyboard.Layout} layout
29 *     The layout of the on-screen keyboard to display.
30 */
31Guacamole.OnScreenKeyboard = function(layout) {
32
33    /**
34     * Reference to this Guacamole.OnScreenKeyboard.
35     *
36     * @private
37     * @type {Guacamole.OnScreenKeyboard}
38     */
39    var osk = this;
40
41    /**
42     * Map of currently-set modifiers to the keysym associated with their
43     * original press. When the modifier is cleared, this keysym must be
44     * released.
45     *
46     * @private
47     * @type {Object.<String, Number>}
48     */
49    var modifierKeysyms = {};
50
51    /**
52     * Map of all key names to their current pressed states. If a key is not
53     * pressed, it may not be in this map at all, but all pressed keys will
54     * have a corresponding mapping to true.
55     *
56     * @private
57     * @type {Object.<String, Boolean>}
58     */
59    var pressed = {};
60
61    /**
62     * All scalable elements which are part of the on-screen keyboard. Each
63     * scalable element is carefully controlled to ensure the interface layout
64     * and sizing remains constant, even on browsers that would otherwise
65     * experience rounding error due to unit conversions.
66     *
67     * @private
68     * @type {ScaledElement[]}
69     */
70    var scaledElements = [];
71
72    /**
73     * Adds a CSS class to an element.
74     *
75     * @private
76     * @function
77     * @param {Element} element
78     *     The element to add a class to.
79     *
80     * @param {String} classname
81     *     The name of the class to add.
82     */
83    var addClass = function addClass(element, classname) {
84
85        // If classList supported, use that
86        if (element.classList)
87            element.classList.add(classname);
88
89        // Otherwise, simply append the class
90        else
91            element.className += " " + classname;
92
93    };
94
95    /**
96     * Removes a CSS class from an element.
97     *
98     * @private
99     * @function
100     * @param {Element} element
101     *     The element to remove a class from.
102     *
103     * @param {String} classname
104     *     The name of the class to remove.
105     */
106    var removeClass = function removeClass(element, classname) {
107
108        // If classList supported, use that
109        if (element.classList)
110            element.classList.remove(classname);
111
112        // Otherwise, manually filter out classes with given name
113        else {
114            element.className = element.className.replace(/([^ ]+)[ ]*/g,
115                function removeMatchingClasses(match, testClassname) {
116
117                    // If same class, remove
118                    if (testClassname === classname)
119                        return "";
120
121                    // Otherwise, allow
122                    return match;
123                   
124                }
125            );
126        }
127
128    };
129
130    /**
131     * Counter of mouse events to ignore. This decremented by mousemove, and
132     * while non-zero, mouse events will have no effect.
133     *
134     * @private
135     * @type {Number}
136     */
137    var ignoreMouse = 0;
138
139    /**
140     * Ignores all pending mouse events when touch events are the apparent
141     * source. Mouse events are ignored until at least touchMouseThreshold
142     * mouse events occur without corresponding touch events.
143     *
144     * @private
145     */
146    var ignorePendingMouseEvents = function ignorePendingMouseEvents() {
147        ignoreMouse = osk.touchMouseThreshold;
148    };
149
150    /**
151     * An element whose dimensions are maintained according to an arbitrary
152     * scale. The conversion factor for these arbitrary units to pixels is
153     * provided later via a call to scale().
154     *
155     * @private
156     * @constructor
157     * @param {Element} element
158     *     The element whose scale should be maintained.
159     *
160     * @param {Number} width
161     *     The width of the element, in arbitrary units, relative to other
162     *     ScaledElements.
163     *
164     * @param {Number} height
165     *     The height of the element, in arbitrary units, relative to other
166     *     ScaledElements.
167     *     
168     * @param {Boolean} [scaleFont=false]
169     *     Whether the line height and font size should be scaled as well.
170     */
171    var ScaledElement = function ScaledElement(element, width, height, scaleFont) {
172
173        /**
174         * The width of this ScaledElement, in arbitrary units, relative to
175         * other ScaledElements.
176         *
177         * @type {Number}
178         */
179         this.width = width;
180
181        /**
182         * The height of this ScaledElement, in arbitrary units, relative to
183         * other ScaledElements.
184         *
185         * @type {Number}
186         */
187         this.height = height;
188 
189        /**
190         * Resizes the associated element, updating its dimensions according to
191         * the given pixels per unit.
192         *
193         * @param {Number} pixels
194         *     The number of pixels to assign per arbitrary unit.
195         */
196        this.scale = function(pixels) {
197
198            // Scale element width/height
199            element.style.width  = (width  * pixels) + "px";
200            element.style.height = (height * pixels) + "px";
201
202            // Scale font, if requested
203            if (scaleFont) {
204                element.style.lineHeight = (height * pixels) + "px";
205                element.style.fontSize   = pixels + "px";
206            }
207
208        };
209
210    };
211
212    /**
213     * Returns whether all modifiers having the given names are currently
214     * active.
215     *
216     * @private
217     * @param {String[]} names
218     *     The names of all modifiers to test.
219     *
220     * @returns {Boolean}
221     *     true if all specified modifiers are pressed, false otherwise.
222     */
223    var modifiersPressed = function modifiersPressed(names) {
224
225        // If any required modifiers are not pressed, return false
226        for (var i=0; i < names.length; i++) {
227
228            // Test whether current modifier is pressed
229            var name = names[i];
230            if (!(name in modifierKeysyms))
231                return false;
232
233        }
234
235        // Otherwise, all required modifiers are pressed
236        return true;
237
238    };
239
240    /**
241     * Returns the single matching Key object associated with the key of the
242     * given name, where that Key object's requirements (such as pressed
243     * modifiers) are all currently satisfied.
244     *
245     * @private
246     * @param {String} keyName
247     *     The name of the key to retrieve.
248     *
249     * @returns {Guacamole.OnScreenKeyboard.Key}
250     *     The Key object associated with the given name, where that object's
251     *     requirements are all currently satisfied, or null if no such Key
252     *     can be found.
253     */
254    var getActiveKey = function getActiveKey(keyName) {
255
256        // Get key array for given name
257        var keys = osk.keys[keyName];
258        if (!keys)
259            return null;
260
261        // Find last matching key
262        for (var i = keys.length - 1; i >= 0; i--) {
263
264            // Get candidate key
265            var candidate = keys[i];
266
267            // If all required modifiers are pressed, use that key
268            if (modifiersPressed(candidate.requires))
269                return candidate;
270
271        }
272
273        // No valid key
274        return null;
275
276    };
277
278    /**
279     * Presses the key having the given name, updating the associated key
280     * element with the "guac-keyboard-pressed" CSS class. If the key is
281     * already pressed, this function has no effect.
282     *
283     * @private
284     * @param {String} keyName
285     *     The name of the key to press.
286     *
287     * @param {String} keyElement
288     *     The element associated with the given key.
289     */
290    var press = function press(keyName, keyElement) {
291
292        // Press key if not yet pressed
293        if (!pressed[keyName]) {
294
295            addClass(keyElement, "guac-keyboard-pressed");
296
297            // Get current key based on modifier state
298            var key = getActiveKey(keyName);
299
300            // Update modifier state
301            if (key.modifier) {
302
303                // Construct classname for modifier
304                var modifierClass = "guac-keyboard-modifier-" + getCSSName(key.modifier);
305
306                // Retrieve originally-pressed keysym, if modifier was already pressed
307                var originalKeysym = modifierKeysyms[key.modifier];
308
309                // Activate modifier if not pressed
310                if (!originalKeysym) {
311                   
312                    addClass(keyboard, modifierClass);
313                    modifierKeysyms[key.modifier] = key.keysym;
314                   
315                    // Send key event
316                    if (osk.onkeydown)
317                        osk.onkeydown(key.keysym);
318
319                }
320
321                // Deactivate if not pressed
322                else {
323
324                    removeClass(keyboard, modifierClass);
325                    delete modifierKeysyms[key.modifier];
326                   
327                    // Send key event
328                    if (osk.onkeyup)
329                        osk.onkeyup(originalKeysym);
330
331                }
332
333            }
334
335            // If not modifier, send key event now
336            else if (osk.onkeydown)
337                osk.onkeydown(key.keysym);
338
339            // Mark key as pressed
340            pressed[keyName] = true;
341
342        }
343
344    };
345
346    /**
347     * Releases the key having the given name, removing the
348     * "guac-keyboard-pressed" CSS class from the associated element. If the
349     * key is already released, this function has no effect.
350     *
351     * @private
352     * @param {String} keyName
353     *     The name of the key to release.
354     *
355     * @param {String} keyElement
356     *     The element associated with the given key.
357     */
358    var release = function release(keyName, keyElement) {
359
360        // Release key if currently pressed
361        if (pressed[keyName]) {
362
363            removeClass(keyElement, "guac-keyboard-pressed");
364
365            // Get current key based on modifier state
366            var key = getActiveKey(keyName);
367
368            // Send key event if not a modifier key
369            if (!key.modifier && osk.onkeyup)
370                osk.onkeyup(key.keysym);
371
372            // Mark key as released
373            pressed[keyName] = false;
374
375        }
376
377    };
378
379    // Create keyboard
380    var keyboard = document.createElement("div");
381    keyboard.className = "guac-keyboard";
382
383    // Do not allow selection or mouse movement to propagate/register.
384    keyboard.onselectstart =
385    keyboard.onmousemove   =
386    keyboard.onmouseup     =
387    keyboard.onmousedown   = function handleMouseEvents(e) {
388
389        // If ignoring events, decrement counter
390        if (ignoreMouse)
391            ignoreMouse--;
392
393        e.stopPropagation();
394        return false;
395
396    };
397
398    /**
399     * The number of mousemove events to require before re-enabling mouse
400     * event handling after receiving a touch event.
401     *
402     * @type {Number}
403     */
404    this.touchMouseThreshold = 3;
405
406    /**
407     * Fired whenever the user presses a key on this Guacamole.OnScreenKeyboard.
408     *
409     * @event
410     * @param {Number} keysym The keysym of the key being pressed.
411     */
412    this.onkeydown = null;
413
414    /**
415     * Fired whenever the user releases a key on this Guacamole.OnScreenKeyboard.
416     *
417     * @event
418     * @param {Number} keysym The keysym of the key being released.
419     */
420    this.onkeyup = null;
421
422    /**
423     * The keyboard layout provided at time of construction.
424     *
425     * @type {Guacamole.OnScreenKeyboard.Layout}
426     */
427    this.layout = new Guacamole.OnScreenKeyboard.Layout(layout);
428
429    /**
430     * Returns the element containing the entire on-screen keyboard.
431     * @returns {Element} The element containing the entire on-screen keyboard.
432     */
433    this.getElement = function() {
434        return keyboard;
435    };
436
437    /**
438     * Resizes all elements within this Guacamole.OnScreenKeyboard such that
439     * the width is close to but does not exceed the specified width. The
440     * height of the keyboard is determined based on the width.
441     *
442     * @param {Number} width The width to resize this Guacamole.OnScreenKeyboard
443     *                       to, in pixels.
444     */
445    this.resize = function(width) {
446
447        // Get pixel size of a unit
448        var unit = Math.floor(width * 10 / osk.layout.width) / 10;
449
450        // Resize all scaled elements
451        for (var i=0; i<scaledElements.length; i++) {
452            var scaledElement = scaledElements[i];
453            scaledElement.scale(unit);
454        }
455
456    };
457
458    /**
459     * Given the name of a key and its corresponding definition, which may be
460     * an array of keys objects, a number (keysym), a string (key title), or a
461     * single key object, returns an array of key objects, deriving any missing
462     * properties as needed, and ensuring the key name is defined.
463     *
464     * @private
465     * @param {String} name
466     *     The name of the key being coerced into an array of Key objects.
467     *
468     * @param {Number|String|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]} object
469     *     The object defining the behavior of the key having the given name,
470     *     which may be the title of the key (a string), the keysym (a number),
471     *     a single Key object, or an array of Key objects.
472     *     
473     * @returns {Guacamole.OnScreenKeyboard.Key[]}
474     *     An array of all keys associated with the given name.
475     */
476    var asKeyArray = function asKeyArray(name, object) {
477
478        // If already an array, just coerce into a true Key[]
479        if (object instanceof Array) {
480            var keys = [];
481            for (var i=0; i < object.length; i++) {
482                keys.push(new Guacamole.OnScreenKeyboard.Key(object[i], name));
483            }
484            return keys;
485        }
486
487        // Derive key object from keysym if that's all we have
488        if (typeof object === 'number') {
489            return [new Guacamole.OnScreenKeyboard.Key({
490                name   : name,
491                keysym : object
492            })];
493        }
494
495        // Derive key object from title if that's all we have
496        if (typeof object === 'string') {
497            return [new Guacamole.OnScreenKeyboard.Key({
498                name  : name,
499                title : object
500            })];
501        }
502
503        // Otherwise, assume it's already a key object, just not an array
504        return [new Guacamole.OnScreenKeyboard.Key(object, name)];
505
506    };
507
508    /**
509     * Converts the rather forgiving key mapping allowed by
510     * Guacamole.OnScreenKeyboard.Layout into a rigorous mapping of key name
511     * to key definition, where the key definition is always an array of Key
512     * objects.
513     *
514     * @private
515     * @param {Object.<String, Number|String|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]>} keys
516     *     A mapping of key name to key definition, where the key definition is
517     *     the title of the key (a string), the keysym (a number), a single
518     *     Key object, or an array of Key objects.
519     *
520     * @returns {Object.<String, Guacamole.OnScreenKeyboard.Key[]>}
521     *     A more-predictable mapping of key name to key definition, where the
522     *     key definition is always simply an array of Key objects.
523     */
524    var getKeys = function getKeys(keys) {
525
526        var keyArrays = {};
527
528        // Coerce all keys into individual key arrays
529        for (var name in layout.keys) {
530            keyArrays[name] = asKeyArray(name, keys[name]);
531        }
532
533        return keyArrays;
534
535    };
536
537    /**
538     * Map of all key names to their corresponding set of keys. Each key name
539     * may correspond to multiple keys due to the effect of modifiers.
540     *
541     * @type {Object.<String, Guacamole.OnScreenKeyboard.Key[]>}
542     */
543    this.keys = getKeys(layout.keys);
544
545    /**
546     * Given an arbitrary string representing the name of some component of the
547     * on-screen keyboard, returns a string formatted for use as a CSS class
548     * name. The result will be lowercase. Word boundaries previously denoted
549     * by CamelCase will be replaced by individual hyphens, as will all
550     * contiguous non-alphanumeric characters.
551     *
552     * @private
553     * @param {String} name
554     *     An arbitrary string representing the name of some component of the
555     *     on-screen keyboard.
556     *
557     * @returns {String}
558     *     A string formatted for use as a CSS class name.
559     */
560    var getCSSName = function getCSSName(name) {
561
562        // Convert name from possibly-CamelCase to hyphenated lowercase
563        var cssName = name
564               .replace(/([a-z])([A-Z])/g, '$1-$2')
565               .replace(/[^A-Za-z0-9]+/g, '-')
566               .toLowerCase();
567
568        return cssName;
569
570    };
571
572    /**
573     * Appends DOM elements to the given element as dictated by the layout
574     * structure object provided. If a name is provided, an additional CSS
575     * class, prepended with "guac-keyboard-", will be added to the top-level
576     * element.
577     *
578     * If the layout structure object is an array, all elements within that
579     * array will be recursively appended as children of a group, and the
580     * top-level element will be given the CSS class "guac-keyboard-group".
581     *
582     * If the layout structure object is an object, all properties within that
583     * object will be recursively appended as children of a group, and the
584     * top-level element will be given the CSS class "guac-keyboard-group". The
585     * name of each property will be applied as the name of each child object
586     * for the sake of CSS. Each property will be added in sorted order.
587     *
588     * If the layout structure object is a string, the key having that name
589     * will be appended. The key will be given the CSS class
590     * "guac-keyboard-key" and "guac-keyboard-key-NAME", where NAME is the name
591     * of the key. If the name of the key is a single character, this will
592     * first be transformed into the C-style hexadecimal literal for the
593     * Unicode codepoint of that character. For example, the key "A" would
594     * become "guac-keyboard-key-0x41".
595     *
596     * If the layout structure object is a number, a gap of that size will be
597     * inserted. The gap will be given the CSS class "guac-keyboard-gap", and
598     * will be scaled according to the same size units as each key.
599     *
600     * @private
601     * @param {Element} element
602     *     The element to append elements to.
603     *
604     * @param {Array|Object|String|Number} object
605     *     The layout structure object to use when constructing the elements to
606     *     append.
607     *
608     * @param {String} [name]
609     *     The name of the top-level element being appended, if any.
610     */
611    var appendElements = function appendElements(element, object, name) {
612
613        var i;
614
615        // Create div which will become the group or key
616        var div = document.createElement('div');
617
618        // Add class based on name, if name given
619        if (name)
620            addClass(div, 'guac-keyboard-' + getCSSName(name));
621
622        // If an array, append each element
623        if (object instanceof Array) {
624
625            // Add group class
626            addClass(div, 'guac-keyboard-group');
627
628            // Append all elements of array
629            for (i=0; i < object.length; i++)
630                appendElements(div, object[i]);
631
632        }
633
634        // If an object, append each property value
635        else if (object instanceof Object) {
636
637            // Add group class
638            addClass(div, 'guac-keyboard-group');
639
640            // Append all children, sorted by name
641            var names = Object.keys(object).sort();
642            for (i=0; i < names.length; i++) {
643                var name = names[i];
644                appendElements(div, object[name], name);
645            }
646
647        }
648
649        // If a number, create as a gap
650        else if (typeof object === 'number') {
651
652            // Add gap class
653            addClass(div, 'guac-keyboard-gap');
654
655            // Maintain scale
656            scaledElements.push(new ScaledElement(div, object, object));
657
658        }
659
660        // If a string, create as a key
661        else if (typeof object === 'string') {
662
663            // If key name is only one character, use codepoint for name
664            var keyName = object;
665            if (keyName.length === 1)
666                keyName = '0x' + keyName.charCodeAt(0).toString(16);
667
668            // Add key container class
669            addClass(div, 'guac-keyboard-key-container');
670
671            // Create key element which will contain all possible caps
672            var keyElement = document.createElement('div');
673            keyElement.className = 'guac-keyboard-key '
674                                 + 'guac-keyboard-key-' + getCSSName(keyName);
675
676            // Add all associated keys as caps within DOM
677            var keys = osk.keys[object];
678            if (keys) {
679                for (i=0; i < keys.length; i++) {
680
681                    // Get current key
682                    var key = keys[i];
683
684                    // Create cap element for key
685                    var capElement = document.createElement('div');
686                    capElement.className   = 'guac-keyboard-cap';
687                    capElement.textContent = key.title;
688
689                    // Add classes for any requirements
690                    for (var j=0; j < key.requires.length; j++) {
691                        var requirement = key.requires[j];
692                        addClass(capElement, 'guac-keyboard-requires-' + getCSSName(requirement));
693                        addClass(keyElement, 'guac-keyboard-uses-'     + getCSSName(requirement));
694                    }
695
696                    // Add cap to key within DOM
697                    keyElement.appendChild(capElement);
698
699                }
700            }
701
702            // Add key to DOM, maintain scale
703            div.appendChild(keyElement);
704            scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true));
705
706            /**
707             * Handles a touch event which results in the pressing of an OSK
708             * key. Touch events will result in mouse events being ignored for
709             * touchMouseThreshold events.
710             *
711             * @private
712             * @param {TouchEvent} e
713             *     The touch event being handled.
714             */
715            var touchPress = function touchPress(e) {
716                e.preventDefault();
717                ignoreMouse = osk.touchMouseThreshold;
718                press(object, keyElement);
719            };
720
721            /**
722             * Handles a touch event which results in the release of an OSK
723             * key. Touch events will result in mouse events being ignored for
724             * touchMouseThreshold events.
725             *
726             * @private
727             * @param {TouchEvent} e
728             *     The touch event being handled.
729             */
730            var touchRelease = function touchRelease(e) {
731                e.preventDefault();
732                ignoreMouse = osk.touchMouseThreshold;
733                release(object, keyElement);
734            };
735
736            /**
737             * Handles a mouse event which results in the pressing of an OSK
738             * key. If mouse events are currently being ignored, this handler
739             * does nothing.
740             *
741             * @private
742             * @param {MouseEvent} e
743             *     The touch event being handled.
744             */
745            var mousePress = function mousePress(e) {
746                e.preventDefault();
747                if (ignoreMouse === 0)
748                    press(object, keyElement);
749            };
750
751            /**
752             * Handles a mouse event which results in the release of an OSK
753             * key. If mouse events are currently being ignored, this handler
754             * does nothing.
755             *
756             * @private
757             * @param {MouseEvent} e
758             *     The touch event being handled.
759             */
760            var mouseRelease = function mouseRelease(e) {
761                e.preventDefault();
762                if (ignoreMouse === 0)
763                    release(object, keyElement);
764            };
765
766            // Handle touch events on key
767            keyElement.addEventListener("touchstart", touchPress,   true);
768            keyElement.addEventListener("touchend",   touchRelease, true);
769
770            // Handle mouse events on key
771            keyElement.addEventListener("mousedown", mousePress,   true);
772            keyElement.addEventListener("mouseup",   mouseRelease, true);
773            keyElement.addEventListener("mouseout",  mouseRelease, true);
774
775        } // end if object is key name
776
777        // Add newly-created group/key
778        element.appendChild(div);
779
780    };
781
782    // Create keyboard layout in DOM
783    appendElements(keyboard, layout.layout);
784
785};
786
787/**
788 * Represents an entire on-screen keyboard layout, including all available
789 * keys, their behaviors, and their relative position and sizing.
790 *
791 * @constructor
792 * @param {Guacamole.OnScreenKeyboard.Layout|Object} template
793 *     The object whose identically-named properties will be used to initialize
794 *     the properties of this layout.
795 */
796Guacamole.OnScreenKeyboard.Layout = function(template) {
797
798    /**
799     * The language of keyboard layout, such as "en_US". This property is for
800     * informational purposes only, but it is recommend to conform to the
801     * [language code]_[country code] format.
802     *
803     * @type {String}
804     */
805    this.language = template.language;
806
807    /**
808     * The type of keyboard layout, such as "qwerty". This property is for
809     * informational purposes only, and does not conform to any standard.
810     *
811     * @type {String}
812     */
813    this.type = template.type;
814
815    /**
816     * Map of key name to corresponding keysym, title, or key object. If only
817     * the keysym or title is provided, the key object will be created
818     * implicitly. In all cases, the name property of the key object will be
819     * taken from the name given in the mapping.
820     *
821     * @type {Object.<String, Number|String|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]>}
822     */
823    this.keys = template.keys;
824
825    /**
826     * Arbitrarily nested, arbitrarily grouped key names. The contents of the
827     * layout will be traversed to produce an identically-nested grouping of
828     * keys in the DOM tree. All strings will be transformed into their
829     * corresponding sets of keys, while all objects and arrays will be
830     * transformed into named groups and anonymous groups respectively. Any
831     * numbers present will be transformed into gaps of that size, scaled
832     * according to the same units as each key.
833     *
834     * @type {Object}
835     */
836    this.layout = template.layout;
837
838    /**
839     * The width of the entire keyboard, in arbitrary units. The width of each
840     * key is relative to this width, as both width values are assumed to be in
841     * the same units. The conversion factor between these units and pixels is
842     * derived later via a call to resize() on the Guacamole.OnScreenKeyboard.
843     *
844     * @type {Number}
845     */
846    this.width = template.width;
847
848    /**
849     * The width of each key, in arbitrary units, relative to other keys in
850     * this layout. The true pixel size of each key will be determined by the
851     * overall size of the keyboard. If not defined here, the width of each
852     * key will default to 1.
853     *
854     * @type {Object.<String, Number>}
855     */
856    this.keyWidths = template.keyWidths || {};
857
858};
859
860/**
861 * Represents a single key, or a single possible behavior of a key. Each key
862 * on the on-screen keyboard must have at least one associated
863 * Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or
864 * implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior
865 * depends on modifier states.
866 *
867 * @constructor
868 * @param {Guacamole.OnScreenKeyboard.Key|Object} template
869 *     The object whose identically-named properties will be used to initialize
870 *     the properties of this key.
871 *     
872 * @param {String} [name]
873 *     The name to use instead of any name provided within the template, if
874 *     any. If omitted, the name within the template will be used, assuming the
875 *     template contains a name.
876 */
877Guacamole.OnScreenKeyboard.Key = function(template, name) {
878
879    /**
880     * The unique name identifying this key within the keyboard layout.
881     *
882     * @type {String}
883     */
884    this.name = name || template.name;
885
886    /**
887     * The human-readable title that will be displayed to the user within the
888     * key. If not provided, this will be derived from the key name.
889     *
890     * @type {String}
891     */
892    this.title = template.title || this.name;
893
894    /**
895     * The keysym to be pressed/released when this key is pressed/released. If
896     * not provided, this will be derived from the title if the title is a
897     * single character.
898     *
899     * @type {Number}
900     */
901    this.keysym = template.keysym || (function deriveKeysym(title) {
902
903        // Do not derive keysym if title is not exactly one character
904        if (!title || title.length !== 1)
905            return null;
906
907        // For characters between U+0000 and U+00FF, the keysym is the codepoint
908        var charCode = title.charCodeAt(0);
909        if (charCode >= 0x0000 && charCode <= 0x00FF)
910            return charCode;
911
912        // For characters between U+0100 and U+10FFFF, the keysym is the codepoint or'd with 0x01000000
913        if (charCode >= 0x0100 && charCode <= 0x10FFFF)
914            return 0x01000000 | charCode;
915
916        // Unable to derive keysym
917        return null;
918
919    })(this.title);
920
921    /**
922     * The name of the modifier set when the key is pressed and cleared when
923     * this key is released, if any. The names of modifiers are distinct from
924     * the names of keys; both the "RightShift" and "LeftShift" keys may set
925     * the "shift" modifier, for example. By default, the key will affect no
926     * modifiers.
927     *
928     * @type {String}
929     */
930    this.modifier = template.modifier;
931
932    /**
933     * An array containing the names of each modifier required for this key to
934     * have an effect. For example, a lowercase letter may require nothing,
935     * while an uppercase letter would require "shift", assuming the Shift key
936     * is named "shift" within the layout. By default, the key will require
937     * no modifiers.
938     *
939     * @type {String[]}
940     */
941    this.requires = template.requires || [];
942
943};
Note: See TracBrowser for help on using the repository browser.