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
|
||||
delvh marked this conversation as resolved
Outdated
kske
commented
Change "Controllers" and "Scenes" to lowercase and "fxml" to all caps. Change "Controllers" and "Scenes" to lowercase and "fxml" to all caps.
|
||||
* 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.fxml.FXMLLoader;
|
||||
import javafx.scene.*;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import envoy.client.data.Settings;
|
||||
import envoy.client.data.shortcuts.*;
|
||||
import envoy.client.event.*;
|
||||
import envoy.client.helper.ShutdownHelper;
|
||||
import envoy.client.util.UserUtil;
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
@ -102,12 +99,17 @@ public final class SceneContext implements EventListener {
|
||||
try {
|
||||
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
|
||||
final var scene = new Scene(rootNode);
|
||||
controllerStack.push(loader.getController());
|
||||
final var controller = loader.getController();
|
||||
controllerStack.push(controller);
|
||||
|
||||
sceneStack.push(scene);
|
||||
stage.setScene(scene);
|
||||
|
||||
supplyKeyboardShortcuts(sceneInfo, scene);
|
||||
// Supply the global custom keyboard shortcuts for that scene
|
||||
scene.getAccelerators().putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
|
||||
delvh marked this conversation as resolved
Outdated
kske
commented
Wouldn't this be easier using Wouldn't this be easier using `addAll`, or does this not fit here?
delvh
commented
How am I supposed to How am I supposed to `addAll` for a custom object type that has no connection to the original? Even if I'd store it in an `Entry` instead of an implementation of entry, I couldn't use addAll.
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
*
|
||||
|
@ -11,6 +11,7 @@ import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
|
||||
import envoy.client.helper.ShutdownHelper;
|
||||
import envoy.client.net.Client;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
@ -84,6 +85,9 @@ public final class Startup extends Application {
|
||||
stage.setTitle("Envoy");
|
||||
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
||||
|
||||
// Configure global shortcuts
|
||||
delvh marked this conversation as resolved
Outdated
kske
commented
Consider changing this to "Configure global shortcuts" Consider changing this to "Configure global shortcuts"
|
||||
EnvoyShortcutConfig.initializeEnvoyShortcuts();
|
||||
|
||||
// Create scene context
|
||||
final var sceneContext = new SceneContext(stage);
|
||||
context.setSceneContext(sceneContext);
|
||||
@ -113,7 +117,7 @@ public final class Startup extends Application {
|
||||
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
|
||||
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
|
||||
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 {
|
||||
client.performHandshake(credentials, cacheMap);
|
||||
if (client.isOnline()) {
|
||||
|
@ -1,9 +1,13 @@
|
||||
package envoy.client.ui.controller;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.*;
|
||||
|
||||
import envoy.client.data.Context;
|
||||
import envoy.client.data.shortcuts.KeyboardMapping;
|
||||
import envoy.client.ui.listcell.ListCellFactory;
|
||||
import envoy.client.ui.settings.*;
|
||||
|
||||
@ -13,7 +17,7 @@ import envoy.client.ui.settings.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public final class SettingsScene {
|
||||
public final class SettingsScene implements KeyboardMapping {
|
||||
|
||||
@FXML
|
||||
private ListView<SettingsPane> settingsList;
|
||||
@ -38,4 +42,9 @@ public final class SettingsScene {
|
||||
|
||||
@FXML
|
||||
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);
|
||||
delvh marked this conversation as resolved
Outdated
kske
commented
Maybe use Maybe use `Map.of()` here instead.
|
||||
}
|
||||
}
|
||||
|
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.