Add Enhanced Keyboard Shortcut Mechanism #91
@ -0,0 +1,44 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import javafx.scene.input.*;
|
||||||
|
|
||||||
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.helper.ShutdownHelper;
|
||||||
|
import envoy.client.ui.SceneContext.SceneInfo;
|
||||||
|
import envoy.client.util.UserUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoy-specific implementation of the keyboard-shortcut interaction offered by
|
||||||
|
* {@link GlobalKeyShortcuts}.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class EnvoyShortcutConfig {
|
||||||
|
|
||||||
|
private EnvoyShortcutConfig() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies the default shortcuts for {@link GlobalKeyShortcuts}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void initializeEnvoyShortcuts() {
|
||||||
|
final var instance = GlobalKeyShortcuts.getInstance();
|
||||||
|
|
||||||
delvh marked this conversation as resolved
|
|||||||
|
// Add the option to exit with "Control" + "Q" or "Alt" + "F4" as offered by
|
||||||
|
// some desktop environments
|
||||||
|
instance.add(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), ShutdownHelper::exit);
|
||||||
|
|
||||||
|
// Add the option to logout using "Control"+"Shift"+"L" if not in login scene
|
||||||
|
instance.addForNotExcluded(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
|
||||||
|
UserUtil::logout,
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to open settings scene with "Control"+"S", if not in login scene
|
||||||
|
instance.addForNotExcluded(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN),
|
||||||
|
() -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
|
||||||
|
SceneInfo.SETTINGS_SCENE,
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
|
||||||
|
import envoy.client.ui.SceneContext.SceneInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all keyboard shortcuts used throughout the application.
|
||||||
|
*
|
||||||
delvh marked this conversation as resolved
kske
commented
Change "KeyBoardshortcuts" to "keyboard shortcuts". Change "KeyBoardshortcuts" to "keyboard shortcuts".
|
|||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class GlobalKeyShortcuts {
|
||||||
|
|
||||||
|
private final EnumMap<SceneInfo, Map<KeyCombination, Runnable>> shortcuts = new EnumMap<>(SceneInfo.class);
|
||||||
|
|
||||||
|
private static GlobalKeyShortcuts instance = new GlobalKeyShortcuts();
|
||||||
|
|
||||||
|
private GlobalKeyShortcuts() {
|
||||||
|
for (final var sceneInfo : SceneInfo.values())
|
||||||
|
shortcuts.put(sceneInfo, new HashMap<KeyCombination, Runnable>());
|
||||||
|
}
|
||||||
delvh marked this conversation as resolved
kske
commented
What purpose does this serve, and why is this an inner class instead of a nested one? What purpose does this serve, and why is this an inner class instead of a nested one?
delvh
commented
1. Now it's a nested class
2. I need to store two parameters for an unknown amount of time, and I don't want to always redeclare the `Entry` interface when implementing it.
delvh
commented
Okay. I agree. It's unnecessary. Okay. I agree. It's unnecessary.
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @return the instance of global keyboard shortcuts.
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static GlobalKeyShortcuts getInstance() { return instance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given keyboard shortcut and its action to all scenes.
|
||||||
|
*
|
||||||
|
* @param keys the keys to press to perform the given action
|
||||||
|
* @param action the action to perform
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void add(KeyCombination keys, Runnable action) { shortcuts.values().forEach(collection -> collection.put(keys, action)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given keyboard shortcut and its action to all scenes that are not
|
||||||
|
* part of exclude.
|
||||||
|
*
|
||||||
|
* @param keys the keys to press to perform the given action
|
||||||
|
* @param action the action to perform
|
||||||
|
* @param exclude the scenes that should be excluded from receiving this
|
||||||
|
* keyboard shortcut
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void addForNotExcluded(KeyCombination keys, Runnable action, SceneInfo... exclude) {
|
||||||
|
|
||||||
|
// Computing the remaining sceneInfos
|
||||||
|
final var include = new SceneInfo[SceneInfo.values().length - exclude.length];
|
||||||
|
int index = 0;
|
||||||
|
outer:
|
||||||
|
for (final var sceneInfo : SceneInfo.values()) {
|
||||||
|
for (final var excluded : exclude)
|
||||||
|
if (sceneInfo.equals(excluded)) continue outer;
|
||||||
|
include[index++] = sceneInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the action to the remaining sceneInfos
|
||||||
|
for (final var sceneInfo : include)
|
||||||
|
shortcuts.get(sceneInfo).put(keys, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all stored keyboard shortcuts for the given scene constant.
|
||||||
|
*
|
||||||
|
* @param sceneInfo the currently loading scene
|
||||||
|
* @return all stored keyboard shortcuts for this scene
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts(SceneInfo sceneInfo) { return shortcuts.get(sceneInfo); }
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides methods to set the keyboard shortcuts for a specific scene.
|
||||||
|
* Should only be implemented by controllers of scenes so that these methods can
|
||||||
|
* automatically be called inside {@link SceneContext} as soon
|
||||||
|
* as the underlying FXML file has been loaded.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public interface KeyboardMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all keyboard shortcuts of a scene
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
Map<KeyCombination, Runnable> getKeyboardShortcuts();
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Contains the necessary classes to enable using keyboard shortcuts in Envoy.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.data.shortcuts;
|
@ -7,14 +7,11 @@ import java.util.logging.Level;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.*;
|
import javafx.scene.*;
|
||||||
import javafx.scene.input.*;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import envoy.client.data.Settings;
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.client.data.shortcuts.*;
|
||||||
import envoy.client.event.*;
|
import envoy.client.event.*;
|
||||||
import envoy.client.helper.ShutdownHelper;
|
|
||||||
import envoy.client.util.UserUtil;
|
|
||||||
import envoy.data.User.UserStatus;
|
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import dev.kske.eventbus.*;
|
||||||
@ -102,12 +99,17 @@ public final class SceneContext implements EventListener {
|
|||||||
try {
|
try {
|
||||||
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
|
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
|
||||||
final var scene = new Scene(rootNode);
|
final var scene = new Scene(rootNode);
|
||||||
controllerStack.push(loader.getController());
|
final var controller = loader.getController();
|
||||||
|
controllerStack.push(controller);
|
||||||
|
|
||||||
sceneStack.push(scene);
|
sceneStack.push(scene);
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
|
|
||||||
supplyKeyboardShortcuts(sceneInfo, scene);
|
// Supply the global custom keyboard shortcuts for that scene
|
||||||
|
scene.getAccelerators().putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
|
||||||
|
|
||||||
|
// Supply the scene specific keyboard shortcuts
|
||||||
|
if (controller instanceof KeyboardMapping) scene.getAccelerators().putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
|
||||||
|
|
||||||
// The LoginScene is the only scene not intended to be resized
|
// The LoginScene is the only scene not intended to be resized
|
||||||
// As strange as it seems, this is needed as otherwise the LoginScene won't be
|
// As strange as it seems, this is needed as otherwise the LoginScene won't be
|
||||||
@ -122,35 +124,6 @@ public final class SceneContext implements EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void supplyKeyboardShortcuts(SceneInfo sceneInfo, final Scene scene) {
|
|
||||||
final var accelerators = scene.getAccelerators();
|
|
||||||
|
|
||||||
// Add the option to exit Linux-like with "Control" + "Q" or "Alt" + "F4"
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), ShutdownHelper::exit);
|
|
||||||
|
|
||||||
if (sceneInfo != SceneInfo.LOGIN_SCENE) {
|
|
||||||
|
|
||||||
// Add the option to logout using "Control"+"Shift"+"L"
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), UserUtil::logout);
|
|
||||||
|
|
||||||
// Add the option to change status using "Control" + "Shift" +
|
|
||||||
// (o)F(fline)/ A(way)/ B(usy)/(o)N(line)
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
|
|
||||||
() -> UserUtil.changeStatus(UserStatus.OFFLINE));
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
|
|
||||||
() -> UserUtil.changeStatus(UserStatus.AWAY));
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
|
|
||||||
() -> UserUtil.changeStatus(UserStatus.BUSY));
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
|
|
||||||
() -> UserUtil.changeStatus(UserStatus.ONLINE));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the option to open the settings scene with "Control"+"S", if being in
|
|
||||||
// chat scene
|
|
||||||
if (sceneInfo == SceneInfo.CHAT_SCENE)
|
|
||||||
accelerators.put(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN), () -> load(SceneInfo.SETTINGS_SCENE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current scene and displays the previous one.
|
* Removes the current scene and displays the previous one.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@ import javafx.scene.control.Alert.AlertType;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
|
||||||
import envoy.client.helper.ShutdownHelper;
|
import envoy.client.helper.ShutdownHelper;
|
||||||
import envoy.client.net.Client;
|
import envoy.client.net.Client;
|
||||||
import envoy.client.ui.SceneContext.SceneInfo;
|
import envoy.client.ui.SceneContext.SceneInfo;
|
||||||
@ -84,6 +85,9 @@ public final class Startup extends Application {
|
|||||||
stage.setTitle("Envoy");
|
stage.setTitle("Envoy");
|
||||||
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
||||||
|
|
||||||
|
// Configure global shortcuts
|
||||||
|
EnvoyShortcutConfig.initializeEnvoyShortcuts();
|
||||||
|
|
||||||
// Create scene context
|
// Create scene context
|
||||||
final var sceneContext = new SceneContext(stage);
|
final var sceneContext = new SceneContext(stage);
|
||||||
context.setSceneContext(sceneContext);
|
context.setSceneContext(sceneContext);
|
||||||
@ -113,7 +117,7 @@ public final class Startup extends Application {
|
|||||||
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
|
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
|
||||||
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
|
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
|
||||||
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
|
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
|
||||||
final var originalStatus = localDB.getUser().getStatus();
|
final var originalStatus = localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
|
||||||
try {
|
try {
|
||||||
client.performHandshake(credentials, cacheMap);
|
client.performHandshake(credentials, cacheMap);
|
||||||
if (client.isOnline()) {
|
if (client.isOnline()) {
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package envoy.client.ui.controller;
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.*;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.data.shortcuts.KeyboardMapping;
|
||||||
import envoy.client.ui.listcell.ListCellFactory;
|
import envoy.client.ui.listcell.ListCellFactory;
|
||||||
import envoy.client.ui.settings.*;
|
import envoy.client.ui.settings.*;
|
||||||
|
|
||||||
@ -13,7 +17,7 @@ import envoy.client.ui.settings.*;
|
|||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class SettingsScene {
|
public final class SettingsScene implements KeyboardMapping {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ListView<SettingsPane> settingsList;
|
private ListView<SettingsPane> settingsList;
|
||||||
@ -38,4 +42,9 @@ public final class SettingsScene {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void backButtonClicked() { Context.getInstance().getSceneContext().pop(); }
|
private void backButtonClicked() { Context.getInstance().getSceneContext().pop(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
|
||||||
|
return Map.of(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN), this::backButtonClicked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user
Stop calling this Linux-like. It has nothing to do with Linux. At best it's GNOME-like or GTK-like.