Skip to content

Commit 26513ed

Browse files
committed
Add Keyboard Feature
Now the app can be used with just a keyboard.
1 parent c8274fc commit 26513ed

File tree

7 files changed

+157
-8
lines changed

7 files changed

+157
-8
lines changed

.vscode/launch.json

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
55
"version": "0.2.0",
66
"configurations": [
7+
{
8+
"type": "java",
9+
"name": "Launch Main",
10+
"request": "launch",
11+
"mainClass": "dev.sevora.Main",
12+
"projectName": "simple-calculator"
13+
},
714
{
815
"type": "java",
916
"name": "Launch Current File",

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>dev.sevora</groupId>
77
<artifactId>simple-calculator</artifactId>
8-
<version>1.0.1-SNAPSHOT</version>
8+
<version>1.5.0-SNAPSHOT</version>
99

1010
<name>simple-calculator</name>
1111
<!-- FIXME change it to the project's website -->

src/main/java/dev/sevora/App.java

+23-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import javafx.event.ActionEvent;
55
import javafx.scene.control.Button;
66
import javafx.scene.control.Label;
7+
import javafx.scene.input.KeyEvent;
78
import javafx.stage.Stage;
89

910
/**
@@ -14,7 +15,7 @@ public class App extends Application
1415
{
1516
private Calculator calculator = new Calculator(); // This is what is used for the functionality in terms of logic of the app.
1617
private Layout layout = new Layout(400, 600); // JavaFX Objects
17-
18+
1819
/**
1920
* Launches the JavaFX application.
2021
* @param args
@@ -41,6 +42,8 @@ public void start(Stage primaryStage) {
4142
}
4243
}
4344

45+
layout.getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> keyHandler(event));
46+
4447
primaryStage.setTitle("Simple Calculator");
4548
primaryStage.setScene( layout.getScene() );
4649
primaryStage.setResizable(false);
@@ -53,11 +56,29 @@ public void start(Stage primaryStage) {
5356
*/
5457
public void buttonHandler(ActionEvent event) {
5558
Button button = (Button) event.getSource();
56-
Calculator calculator = this.calculator;
5759
Label label = this.layout.getLabel();
5860

5961
calculator.punch(button.getText());
6062
label.setText(calculator.display());
6163
}
6264

65+
/**
66+
* This handles the keyboard input for the application.
67+
* @param event A JavaFX KeyEvent.
68+
*/
69+
public void keyHandler(KeyEvent event) {
70+
Label label = this.layout.getLabel();
71+
String toPunch = Keybindings.getInstructionFromEvent(event);
72+
73+
if (toPunch.length() > 0) {
74+
// I didn't make simulate click run the event and made it independent
75+
// because there are non-existent buttons such as backspace in the layout
76+
calculator.punch(toPunch);
77+
this.layout.simulateClickWithoutEvent(toPunch, 200);
78+
}
79+
80+
label.setText(calculator.display());
81+
event.consume();
82+
}
83+
6384
}

src/main/java/dev/sevora/Calculator.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ private void evaluate() {
157157
* and clears both the operation and the right side.
158158
*/
159159
private void evaluateOnce() {
160-
if (this.left.length() > 0 && this.right.length() > 0 && this.operation.length() > 0) {
160+
boolean hasE = (this.left.endsWith("E") || this.right.endsWith("E"));
161+
162+
if (!hasE && this.left.length() > 0 && this.right.length() > 0 && this.operation.length() > 0) {
161163
double left = Double.parseDouble(this.left);
162164
double right = Double.parseDouble(this.right);
163165
double result = 0.0;
@@ -249,7 +251,7 @@ public void clear() {
249251
public void backspace() {
250252
String number = getNumberAtSide().length() > 0 ? getNumberAtSide() : "0";
251253

252-
if (number.length() > 0) {
254+
if (!number.equals("0")) {
253255
number = number.substring(0, number.length() - 1);
254256
if (number.length() > 1 && number.charAt(number.length() - 1) == '.') {
255257
number = number.substring(0, number.length() - 1);
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package dev.sevora;
2+
3+
import java.util.Map;
4+
import static java.util.Map.entry;
5+
6+
import javafx.scene.input.KeyCode;
7+
import javafx.scene.input.KeyCodeCombination;
8+
import javafx.scene.input.KeyCombination;
9+
import javafx.scene.input.KeyEvent;
10+
11+
/**
12+
* Keybinding is a class meant to be used statically, it should hold
13+
* all the logic and values to figure out the calculator instruction from
14+
* a key event.
15+
* @author Ralph Louis Gopez
16+
*/
17+
public class Keybindings {
18+
19+
// Below are all the single keys
20+
public static Map<KeyCode, String> ALL_NORMAL = Map.ofEntries(
21+
entry(KeyCode.DIGIT0, "0"),
22+
entry(KeyCode.DIGIT1, "1"),
23+
entry(KeyCode.DIGIT2, "2"),
24+
entry(KeyCode.DIGIT3, "3"),
25+
entry(KeyCode.DIGIT4, "4"),
26+
entry(KeyCode.DIGIT5, "5"),
27+
entry(KeyCode.DIGIT6, "6"),
28+
entry(KeyCode.DIGIT7, "7"),
29+
entry(KeyCode.DIGIT8, "8"),
30+
entry(KeyCode.DIGIT9, "9"),
31+
32+
entry(KeyCode.NUMPAD0, "0"),
33+
entry(KeyCode.NUMPAD1, "1"),
34+
entry(KeyCode.NUMPAD2, "2"),
35+
entry(KeyCode.NUMPAD3, "3"),
36+
entry(KeyCode.NUMPAD4, "4"),
37+
entry(KeyCode.NUMPAD5, "5"),
38+
entry(KeyCode.NUMPAD6, "6"),
39+
entry(KeyCode.NUMPAD7, "7"),
40+
entry(KeyCode.NUMPAD8, "8"),
41+
entry(KeyCode.NUMPAD9, "9"),
42+
43+
entry(KeyCode.PLUS, "+"),
44+
entry(KeyCode.MINUS, "-"),
45+
entry(KeyCode.MULTIPLY, "x"),
46+
entry(KeyCode.X, "x"),
47+
entry(KeyCode.DIVIDE, "÷"),
48+
entry(KeyCode.SLASH, "÷"),
49+
50+
entry(KeyCode.ENTER, "="),
51+
entry(KeyCode.SPACE, "="),
52+
entry(KeyCode.EQUALS, "="),
53+
54+
entry(KeyCode.BACK_SPACE, "C"),
55+
entry(KeyCode.DELETE, "AC"),
56+
entry(KeyCode.PERIOD, ".")
57+
);
58+
59+
// Below are combination of keys for ease-of-use as not all
60+
// keyboards have a number pad.
61+
public static Map<KeyCombination, String> COMBINATIONS = Map.ofEntries(
62+
entry(new KeyCodeCombination(KeyCode.EQUALS, KeyCombination.SHIFT_DOWN), "+"),
63+
entry(new KeyCodeCombination(KeyCode.DIGIT8, KeyCombination.SHIFT_DOWN), "*"),
64+
entry(new KeyCodeCombination(KeyCode.DIGIT5, KeyCombination.SHIFT_DOWN), "%")
65+
);
66+
67+
/**
68+
* Use this method to figure out what instruction
69+
* a key event should be translated to.
70+
* @param event A JavaFX KeyEvent.
71+
* @return String that contains the instruction that can be used on the calculator.
72+
*/
73+
public static String getInstructionFromEvent(KeyEvent event) {
74+
KeyCode keyCode = event.getCode();
75+
76+
for (Map.Entry<KeyCombination, String> combination : Keybindings.COMBINATIONS.entrySet()) {
77+
if (combination.getKey().match(event)) {
78+
return combination.getValue();
79+
}
80+
}
81+
82+
if (Keybindings.ALL_NORMAL.containsKey(keyCode)) {
83+
return Keybindings.ALL_NORMAL.get(keyCode);
84+
}
85+
86+
return "";
87+
}
88+
}

src/main/java/dev/sevora/Layout.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package dev.sevora;
22

3+
import javafx.animation.PauseTransition;
34
import javafx.geometry.Insets;
45
import javafx.geometry.Pos;
56
import javafx.scene.Scene;
67
import javafx.scene.layout.ColumnConstraints;
78
import javafx.scene.layout.GridPane;
89
import javafx.scene.layout.Priority;
910
import javafx.scene.layout.RowConstraints;
11+
import javafx.util.Duration;
1012
import javafx.scene.control.Button;
1113
import javafx.scene.control.Label;
1214

@@ -19,7 +21,7 @@ public class Layout {
1921
private GridPane gridPane;
2022
private Scene scene;
2123
private Button[] buttons = new Button[20];
22-
private String[] buttonKeys = { // Could make this static.
24+
private static String[] BUTTON_KEYS = { // Could make this static.
2325
"AC", "+/-", "%", "÷",
2426
"7", "8", "9", "x",
2527
"4", "5", "6", "-",
@@ -100,9 +102,15 @@ private void fillGridPane(GridPane root, int columns, int rows) {
100102
if (index == 17) continue; // There's no 17th button.
101103
if (index > 19) break loop; // Also index > 19 won't work since (y+1)
102104

103-
buttons[index] = new Button(buttonKeys[index]);
105+
buttons[index] = new Button(Layout.BUTTON_KEYS[index]);
104106
Button button = buttons[index];
105107

108+
button.setFocusTraversable(false); // prevents focusing by TAB
109+
// This event handling makes it so that it doesn't focus on other nodes.
110+
button.focusedProperty().addListener((observable, old, hasNew) -> {
111+
if (hasNew) gridPane.requestFocus();
112+
});
113+
106114
button.setId(String.format("button-%d", index)); // useful for CSS, selecting individually
107115
button.getStyleClass().add("button"); // useful for CSS, selecting all buttons
108116

@@ -148,4 +156,27 @@ public Scene getScene() {
148156
return scene;
149157
}
150158

159+
/**
160+
* This makes it appear that a button is being pressed even if it is not,
161+
* just does that programmatically.
162+
* @param entry The string that signifies the text a button should have.
163+
* @param milliseconds A double signifying how many milliseconds it should appear pressed.
164+
*/
165+
public void simulateClickWithoutEvent(String entry, double milliseconds) {
166+
for (Button button : buttons) {
167+
if (button != null && button.getText().equals(entry)) {
168+
PauseTransition pause = new PauseTransition(Duration.millis(milliseconds));
169+
170+
button.getStyleClass().add("pressed");
171+
172+
pause.setOnFinished(event -> {
173+
button.getStyleClass().remove("pressed");
174+
});
175+
176+
pause.play();
177+
break;
178+
}
179+
}
180+
}
181+
151182
}

src/main/java/dev/sevora/resources/stylesheet.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
-fx-opacity: 0.9;
2727
}
2828

29-
.button:pressed {
29+
.button:pressed, .pressed {
3030
-fx-opacity: 0.7;
3131
}
3232

0 commit comments

Comments
 (0)