mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17: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',
|
directive.controller = ['$scope', '$injector', '$element',
|
||||||
function guacTiledClientsController($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
|
* Returns a callback for guacClick that assigns or updates keyboard
|
||||||
* receive and handle keyboard events. Multiple clients may have
|
* focus to the given client, allowing that client to receive and
|
||||||
* keyboard focus simultaneously.
|
* handle keyboard events. Multiple clients may have keyboard focus
|
||||||
|
* simultaneously.
|
||||||
*
|
*
|
||||||
* @param {ManagedClient} client
|
* @param {ManagedClient} client
|
||||||
* The client that should receive keyboard focus.
|
* 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
|
// Clear focus of all other clients if not selecting multiple
|
||||||
$scope.clientGroup.clients.forEach(client => {
|
if (!shift && !ctrl) {
|
||||||
client.clientProperties.focused = false;
|
$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-repeat="client in clientGroup.clients"
|
||||||
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }"
|
ng-style="{ 'width' : getTileWidth(), 'height' : getTileHeight() }"
|
||||||
ng-class="{ 'focused' : client.clientProperties.focused }"
|
ng-class="{ 'focused' : client.clientProperties.focused }"
|
||||||
ng-click="assignFocus(client)">
|
guac-click="getFocusAssignmentCallback(client)">
|
||||||
|
|
||||||
<h3>{{ client.title }}</h3>
|
<h3>{{ client.title }}</h3>
|
||||||
<guac-client client="client" emulate-absolute-mouse="emulateAbsoluteMouse"></guac-client>
|
<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;
|
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