mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-724: Allow multiple tiled clients to be focused using Shift+Click and Ctrl+Click.
This commit is contained in:
@@ -52,23 +52,64 @@ angular.module('client').directive('guacTiledClients', [function guacTiledClient
|
||||
directive.controller = ['$scope', '$injector', '$element',
|
||||
function guacTiledClientsController($scope, $injector, $element) {
|
||||
|
||||
// Required types
|
||||
var ManagedClientGroup = $injector.get('ManagedClientGroup');
|
||||
|
||||
/**
|
||||
* Assigns keyboard focus to the given client, allowing that client to
|
||||
* receive and handle keyboard events. Multiple clients may have
|
||||
* keyboard focus simultaneously.
|
||||
* Returns a callback for guacClick that assigns or updates keyboard
|
||||
* focus to the given client, allowing that client to receive and
|
||||
* handle keyboard events. Multiple clients may have keyboard focus
|
||||
* simultaneously.
|
||||
*
|
||||
* @param {ManagedClient} client
|
||||
* The client that should receive keyboard focus.
|
||||
*
|
||||
* @return {guacClick~callback}
|
||||
* The callback that guacClient should invoke when the given client
|
||||
* has been clicked.
|
||||
*/
|
||||
$scope.assignFocus = function assignFocus(client) {
|
||||
$scope.getFocusAssignmentCallback = function getFocusAssignmentCallback(client) {
|
||||
return (shift, ctrl) => {
|
||||
|
||||
// Clear focus of all other clients
|
||||
$scope.clientGroup.clients.forEach(client => {
|
||||
client.clientProperties.focused = false;
|
||||
});
|
||||
// Clear focus of all other clients if not selecting multiple
|
||||
if (!shift && !ctrl) {
|
||||
$scope.clientGroup.clients.forEach(client => {
|
||||
client.clientProperties.focused = false;
|
||||
});
|
||||
}
|
||||
|
||||
client.clientProperties.focused = true;
|
||||
client.clientProperties.focused = true;
|
||||
|
||||
// Fill in any gaps if performing rectangular multi-selection
|
||||
// via shift-click
|
||||
if (shift) {
|
||||
|
||||
var minRow = $scope.clientGroup.rows - 1;
|
||||
var minColumn = $scope.clientGroup.columns - 1;
|
||||
var maxRow = 0;
|
||||
var maxColumn = 0;
|
||||
|
||||
// Determine extents of selected area
|
||||
ManagedClientGroup.forEach($scope.clientGroup, (client, row, column) => {
|
||||
if (client.clientProperties.focused) {
|
||||
minRow = Math.min(minRow, row);
|
||||
minColumn = Math.min(minColumn, column);
|
||||
maxRow = Math.max(maxRow, row);
|
||||
maxColumn = Math.max(maxColumn, column);
|
||||
}
|
||||
});
|
||||
|
||||
ManagedClientGroup.forEach($scope.clientGroup, (client, row, column) => {
|
||||
client.clientProperties.focused =
|
||||
row >= minRow
|
||||
&& row <= maxRow
|
||||
&& column >= minColumn
|
||||
&& column <= maxColumn;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -4,7 +4,7 @@
|
||||
ng-repeat="client in clientGroup.clients"
|
||||
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }"
|
||||
ng-class="{ 'focused' : client.clientProperties.focused }"
|
||||
ng-click="assignFocus(client)">
|
||||
guac-click="getFocusAssignmentCallback(client)">
|
||||
|
||||
<h3>{{ client.title }}</h3>
|
||||
<guac-client client="client" emulate-absolute-mouse="emulateAbsoluteMouse"></guac-client>
|
||||
|
@@ -220,6 +220,47 @@ angular.module('client').factory('ManagedClientGroup', ['$injector', function de
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback that is invoked for a ManagedClient within a ManagedClientGroup.
|
||||
*
|
||||
* @callback ManagedClientGroup~clientCallback
|
||||
* @param {ManagedClient} client
|
||||
* The relevant ManagedClient.
|
||||
*
|
||||
* @param {number} row
|
||||
* The row number of the client within the tiled grid, where 0 is the
|
||||
* first row.
|
||||
*
|
||||
* @param {number} column
|
||||
* The column number of the client within the tiled grid, where 0 is
|
||||
* the first column.
|
||||
*
|
||||
* @param {number} index
|
||||
* The index of the client within the relevant
|
||||
* {@link ManagedClientGroup#clients} array.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loops through each of the clients associated with the given
|
||||
* ManagedClientGroup, invoking the given callback for each client.
|
||||
*
|
||||
* @param {ManagedClientGroup} group
|
||||
* The ManagedClientGroup to loop through.
|
||||
*
|
||||
* @param {ManagedClientGroup~clientCallback} callback
|
||||
* The callback to invoke for each of the clients within the given
|
||||
* ManagedClientGroup.
|
||||
*/
|
||||
ManagedClientGroup.forEach = function forEach(group, callback) {
|
||||
var current = 0;
|
||||
for (var row = 0; row < group.rows; row++) {
|
||||
for (var column = 0; column < group.columns; column++) {
|
||||
callback(group.clients[current], row, column, current);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return ManagedClientGroup;
|
||||
|
||||
}]);
|
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A directive which provides handling of click and click-like touch events.
|
||||
* The state of Shift and Ctrl modifiers is tracked through these click events
|
||||
* to allow for specific handling of Shift+Click and Ctrl+Click.
|
||||
*/
|
||||
angular.module('element').directive('guacClick', [function guacClick() {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
|
||||
link: function linkGuacClick($scope, $element, $attrs) {
|
||||
|
||||
/**
|
||||
* A callback that is invoked by the guacClick directive when a
|
||||
* click or click-like event is received.
|
||||
*
|
||||
* @callback guacClick~callback
|
||||
* @param {boolean} shift
|
||||
* Whether Shift was held down at the time the click occurred.
|
||||
*
|
||||
* @param {boolean} ctrl
|
||||
* Whether Ctrl or Meta (the Mac "Command" key) was held down
|
||||
* at the time the click occurred.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The callback to invoke when a click or click-like event is
|
||||
* received on the assocaited element.
|
||||
*
|
||||
* @type guacClick~callback
|
||||
*/
|
||||
var guacClick = $scope.$eval($attrs.guacClick);
|
||||
|
||||
/**
|
||||
* The element which will register the click.
|
||||
*
|
||||
* @type Element
|
||||
*/
|
||||
var element = $element[0];
|
||||
|
||||
/**
|
||||
* Whether either Shift key is currently pressed.
|
||||
*
|
||||
* @type boolean
|
||||
*/
|
||||
var shift = false;
|
||||
|
||||
/**
|
||||
* Whether either Ctrl key is currently pressed. To allow the
|
||||
* Command key to be used on Mac platforms, this flag also
|
||||
* considers the state of either Meta key.
|
||||
*
|
||||
* @type boolean
|
||||
*/
|
||||
var ctrl = false;
|
||||
|
||||
/**
|
||||
* Updates the state of the {@link shift} and {@link ctrl} flags
|
||||
* based on which keys are currently marked as held down by the
|
||||
* given Guacamole.Keyboard.
|
||||
*
|
||||
* @param {Guacamole.Keyboard} keyboard
|
||||
* The Guacamole.Keyboard instance to read key states from.
|
||||
*/
|
||||
var updateModifiers = function updateModifiers(keyboard) {
|
||||
|
||||
shift = !!(
|
||||
keyboard.pressed[0xFFE1] // Left shift
|
||||
|| keyboard.pressed[0xFFE2] // Right shift
|
||||
);
|
||||
|
||||
ctrl = !!(
|
||||
keyboard.pressed[0xFFE3] // Left ctrl
|
||||
|| keyboard.pressed[0xFFE4] // Right ctrl
|
||||
|| keyboard.pressed[0xFFE7] // Left meta (command)
|
||||
|| keyboard.pressed[0xFFE8] // Right meta (command)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
// Update tracking of modifier states for each key press
|
||||
$scope.$on('guacKeydown', function keydownListener(event, keysym, keyboard) {
|
||||
updateModifiers(keyboard);
|
||||
});
|
||||
|
||||
// Update tracking of modifier states for each key release
|
||||
$scope.$on('guacKeyup', function keyupListener(event, keysym, keyboard) {
|
||||
updateModifiers(keyboard);
|
||||
});
|
||||
|
||||
// Fire provided callback for each mouse-initiated "click" event ...
|
||||
element.addEventListener('click', function elementClicked(e) {
|
||||
if (element.contains(e.target))
|
||||
$scope.$apply(() => guacClick(shift, ctrl));
|
||||
});
|
||||
|
||||
// ... and for touch-initiated click-like events
|
||||
element.addEventListener('touchstart', function elementClicked(e) {
|
||||
if (element.contains(e.target))
|
||||
$scope.$apply(() => guacClick(shift, ctrl));
|
||||
});
|
||||
|
||||
} // end guacClick link function
|
||||
|
||||
};
|
||||
|
||||
}]);
|
Reference in New Issue
Block a user