Add Enhanced Keyboard Shortcut Mechanism (#91)
Reviewed-on: https://git.kske.dev/zdm/envoy/pulls/91 Reviewed-by: DieGurke <maxi@kske.dev> Reviewed-by: kske <kai@kske.dev>
This commit is contained in:
		@@ -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();
 | 
			
		||||
 | 
			
		||||
		// 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.
 | 
			
		||||
 *
 | 
			
		||||
 * @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>());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @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.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));
 | 
			
		||||
 | 
			
		||||
			// 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
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user