C keybindings AC: * All keybindings can be easily changed in the start screen * Buttons are replacable on clicking on the current button assigned. * When the current button assigned is clicked the text should turn "blank" and return to with text after a new button has been pressed or escape has been pressed. * There cannot be more than one active replace button at a time. * Reset to default should reset to the keys defined by the P.O (but with UP, DOWN instead of PG_UP, PG_DWN) * Buttons should not save until the user exits with "save" See merge request !35main
commit
28d27a7b2b
@ -0,0 +1,247 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
import visualiser.gameController.Keys.ControlKey;
|
||||||
|
import visualiser.gameController.Keys.KeyFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static visualiser.app.App.keyFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the scene used to display and update current key bindings.
|
||||||
|
*/
|
||||||
|
public class KeyBindingsController {
|
||||||
|
private @FXML Button btnSave;
|
||||||
|
private @FXML Button btnCancel;
|
||||||
|
private @FXML Button btnReset;
|
||||||
|
private @FXML ListView lstControl;
|
||||||
|
private @FXML ListView lstKey;
|
||||||
|
private @FXML ListView lstDescription;
|
||||||
|
private @FXML AnchorPane anchor;
|
||||||
|
private KeyFactory newKeyFactory;
|
||||||
|
private Boolean changed = false; // keyBindings have been modified
|
||||||
|
private Button currentButton = null; // last button clicked
|
||||||
|
|
||||||
|
public void initialize(){
|
||||||
|
// create new key factory to modify, keeping the existing one safe
|
||||||
|
newKeyFactory = copyExistingFactory();
|
||||||
|
initializeTable();
|
||||||
|
populateTable();
|
||||||
|
setKeyListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up table before populating it.
|
||||||
|
* Set up includes headings, CSS styling and modifying default properties.
|
||||||
|
*/
|
||||||
|
public void initializeTable(){
|
||||||
|
// set the headings for each column
|
||||||
|
lstKey.getItems().add("Key");
|
||||||
|
lstControl.getItems().add("Command");
|
||||||
|
lstDescription.getItems().add("Description");
|
||||||
|
lstKey.getSelectionModel().select(0);
|
||||||
|
lstControl.getSelectionModel().select(0);
|
||||||
|
lstDescription.getSelectionModel().select(0);
|
||||||
|
|
||||||
|
// add CSS stylesheet once the scene has been created
|
||||||
|
lstKey.sceneProperty().addListener((obs, oldScene, newScene) -> {
|
||||||
|
if (newScene != null) {
|
||||||
|
newScene.getStylesheets().add("/css/keyBindings.css");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// stop the columns from being selectable, so only the buttons are
|
||||||
|
lstKey.getSelectionModel().selectedItemProperty()
|
||||||
|
.addListener((observable, oldvalue, newValue) ->
|
||||||
|
Platform.runLater(() ->
|
||||||
|
lstKey.getSelectionModel().select(0)));
|
||||||
|
lstDescription.getSelectionModel().selectedItemProperty()
|
||||||
|
.addListener((observable, oldvalue, newValue) ->
|
||||||
|
Platform.runLater(() ->
|
||||||
|
lstDescription.getSelectionModel().select(0)));
|
||||||
|
lstControl.getSelectionModel().selectedItemProperty()
|
||||||
|
.addListener((observable, oldvalue, newValue) ->
|
||||||
|
Platform.runLater(() ->
|
||||||
|
lstControl.getSelectionModel().select(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the table with commands and their key binding details.
|
||||||
|
*/
|
||||||
|
public void populateTable(){
|
||||||
|
// add each command to the table
|
||||||
|
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet()) {
|
||||||
|
// create button for command
|
||||||
|
Button button = new Button(entry.getKey());
|
||||||
|
button.setMinWidth(120);
|
||||||
|
button.setId(entry.getValue().toString());
|
||||||
|
button.setOnAction(e -> currentButton = button);
|
||||||
|
// display details for command in table
|
||||||
|
lstControl.getItems().add(entry.getValue());
|
||||||
|
lstKey.getItems().add(button);
|
||||||
|
lstDescription.getItems().add(entry.getValue().getProtocolCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a copy of the {@link KeyFactory} that does not modify the original.
|
||||||
|
* @return new keyfactory to be modified
|
||||||
|
*/
|
||||||
|
public KeyFactory copyExistingFactory(){
|
||||||
|
newKeyFactory = new KeyFactory();
|
||||||
|
Map<String, ControlKey> oldKeyState = keyFactory.getKeyState();
|
||||||
|
Map<String, ControlKey> newKeyState = new HashMap<>();
|
||||||
|
|
||||||
|
// copy over commands and their keys
|
||||||
|
for (Map.Entry<String, ControlKey> entry : oldKeyState.entrySet()){
|
||||||
|
newKeyState.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
newKeyFactory.setKeyState(newKeyState);
|
||||||
|
return newKeyFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a listener for the base anchorpane for key presses.
|
||||||
|
* It updates the current key bindings of the {@link KeyFactory} if
|
||||||
|
* required.
|
||||||
|
*/
|
||||||
|
public void setKeyListener(){
|
||||||
|
anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||||
|
// if esc, cancel current button click
|
||||||
|
if (event.getCode() == KeyCode.ESCAPE){
|
||||||
|
btnCancel.requestFocus();
|
||||||
|
currentButton = null;
|
||||||
|
}
|
||||||
|
// if a button was clicked
|
||||||
|
else if (currentButton != null) {
|
||||||
|
// check if a button is already mapped to this key
|
||||||
|
for (int i = 1; i < lstKey.getItems().size(); i++) {
|
||||||
|
Button button = (Button)lstKey.getItems().get(i);
|
||||||
|
// update buttons text and remove key binding from command
|
||||||
|
if (button.getText().equals(event.getCode().toString())) {
|
||||||
|
button.setText("");
|
||||||
|
newKeyFactory.updateKey(button.getId(), button.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update text on the button
|
||||||
|
currentButton.setText(event.getCode().toString());
|
||||||
|
// update the control key
|
||||||
|
newKeyFactory.updateKey(event.getCode().toString(),
|
||||||
|
currentButton.getId());
|
||||||
|
// remove current button selection
|
||||||
|
currentButton = null;
|
||||||
|
changed = true;
|
||||||
|
btnCancel.requestFocus();
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel and exits the key bindings menu. Changes are not forced to be
|
||||||
|
* saved or fixed if invalid, and instead are defaulted back to the last
|
||||||
|
* successful saved state.
|
||||||
|
*/
|
||||||
|
public void cancel(){
|
||||||
|
((Stage)btnCancel.getScene().getWindow()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all key bindings to the built-in defaults.
|
||||||
|
*/
|
||||||
|
public void reset(){
|
||||||
|
lstKey.getItems().clear();
|
||||||
|
lstControl.getItems().clear();
|
||||||
|
lstDescription.getItems().clear();
|
||||||
|
newKeyFactory = new KeyFactory();
|
||||||
|
initializeTable();
|
||||||
|
populateTable();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace existing {@link KeyFactory} with the modified key bindings.
|
||||||
|
*/
|
||||||
|
public void save(){
|
||||||
|
if (isFactoryValid()) {
|
||||||
|
keyFactory = newKeyFactory;
|
||||||
|
newKeyFactory = new KeyFactory();
|
||||||
|
changed = false;
|
||||||
|
keyFactory.save(); // save persistently
|
||||||
|
loadNotification("Key bindings were successfully saved.", false);
|
||||||
|
} else {
|
||||||
|
loadNotification("One or more key bindings are missing. " +
|
||||||
|
"Failed to save.", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the {@link KeyFactory} being modified is valid and that no
|
||||||
|
* commands are missing a key binding.
|
||||||
|
* @return True if valid, false if invalid
|
||||||
|
*/
|
||||||
|
public Boolean isFactoryValid(){
|
||||||
|
for (Map.Entry<String, ControlKey> entry : newKeyFactory.getKeyState().entrySet
|
||||||
|
()) {
|
||||||
|
if (entry.getKey().toString()==entry.getValue().toString()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to stop a user from exiting key bindings without saving
|
||||||
|
* their changes to the {@link KeyFactory}.
|
||||||
|
* @param we {@link WindowEvent} close request to be consumed if settings
|
||||||
|
* have not been successfully saved.
|
||||||
|
*/
|
||||||
|
public void onExit(WindowEvent we){
|
||||||
|
// if modified KeyFactory hasn't been saved
|
||||||
|
if (changed){
|
||||||
|
loadNotification("Please cancel or save your changes before exiting" +
|
||||||
|
".", true);
|
||||||
|
we.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a popup window giving confirmation/warning of user activity.
|
||||||
|
* @param message the message to be displayed to the user
|
||||||
|
* @param warning true if the message to be displayed is due to user error
|
||||||
|
*/
|
||||||
|
public void loadNotification(String message, Boolean warning){
|
||||||
|
Parent root = null;
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource
|
||||||
|
("/visualiser/scenes/notification.fxml"));
|
||||||
|
try {
|
||||||
|
root = loader.load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
NotificationController controller = loader.getController();
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.setScene(new Scene(root));
|
||||||
|
stage.centerOnScreen();
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
stage.show();
|
||||||
|
// displays given message in the window
|
||||||
|
controller.setMessage(message, warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package visualiser.Controllers;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for a popup notification regarding user activity.
|
||||||
|
*/
|
||||||
|
public class NotificationController {
|
||||||
|
private @FXML Label lblDescription;
|
||||||
|
private @FXML Text txtMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the popup window once clicked.
|
||||||
|
*/
|
||||||
|
public void ok(){
|
||||||
|
((Stage)lblDescription.getScene().getWindow()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the appropriate popup notification.
|
||||||
|
* @param message message for the user
|
||||||
|
* @param warning if true warning text shown, if false success text shown
|
||||||
|
*/
|
||||||
|
public void setMessage(String message, Boolean warning){
|
||||||
|
lblDescription.setText(message);
|
||||||
|
if (!warning){
|
||||||
|
txtMessage.setText("Success!");
|
||||||
|
txtMessage.setFill(Color.GREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
.list-view .list-cell {
|
||||||
|
-fx-cell-size: 40;
|
||||||
|
-fx-font-family: "Tahoma";
|
||||||
|
-fx-background-color: #fffff0;
|
||||||
|
-fx-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view .list-cell:even {
|
||||||
|
-fx-background-color: #ffffdc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view .list-cell:selected {
|
||||||
|
-fx-background-color: #f9e5c3;
|
||||||
|
-fx-font-family: "Comic Sans MS";
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-font-size: 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
-fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
-fx-text-fill: #242d35;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-font-family: "Courier New";
|
||||||
|
-fx-border-color: #a9c8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:focused {
|
||||||
|
-fx-background-color: white;
|
||||||
|
-fx-border-color: #0056bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#anchor {
|
||||||
|
-fx-background-color: #fdfac3;
|
||||||
|
}
|
||||||
|
#menu{
|
||||||
|
-fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%);
|
||||||
|
-fx-background-radius: 4px;
|
||||||
|
-fx-border-radius: 4px;
|
||||||
|
-fx-text-fill: #242d35;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-font-family: "Verdana";
|
||||||
|
-fx-border-color: #a9c8ff;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<AnchorPane id="anchor" fx:id="anchor" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="471.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.KeyBindingsController">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="lblTitle" layoutX="172.0" layoutY="14.0" text="Key Bindings">
|
||||||
|
<font>
|
||||||
|
<Font name="Comic Sans MS" size="44.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Button id="menu" fx:id="btnCancel" layoutX="430.0" layoutY="428.0"
|
||||||
|
mnemonicParsing="false" onAction="#cancel" prefWidth="160.0" text="Cancel" AnchorPane.bottomAnchor="18.0" AnchorPane.rightAnchor="20.0" />
|
||||||
|
<ListView fx:id="lstControl" focusTraversable="false" layoutX="27.0" layoutY="88.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="322.0" prefWidth="150.0" />
|
||||||
|
<ListView fx:id="lstKey" focusTraversable="false" layoutX="176.0" layoutY="88.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="322.0" prefWidth="150.0" />
|
||||||
|
<ListView fx:id="lstDescription" focusTraversable="false" layoutX="325.0" layoutY="88.0" prefHeight="322.0" prefWidth="250.0" />
|
||||||
|
<Button id="menu" fx:id="btnSave" layoutX="220.0" layoutY="428.0" mnemonicParsing="false" onAction="#save" prefWidth="160.0" text="Save" AnchorPane.bottomAnchor="18.0" />
|
||||||
|
<Button id="menu" fx:id="btnReset" layoutX="20.0" layoutY="428.0" mnemonicParsing="false" onAction="#reset" prefWidth="160.0" text="Reset to Default" AnchorPane.bottomAnchor="18.0" AnchorPane.leftAnchor="20.0" />
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
|
||||||
|
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.NotificationController">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="btnOk" layoutX="110.0" layoutY="65.0" mnemonicParsing="false" onAction="#ok" prefWidth="80.0" text="Ok" />
|
||||||
|
<Text fx:id="txtMessage" fill="RED" layoutY="23.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Warning!" textAlignment="CENTER" wrappingWidth="300.0">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
</Text>
|
||||||
|
<Label fx:id="lblDescription" alignment="CENTER" layoutY="36.0" prefHeight="17.0" prefWidth="300.0" textAlignment="CENTER" />
|
||||||
|
</children>
|
||||||
|
</Pane>
|
||||||
Loading…
Reference in new issue