Track pressed state for modifiers globally. Do not track pressed state of non-modifiers.
[guacamole-common-js.git] / src / main / resources / oskeyboard.js
index e0c2d69..2c095fb 100644 (file)
@@ -12,7 +12,7 @@
  * for the specific language governing rights and limitations under the
  * License.
  *
- * The Original Code is guacamole-common-js.
+ * The Original Code is guac-common-js.
  *
  * The Initial Developer of the Original Code is
  * Michael Jumper.
@@ -48,6 +48,50 @@ var Guacamole = Guacamole || {};
  */
 Guacamole.OnScreenKeyboard = function(url) {
 
+    var on_screen_keyboard = this;
+
+    var scaledElements = [];
+    
+    var modifiers = {};
+    var currentModifier = 1;
+
+    // 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 getModifier(name) {
+        
+        var value = modifiers[name];
+        if (!value) {
+
+            // Get current modifier, advance to next
+            value = currentModifier;
+            currentModifier <<= 1;
+
+            // Store value of this modifier
+            modifiers[name] = value;
+
+        }
+
+        return value;
+            
+    }
+
+    function ScaledElement(element, width, height, scaleFont) {
+
+        this.width = width;
+        this.height = height;
+
+        this.scale = function(pixels) {
+            element.style.width      = Math.floor(width  * pixels) + "px";
+            element.style.height     = Math.floor(height * pixels) + "px";
+
+            if (scaleFont) {
+                element.style.lineHeight = Math.floor(height * pixels) + "px";
+                element.style.fontSize   = pixels + "px";
+            }
+        }
+
+    }
+
     // For each child of element, call handler defined in next
     function parseChildren(element, next) {
 
@@ -81,7 +125,7 @@ Guacamole.OnScreenKeyboard = function(url) {
 
     // Create keyboard
     var keyboard = document.createElement("div");
-    keyboard.className = "keyboard";
+    keyboard.className = "guac-keyboard";
 
     // Retrieve keyboard XML
     var xmlhttprequest = new XMLHttpRequest();
@@ -95,7 +139,7 @@ Guacamole.OnScreenKeyboard = function(url) {
         function parse_row(e) {
             
             var row = document.createElement("div");
-            row.className = "row";
+            row.className = "guac-keyboard-row";
 
             parseChildren(e, {
                 
@@ -110,13 +154,14 @@ Guacamole.OnScreenKeyboard = function(url) {
 
                     // Create element
                     var gap = document.createElement("div");
-                    gap.className = "gap";
-                    gap.textContent = " ";
+                    gap.className = "guac-keyboard-gap";
 
+                    // Set gap size
+                    var gap_units = 1;
                     if (gap_size)
-                        gap.style.width = gap.style.height =
-                            parseFloat(gap_size.value) + "em";
+                        gap_units = parseFloat(gap_size.value);
 
+                    scaledElements.push(new ScaledElement(gap, gap_units, gap_units));
                     row.appendChild(gap);
 
                 },
@@ -127,12 +172,26 @@ Guacamole.OnScreenKeyboard = function(url) {
                     var key_size = e.attributes["size"];
 
                     // Create element
-                    var key = document.createElement("div");
-                    key.className = "key";
-                    key.textContent = "K";
+                    var key_element = document.createElement("div");
+                    key_element.className = "guac-keyboard-key";
+
+                    // Position keys using container div
+                    var key_container_element = document.createElement("div");
+                    key_container_element.className = "guac-keyboard-key-container";
+                    key_container_element.appendChild(key_element);
+
+                    // Create key
+                    var key = new Guacamole.OnScreenKeyboard.Key();
+
+                    // Set key size
+                    var key_units = 1;
+                    if (key_size)
+                        key_units = parseFloat(key_size.value);
+
+                    key.size = key_units;
 
                     parseChildren(e, {
-                        "cap": function cap(e) {
+                        "cap": function parse_cap(e) {
 
                             // Get attributes
                             var required = e.attributes["if"];
@@ -143,15 +202,73 @@ Guacamole.OnScreenKeyboard = function(url) {
                             // Get content of key cap
                             var content = e.textContent;
                             
-                            // If no requirements, then show cap by default
-                            if (!required) {
-                                key.textContent = content;
+                            // Create cap
+                            var cap = new Guacamole.OnScreenKeyboard.Cap(content,
+                                keysym ? keysym.value : null);
+
+                            if (modifier)
+                                cap.modifier = modifier.value;
+                            
+                            // Create cap element
+                            var cap_element = document.createElement("div");
+                            cap_element.className = "guac-keyboard-cap";
+                            cap_element.textContent = content;
+                            key_element.appendChild(cap_element);
+
+                            // Get modifier value
+                            var modifierValue = 0;
+                            if (required) {
+
+                                // Get modifier value for specified comma-delimited
+                                // list of required modifiers.
+                                var requirements = required.value.split(",");
+                                for (var i=0; i<requirements.length; i++) {
+                                    modifierValue |= getModifier(requirements[i]);
+                                    cap_element.classList.add("guac-keyboard-requires-" + requirements[i]);
+                                    key_element.classList.add("guac-keyboard-uses-" + requirements[i]);
+                                }
+
                             }
 
+                            // Store cap
+                            key.modifierMask |= modifierValue;
+                            key.caps[modifierValue] = cap;
+
                         }
                     });
 
-                    row.appendChild(key);
+                    scaledElements.push(new ScaledElement(key_container_element, key_units, 1, true));
+                    row.appendChild(key_container_element);
+
+                    // Set up click handler for key
+                    key_element.onclick = function() {
+
+                        // Get current cap based on modifier state
+                        var cap = key.getCap(on_screen_keyboard.modifiers);
+
+                        // Update modifier state
+                        if (cap.modifier) {
+
+                            // Construct classname for modifier
+                            var modifierClass = "guac-keyboard-modifier-" + cap.modifier;
+                            var modifierFlag = getModifier(cap.modifier);
+
+                            // Toggle modifier state
+                            on_screen_keyboard.modifiers ^= modifierFlag;
+
+                            // Activate modifier if pressed
+                            if (on_screen_keyboard.modifiers & modifierFlag)
+                                keyboard.classList.add(modifierClass);
+
+                            // Deactivate if not pressed
+                            else
+                                keyboard.classList.remove(modifierClass);
+
+                        }
+
+                        // TODO: Send key event
+
+                    };
 
                 }
                 
@@ -164,7 +281,7 @@ Guacamole.OnScreenKeyboard = function(url) {
         function parse_column(e) {
             
             var col = document.createElement("div");
-            col.className = "col";
+            col.className = "guac-keyboard-column";
 
             var align = col.attributes["align"];
 
@@ -189,7 +306,10 @@ Guacamole.OnScreenKeyboard = function(url) {
             throw new Error("Root element must be keyboard");
 
         // Get attributes
-        var keyboard_size = keyboard_element.attributes["size"];
+        if (!keyboard_element.attributes["size"])
+            throw new Error("size attribute is required for keyboard");
+        
+        var keyboard_size = parseFloat(keyboard_element.attributes["size"].value);
         
         parseChildren(keyboard_element, {
             
@@ -215,6 +335,10 @@ Guacamole.OnScreenKeyboard = function(url) {
         return false;
     };
 
+    /**
+     * State of all modifiers.
+     */
+    this.modifiers = 0;
 
     this.onkeypressed  = null;
     this.onkeyreleased = null;
@@ -223,33 +347,56 @@ Guacamole.OnScreenKeyboard = function(url) {
         return keyboard;
     };
 
+    this.resize = function(width) {
+
+        // Get pixel size of a unit
+        var unit = Math.floor(width / keyboard_size);
+
+        // Resize all scaled elements
+        for (var i=0; i<scaledElements.length; i++) {
+            var scaledElement = scaledElements[i];
+            scaledElement.scale(unit)
+        }
+
+    };
+
 };
 
 Guacamole.OnScreenKeyboard.Key = function() {
 
+    var key = this;
+
     /**
      * Width of the key, relative to the size of the keyboard.
      */
     this.size = 1;
 
     /**
-     * Whether this key is currently pressed.
+     * An associative map of all caps by modifier.
+     */
+    this.caps = {};
+
+    /**
+     * Bit mask with all modifiers that affect this key set.
      */
-    this.pressed = false;
+    this.modifierMask = 0;
 
     /**
-     * An associative map of all caps by modifier.
+     * Given the bitwise OR of all active modifiers, returns the key cap
+     * which applies.
      */
-    this.caps = {};
+    this.getCap = function(modifier) {
+        return key.caps[modifier & key.modifierMask];
+    };
 
 }
 
-Guacamole.OnScreenKeyboard.Cap = function(text, keycode, modifier) {
+Guacamole.OnScreenKeyboard.Cap = function(text, keysym, modifier) {
     
     /**
      * Modifier represented by this keycap
      */
-    this.modifier = 0;
+    this.modifier = null;
     
     /**
      * The text to be displayed within this keycap
@@ -257,9 +404,9 @@ Guacamole.OnScreenKeyboard.Cap = function(text, keycode, modifier) {
     this.text = text;
 
     /**
-     * The keycode this cap sends when its associated key is pressed/released
+     * The keysym this cap sends when its associated key is pressed/released
      */
-    this.keycode = keycode;
+    this.keysym = keysym;
 
     // Set modifier if provided
     if (modifier) this.modifier = modifier;