From b33333da3f5e6f852e3c4fbcb021ff72f298dae2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Dec 2014 21:56:38 -0800 Subject: [PATCH] GUAC-810: Implement guacTextInput directive. --- .../app/textInput/directives/guacKey.js | 115 +++++++ .../app/textInput/directives/guacTextInput.js | 289 ++++++++++++++++++ .../styles/textInput.css} | 31 +- .../app/textInput/templates/guacKey.html | 25 ++ .../textInput/templates/guacTextInput.html | 27 ++ .../webapp/app/textInput/textInputModule.js | 26 ++ 6 files changed, 496 insertions(+), 17 deletions(-) create mode 100644 guacamole/src/main/webapp/app/textInput/directives/guacKey.js create mode 100644 guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js rename guacamole/src/main/webapp/app/{client/styles/text-input.css => textInput/styles/textInput.css} (82%) create mode 100644 guacamole/src/main/webapp/app/textInput/templates/guacKey.html create mode 100644 guacamole/src/main/webapp/app/textInput/templates/guacTextInput.html create mode 100644 guacamole/src/main/webapp/app/textInput/textInputModule.js diff --git a/guacamole/src/main/webapp/app/textInput/directives/guacKey.js b/guacamole/src/main/webapp/app/textInput/directives/guacKey.js new file mode 100644 index 000000000..c30665625 --- /dev/null +++ b/guacamole/src/main/webapp/app/textInput/directives/guacKey.js @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which displays a button that controls the pressed state of a + * single keyboard key. + */ +angular.module('textInput').directive('guacKey', [function guacKey() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The text to display within the key. This will be run through the + * translation filter prior to display. + * + * @type String + */ + text : '=', + + /** + * The keysym to send within keyup and keydown events when this key + * is pressed or released. + * + * @type Number + */ + keysym : '=', + + /** + * Whether this key is sticky. Sticky keys toggle their pressed + * state with each click. + * + * @type Boolean + * @default false + */ + sticky : '=?', + + /** + * Whether this key is currently pressed. + * + * @type Boolean + * @default false + */ + pressed : '=?' + + }, + + templateUrl: 'app/textInput/templates/guacKey.html', + controller: ['$scope', '$rootScope', + function guacKey($scope, $rootScope) { + + // Not sticky by default + $scope.sticky = $scope.sticky || false; + + // Unpressed by default + $scope.pressed = $scope.pressed || false; + + /** + * Presses and releases this key, sending the corresponding keydown + * and keyup events. In the case of sticky keys, the pressed state + * is toggled, and only a single keydown/keyup event will be sent, + * depending on the current state. + */ + $scope.updateKey = function updateKey() { + + // If sticky, toggle pressed state + if ($scope.sticky) + $scope.pressed = !$scope.pressed; + + // For all non-sticky keys, press and release key immediately + else { + $rootScope.$broadcast('guacKeydown', $scope.keysym); + $rootScope.$broadcast('guacKeyup', $scope.keysym); + } + + }; + + // Send keyup/keydown when pressed state is altered + $scope.$watch('pressed', function updatePressedState(isPressed, wasPressed) { + + // If the key is pressed now, send keydown + if (isPressed) + $rootScope.$broadcast('guacKeydown', $scope.keysym); + + // If the key was pressed, but is not pressed any longer, send keyup + else if (wasPressed) + $rootScope.$broadcast('guacKeyup', $scope.keysym); + + }); + + }] + + }; +}]); diff --git a/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js b/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js new file mode 100644 index 000000000..c09572489 --- /dev/null +++ b/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which displays the Guacamole text input method. + */ +angular.module('textInput').directive('guacTextInput', [function guacTextInput() { + + return { + restrict: 'E', + replace: true, + scope: {}, + + templateUrl: 'app/textInput/templates/guacTextInput.html', + controller: ['$scope', '$rootScope', '$element', '$timeout', + function guacTextInput($scope, $rootScope, $element, $timeout) { + + /** + * The number of characters to include on either side of text input + * content, to allow the user room to use backspace and delete. + * + * @type Number + */ + var TEXT_INPUT_PADDING = 128; + + /** + * The Unicode codepoint of the character to use for padding on + * either side of text input content. + * + * @type Number + */ + var TEXT_INPUT_PADDING_CODEPOINT = 0x200B; + + /** + * Recently-sent text, ordered from oldest to most recent. + * + * @type String[] + */ + $scope.sentText = []; + + /** + * Whether the "Alt" key is currently pressed within the text input + * interface. + * + * @type Boolean + */ + $scope.altPressed = false; + + /** + * Whether the "Ctrl" key is currently pressed within the text + * input interface. + * + * @type Boolean + */ + $scope.ctrlPressed = false; + + /** + * The text area input target. + * + * @type Element + */ + var target = $element.find('.target')[0]; + + /** + * Whether the text input target currently has focus. Setting this + * attribute has no effect, but any bound property will be updated + * as focus is gained or lost. + * + * @type Boolean + */ + var hasFocus = false; + + target.onfocus = function targetFocusGained() { + hasFocus = true; + resetTextInputTarget(TEXT_INPUT_PADDING); + }; + + target.onblur = function targetFocusLost() { + hasFocus = false; + target.focus(); + }; + + /** + * Whether composition is currently active within the text input + * target element, such as when an IME is in use. + * + * @type Boolean + */ + var composingText = false; + + target.addEventListener("compositionstart", function targetComposeStart(e) { + composingText = true; + }, false); + + target.addEventListener("compositionend", function targetComposeEnd(e) { + composingText = false; + }, false); + + /** + * Translates a given Unicode codepoint into the corresponding X11 + * keysym. + * + * @param {Number} codepoint + * The Unicode codepoint to translate. + * + * @returns {Number} + * The X11 keysym that corresponds to the given Unicode + * codepoint, or null if no such keysym exists. + */ + var keysymFromCodepoint = function keysymFromCodepoint(codepoint) { + + // Keysyms for control characters + if (codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F)) + return 0xFF00 | codepoint; + + // Keysyms for ASCII chars + if (codepoint >= 0x0000 && codepoint <= 0x00FF) + return codepoint; + + // Keysyms for Unicode + if (codepoint >= 0x0100 && codepoint <= 0x10FFFF) + return 0x01000000 | codepoint; + + return null; + + }; + + /** + * Presses and releases the key corresponding to the given keysym, + * as if typed by the user. + * + * @param {Number} keysym The keysym of the key to send. + */ + var sendKeysym = function sendKeysym(keysym) { + $rootScope.$broadcast('guacKeydown', keysym); + $rootScope.$broadcast('guacKeyup', keysym); + }; + + /** + * Presses and releases the key having the keysym corresponding to + * the Unicode codepoint given, as if typed by the user. + * + * @param {Number} codepoint + * The Unicode codepoint of the key to send. + */ + var sendCodepoint = function sendCodepoint(codepoint) { + + if (codepoint === 10) { + sendKeysym(0xFF0D); + releaseStickyKeys(); + return; + } + + var keysym = keysymFromCodepoint(codepoint); + if (keysym) { + sendKeysym(keysym); + releaseStickyKeys(); + } + + }; + + /** + * Translates each character within the given string to keysyms and + * sends each, in order, as if typed by the user. + * + * @param {String} content + * The string to send. + */ + var sendString = function sendString(content) { + + var sentText = ""; + + // Send each codepoint within the string + for (var i=0; i + + + {{text | translate}} + diff --git a/guacamole/src/main/webapp/app/textInput/templates/guacTextInput.html b/guacamole/src/main/webapp/app/textInput/templates/guacTextInput.html new file mode 100644 index 000000000..3adcdd737 --- /dev/null +++ b/guacamole/src/main/webapp/app/textInput/templates/guacTextInput.html @@ -0,0 +1,27 @@ +
+ + + +
{{text}}
+ +
diff --git a/guacamole/src/main/webapp/app/textInput/textInputModule.js b/guacamole/src/main/webapp/app/textInput/textInputModule.js new file mode 100644 index 000000000..89a1e79f8 --- /dev/null +++ b/guacamole/src/main/webapp/app/textInput/textInputModule.js @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Module for displaying the Guacamole text input method. + */ +angular.module('textInput', []);