From 29a0bc6387065852fe9ea02609e639fefeb8b069 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 14:34:04 -0700 Subject: [PATCH] GUAC-1170: Migrate to JSON representation, at least in spirit. --- .../main/webapp/modules/OnScreenKeyboard.js | 757 +++++++----------- 1 file changed, 294 insertions(+), 463 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 71a5a0897..0229b6c6d 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,29 @@ var Guacamole = Guacamole || {}; /** - * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file, - * this object will download and use the XML to construct a clickable on-screen - * keyboard with its own key events. - * + * Dynamic on-screen keyboard. Given the layout object for an on-screen + * keyboard, this object will construct a clickable on-screen keyboard with its + * own key events. + * * @constructor - * @param {String} url The URL of an XML keyboard layout file. + * @param {Guacamole.OnScreenKeyboard.Layout} layout + * The layout of the on-screen keyboard to display. */ -Guacamole.OnScreenKeyboard = function(url) { +Guacamole.OnScreenKeyboard = function(layout) { - var on_screen_keyboard = this; + /** + * Reference to this Guacamole.OnScreenKeyboard. + * + * @type Guacamole.OnScreenKeyboard + */ + var osk = this; /** * State of all modifiers. This is the bitwise OR of all active modifier * values. * * @private + * @type Number */ var modifiers = 0; @@ -47,86 +54,69 @@ Guacamole.OnScreenKeyboard = function(url) { * original press. When the modifier is cleared, this keysym must be * released. * + * @private * @type Object. */ - var modifier_keysyms = {}; + var modifierKeysyms = {}; + /** + * All scalable elements which are part of the on-screen keyboard. Each + * scalable element is carefully controlled to ensure the interface layout + * and sizing remains constant, even on browsers that would otherwise + * experience rounding error due to unit conversions. + * + * @private + * @type ScaledElement[] + */ var scaledElements = []; - - var modifier_masks = {}; - var next_mask = 1; /** - * Adds a class to an element. + * Adds a CSS class to an element. * * @private * @function - * @param {Element} element The element to add a class to. - * @param {String} classname The name of the class to add. + * @param {Element} element + * The element to add a class to. + * + * @param {String} classname + * The name of the class to add. */ - var addClass; + var addClass = function addClass(element, classname) { - /** - * Removes a class from an element. - * - * @private - * @function - * @param {Element} element The element to remove a class from. - * @param {String} classname The name of the class to remove. - */ - var removeClass; - - /** - * The number of mousemove events to require before re-enabling mouse - * event handling after receiving a touch event. - */ - this.touchMouseThreshold = 3; - - /** - * Counter of mouse events to ignore. This decremented by mousemove, and - * while non-zero, mouse events will have no effect. - * @private - */ - var ignore_mouse = 0; - - // Ignore all pending mouse events when touch events are the apparent source - function ignorePendingMouseEvents() { ignore_mouse = on_screen_keyboard.touchMouseThreshold; } - - // If Node.classList is supported, implement addClass/removeClass using that - if (Node.classList) { - - /** @ignore */ - addClass = function(element, classname) { + // If classList supported, use that + if (element.classList) element.classList.add(classname); - }; - - /** @ignore */ - removeClass = function(element, classname) { - element.classList.remove(classname); - }; - - } - // Otherwise, implement own - else { - - /** @ignore */ - addClass = function(element, classname) { - - // Simply add new class + // Otherwise, simply append the class + else element.className += " " + classname; - }; - - /** @ignore */ - removeClass = function(element, classname) { + }; - // Filter out classes with given name + /** + * Removes a CSS class from an element. + * + * @private + * @function + * @param {Element} element + * The element to remove a class from. + * + * @param {String} classname + * The name of the class to remove. + */ + var removeClass = function removeClass(element, classname) { + + // If classList supported, use that + if (element.classList) + element.classList.remove(classname); + + // Otherwise, manually filter out classes with given name + else { element.className = element.className.replace(/([^ ]+)[ ]*/g, - function(match, testClassname, spaces, offset, string) { + function removeMatchingClasses(match, testClassname) { // If same class, remove - if (testClassname == classname) + if (testClassname === classname) return ""; // Otherwise, allow @@ -134,393 +124,170 @@ Guacamole.OnScreenKeyboard = function(url) { } ); + } - }; - - } + }; - // Returns a unique power-of-two value for the modifier with the - // given name. The same value will be returned for the same modifier. - function getModifierMask(name) { + /** + * Counter of mouse events to ignore. This decremented by mousemove, and + * while non-zero, mouse events will have no effect. + * + * @private + * @type Number + */ + var ignoreMouse = 0; + + /** + * Ignores all pending mouse events when touch events are the apparent + * source. Mouse events are ignored until at least touchMouseThreshold + * mouse events occur without corresponding touch events. + * + * @private + */ + var ignorePendingMouseEvents = function ignorePendingMouseEvents() { + ignoreMouse = osk.touchMouseThreshold; + }; + + /** + * Map of modifier names to their corresponding, unique, power-of-two value + * bitmasks. + * + * @private + * @type Object. + */ + var modifierMasks = {}; + + /** + * The bitmask to assign to the next modifier, if a new modifier is used + * which does not yet have a corresponding bitmask. + * + * @private + * @type Number + */ + var nextMask = 1; + + /** + * Returns a unique power-of-two value for the modifier with the given + * name. The same value will be returned consistently for the same + * modifier. + * + * @private + * @param {String} name + * The name of the modifier to return a bitmask for. + * + * @return {Number} + * The unique power-of-two value associated with the modifier having + * the given name, which may be newly allocated. + */ + var getModifierMask = function getModifierMask(name) { - var value = modifier_masks[name]; + var value = modifierMasks[name]; if (!value) { // Get current modifier, advance to next - value = next_mask; - next_mask <<= 1; + value = nextMask; + nextMask <<= 1; // Store value of this modifier - modifier_masks[name] = value; + modifierMasks[name] = value; } return value; - } + }; - function ScaledElement(element, width, height, scaleFont) { + /** + * An element whose dimensions are maintained according to an arbitrary + * scale. The conversion factor for these arbitrary units to pixels is + * provided later via a call to scale(). + * + * @private + * @constructor + * @param {Element} element + * The element whose scale should be maintained. + * + * @param {Number} width + * The width of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {Number} height + * The height of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {Boolean} [scaleFont=false] + * Whether the line height and font size should be scaled as well. + */ + var ScaledElement = function ScaledElement(element, width, height, scaleFont) { - this.width = width; - this.height = height; + /** + * The width of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type Number + */ + this.width = width; + /** + * The height of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type Number + */ + this.height = height; + + /** + * Resizes the associated element, updating its dimensions according to + * the given pixels per unit. + * + * @param {Number} pixels + * The number of pixels to assign per arbitrary unit. + */ this.scale = function(pixels) { - element.style.width = (width * pixels) + "px"; - element.style.height = (height * pixels) + "px"; + // Scale element width/height + element.style.width = (width * pixels) + "px"; + element.style.height = (height * pixels) + "px"; + + // Scale font, if requested if (scaleFont) { element.style.lineHeight = (height * pixels) + "px"; element.style.fontSize = pixels + "px"; } - } - } + }; - // For each child of element, call handler defined in next - function parseChildren(element, next) { - - var children = element.childNodes; - for (var i=0; i= 0x0000 && charCode <= 0x00FF) - real_keysym = charCode; - else if (charCode >= 0x0100 && charCode <= 0x10FFFF) - real_keysym = 0x01000000 | charCode; - - } - - // Create cap - var cap = new Guacamole.OnScreenKeyboard.Cap(content, real_keysym); - - if (e.getAttribute("modifier")) - cap.modifier = e.getAttribute("modifier"); - - // Create cap element - var cap_element = document.createElement("div"); - cap_element.className = "guac-keyboard-cap"; - cap_element.textContent = content; - key_element.appendChild(cap_element); - - // Append class if specified - if (e.getAttribute("class")) - cap_element.className += " " + e.getAttribute("class"); - - // Get modifier value - var modifierValue = 0; - if (e.getAttribute("if")) { - - // Get modifier value for specified comma-delimited - // list of required modifiers. - var requirements = e.getAttribute("if").split(","); - for (var i=0; i */ - this.caps = {}; + this.keys = template.keys; /** - * Bit mask with all modifiers that affect this key set. + * Arbitrarily nested, arbitrarily grouped key names. The contents of the + * layout will be traversed to produce an identically-nested grouping of + * keys in the DOM tree. All strings will be transformed into their + * corresponding sets of keys, while all objects and arrays will be + * transformed into named groups and anonymous groups respectively. + * + * @type Object */ - this.modifierMask = 0; + this.layout = template.layout; /** - * Given the bitwise OR of all active modifiers, returns the key cap - * which applies. + * The width of the entire keyboard, in arbitrary units. The width of each + * key is relative to this width, as both width values are assumed to be in + * the same units. The conversion factor between these units and pixels is + * derived later via a call to resize() on the Guacamole.OnScreenKeyboard. + * + * @type Number */ - this.getCap = function(modifier) { - return key.caps[modifier & key.modifierMask]; - }; + this.width = template.width; }; /** - * Basic representation of a cap of a key. The cap is the visible part of a key - * and determines the active behavior of a key when pressed. The state of all - * modifiers on the keyboard determines the active cap for all keys, thus - * each cap is associated with a set of modifiers. - * + * Represents a single key, or a single possible behavior of a key. Each key + * on the on-screen keyboard must have at least one associated + * Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or + * implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior + * depends on modifier states. + * * @constructor - * @param {String} text The text to be displayed within this cap. - * @param {Number} keysym The keysym this cap sends when its associated key is - * pressed or released. - * @param {String} modifier The modifier represented by this cap. + * @param {Guacamole.OnScreenKeyboard.Key|Object} template + * The object whose identically-named properties will be used to initialize + * the properties of this key. */ -Guacamole.OnScreenKeyboard.Cap = function(text, keysym, modifier) { - - /** - * Modifier represented by this keycap - */ - this.modifier = null; - - /** - * The text to be displayed within this keycap - */ - this.text = text; +Guacamole.OnScreenKeyboard.Key = function(template) { /** - * The keysym this cap sends when its associated key is pressed/released + * The unique name identifying this key within the keyboard layout. + * + * @type String */ - this.keysym = keysym; + this.name = template.name; + + /** + * The human-readable title that will be displayed to the user within the + * key. If not provided, this will be derived from the key name. + * + * @type String + */ + this.title = template.title || template.name; + + /** + * The keysym to be pressed/released when this key is pressed/released. If + * not provided, this will be derived from the title if the title is a + * single character. + * + * @type Number + */ + this.keysym = template.keysym; + + /** + * The name of the modifier set when the key is pressed and cleared when + * this key is released, if any. The names of modifiers are distinct from + * the names of keys; both the "RightShift" and "LeftShift" keys may set + * the "shift" modifier, for example. By default, the key will affect no + * modifiers. + * + * @type String + */ + this.modifier = template.modifier; + + /** + * An array containing the names of each modifier required for this key to + * have an effect. For example, a lowercase letter may require nothing, + * while an uppercase letter would require "shift", assuming the Shift key + * is named "shift" within the layout. By default, the key will require + * no modifiers. + * + * @type String[] + */ + this.requires = template.requires || []; + + /** + * The width of this key, in arbitrary units, relative to other keys in the + * same layout. The true pixel size of this key will be determined by the + * overall size of the keyboard. By default, this will be 1. + * + * @type Number + */ + this.width = template.width || 1; - // Set modifier if provided - if (modifier) this.modifier = modifier; - };