Skip to content

Commit

Permalink
Level-10
Browse files Browse the repository at this point in the history
Add GUI to Duke implemented with JavaFX.
Removed the CLI UI from Duke. Users will now interact through the GUI exclusively.
  • Loading branch information
JonLamy committed Sep 2, 2022
1 parent 1ae36c6 commit ee9aad0
Show file tree
Hide file tree
Showing 23 changed files with 339 additions and 225 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ bin/
text-ui-test/EXPECTED-UNIX.TXT

src/main/java/ip/taskData.txt
data/

META-INF/
26 changes: 23 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,41 @@ plugins {
id 'application'
id 'com.github.johnrengelman.shadow' version '5.1.0'
id 'checkstyle'
id "org.openjfx.javafxplugin" version "0.0.13"
}

checkstyle {
toolVersion = '10.2'
}


repositories {
mavenCentral()
}

javafx {
version = "18"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}

dependencies {
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
}
String javaFxVersion = '18'

implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
}

test {
useJUnitPlatform()
Expand All @@ -34,14 +54,14 @@ test {
}

application {
mainClassName = "seedu.duke.Duke"
mainClassName = "ip.Duke"
}

shadowJar {
archiveBaseName = "duke"
archiveClassifier = null
}

run{
run {
standardInput = System.in
}
61 changes: 61 additions & 0 deletions src/main/java/ip/DialogBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ip;

import java.io.IOException;
import java.util.Collections;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;

/**
* An example of a custom control using FXML.
* This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
* containing text from the speaker.
*/
public class DialogBox extends HBox {
@FXML
private Label dialog;
@FXML
private ImageView displayPicture;

private DialogBox(String text, Image img) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}

dialog.setText(text);
displayPicture.setImage(img);
}

/**
* Flips the dialog box such that the ImageView is on the left and text on the right.
*/
private void flip() {
ObservableList<Node> tmp = FXCollections.observableArrayList(this.getChildren());
Collections.reverse(tmp);
getChildren().setAll(tmp);
setAlignment(Pos.TOP_LEFT);
}

public static DialogBox getUserDialog(String text, Image img) {
return new DialogBox(text, img);
}

public static DialogBox getDukeDialog(String text, Image img) {
var db = new DialogBox(text, img);
db.flip();
return db;
}
}
79 changes: 26 additions & 53 deletions src/main/java/ip/Duke.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import java.io.IOException;

import ip.command.ByeCommand;
import ip.command.Command;
import ip.command.DukeCommand;
import ip.exception.DukeException;
import ip.exception.InvalidCommand;
import ip.utility.Parser;
import ip.utility.Storage;
import ip.utility.TaskList;


/**
Expand All @@ -17,67 +18,39 @@
* @author Jonathan Lam
*/
public class Duke {
/** Interacts with the user, take input and give output */
private static final Ui ui = new Ui();
/** Extract commands given to the ui */
private static final Parser parser = new Parser();
private static Parser parser;
/** Encapsulation of tasks */
private static TaskList taskList = new TaskList();
private static TaskList taskList;
/** File that task data is read from and written to */
private static final Storage storage = new Storage("src/main/java/ip/taskData.txt");
/** Flag to determine if the task data file should be wiped after program end */
private static boolean toWipe = false;
private static Storage storage;

/**
* Duke's main method.
* Constructor for Duke.
*
* @param args Arguments passed to main when run.
* @param path Path to load the task data to.
*/
public static void main(String[] args) {
// Set Duke to wipe stored task data on termination.
if (System.getProperty("user.dir").endsWith("text-ui-test")) {
toWipe = true;
}
// Load task list from default storage location.
public Duke(String path) {
storage = new Storage(path);
parser = new Parser();
try {
taskList = storage.load();
// Try to find task list from fallback location.
} catch (IOException e) {
ui.say("Error in loading task data file. Searching for file in fallback location.");
try {
taskList = storage.load("taskList.txt");
} catch (IOException ee) {
ui.say(ee.toString());
}
}
ui.divider();
while (true) {
try {
parser.load(ui.getNextLine());
Command command = parser.getCommand();
if (command instanceof ByeCommand) {
ui.sayBye();
break;
} else {
ui.divider();
try {
command.execute(taskList);
storage.write(taskList);
} catch (DukeException e) {
System.out.println(e);
}
parser.clear();
ui.divider();
}
} catch (InvalidCommand e) {
System.out.println("You have entered an invalid command.");
ui.say(e.toString());
}
}
if (toWipe) {
storage.wipe();
ui.say("Storage wiped.");
System.out.println("Error in loading file from specified path.");
System.out.println("Duke will not save any task data this run.");
taskList = new TaskList();
}
}

public String getResponse(String input) {
parser.load(input);
try {
DukeCommand command = parser.getCommand();
String executionReply = command.execute(taskList);
storage.write(taskList);
return executionReply;
} catch (DukeException e) {
return e.toString();
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/ip/Launcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ip;

import javafx.application.Application;

/**
* A launcher class to workaround classpath issues.
*/
public class Launcher {
public static void main(String[] args) {
Application.launch(Main.class, args);
}
}
35 changes: 35 additions & 0 deletions src/main/java/ip/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ip;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

/**
* A GUI for Duke using FXML.
*/
public class Main extends Application {

private final Duke duke = new Duke("data/taskData.txt");

public static void main(String[] args) {

}

@Override
public void start(Stage stage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
AnchorPane ap = fxmlLoader.load();
Scene scene = new Scene(ap);
stage.setScene(scene);
fxmlLoader.<MainWindow>getController().setDuke(duke);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
51 changes: 51 additions & 0 deletions src/main/java/ip/MainWindow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ip;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
/**
* Controller for ip.MainWindow. Provides the layout for the other controls.
*/
public class MainWindow extends AnchorPane {
@FXML
private ScrollPane scrollPane;
@FXML
private VBox dialogContainer;
@FXML
private TextField userInput;
@FXML
private Button sendButton;

private Duke duke;

private final Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
private final Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));

@FXML
public void initialize() {
scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
}

public void setDuke(Duke d) {
duke = d;
}

/**
* Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
* the dialog container. Clears the user input after processing.
*/
@FXML
private void handleUserInput() {
String input = userInput.getText();
String response = duke.getResponse(input);
dialogContainer.getChildren().addAll(
DialogBox.getUserDialog(input, userImage),
DialogBox.getDukeDialog(response, dukeImage)
);
userInput.clear();
}
}
59 changes: 0 additions & 59 deletions src/main/java/ip/Ui.java

This file was deleted.

Loading

0 comments on commit ee9aad0

Please sign in to comment.