diff --git a/build.gradle b/build.gradle index 0bdc254568..c7703ddcee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'application' + id 'org.openjfx.javafxplugin' version '0.0.10' id 'com.github.johnrengelman.shadow' version '5.1.0' } @@ -11,6 +12,20 @@ repositories { 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 = '11' + 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 { @@ -28,12 +43,7 @@ test { } application { - mainClassName = "duke.Duke" -} - -shadowJar { - archiveBaseName = "duke" - archiveClassifier = null + mainClassName = "duke.Launcher" } run{ diff --git a/savedata.txt b/savedata.txt index 4ba8f571f8..e95a82f579 100644 --- a/savedata.txt +++ b/savedata.txt @@ -2,3 +2,4 @@ [D] | 1 | say bye | 2022-09-01 [E] | 0 | jellyfish catching | 2022-09-02 [T] | 1 | catch jellyfish +[T] | 0 | say DUKE! diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index aef18da262..7b038fabbe 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,52 +1,56 @@ package duke; import duke.commands.Command; +import duke.controllers.MainWindow; import duke.tasks.*; import duke.ui.Ui; import duke.utils.InputParser; import duke.utils.Storage; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; import java.io.File; +import java.io.IOException; +import java.net.URL; import java.util.Scanner; -public class Duke { +public class Duke extends Application { private TaskList taskList; private Storage storage; - private Ui ui; - private InputParser inputParser; - private static final File saveFile = new File("savedata.txt"); - - public static void main(String[] args) { - Duke duke = new Duke(); - duke.start(); - } + private static final File SAVE_FILE = new File("savedata.txt"); + private static final URL MAIN_WINDOW_FXML = Duke.class.getResource("/view/MainWindow.fxml"); - public Duke() { + @Override + public void start(Stage stage) throws Exception { inputParser = new InputParser(); ui = new Ui(); - } - - public void start() { - Scanner sc = new Scanner(System.in); - storage = new Storage(saveFile); - ui.showLogo(); + storage = new Storage(SAVE_FILE); taskList = new TaskList(storage.loadFromFile()); - ui.showWelcome(); - loop(sc); + loadMainWindow(stage); } - public void loop(Scanner sc) { - while (sc.hasNext()) { - try { - String input = sc.nextLine().trim(); - Command cmd = inputParser.parse(input, taskList, storage, ui); - cmd.execute(); - } catch (Exception e) { - e.printStackTrace(); - } + private void loadMainWindow(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MAIN_WINDOW_FXML); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setDuke(this); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); } } + + public String getResponse(String input) { + Command cmd = inputParser.parse(input, taskList, storage, ui); + String response = cmd.execute(); + return response; + } } \ No newline at end of file diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..47eeb98b82 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,11 @@ +package duke; + +import javafx.application.Application; + +public class Launcher { + + public static void main(String[] args) { + Application.launch(Duke.class, args); + } + +} diff --git a/src/main/java/duke/commands/AddTaskCommand.java b/src/main/java/duke/commands/AddTaskCommand.java index 7882db2c5f..dfa63a3f4b 100644 --- a/src/main/java/duke/commands/AddTaskCommand.java +++ b/src/main/java/duke/commands/AddTaskCommand.java @@ -28,17 +28,21 @@ public AddTaskCommand(Storage storage, Ui ui, TaskList tasks, TaskType type, Str } @Override - public boolean execute() { + public String execute() { try { Task newTask = TaskParser.stringToTask(type, taskString); tasks.addTask(newTask); - ui.showAddTaskResponse(newTask, tasks); storage.saveToFile(tasks.getList()); + + String response = String.format( + "Got it. I've added this task:\n %s\nNow you have %d tasks in the list.", + newTask, tasks.getSize()); + return response; } catch (EmptyTaskDescException | EmptyTaskDateException | NoSuchTaskTypeException | UnrecognisedDateException e) { - ui.showError(e); - return false; + + String response = String.format("Oops! %s", e.getMessage()); + return response; } - return true; } } diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java index f593ff8bc2..7137f4a31e 100644 --- a/src/main/java/duke/commands/Command.java +++ b/src/main/java/duke/commands/Command.java @@ -2,10 +2,6 @@ public abstract class Command { - /** - * Executes the command. - * @return True if the command executed successfully and false otherwise. - */ - public abstract boolean execute(); + public abstract String execute(); } diff --git a/src/main/java/duke/commands/DeleteTaskCommand.java b/src/main/java/duke/commands/DeleteTaskCommand.java index b5efad4afc..571a8821fc 100644 --- a/src/main/java/duke/commands/DeleteTaskCommand.java +++ b/src/main/java/duke/commands/DeleteTaskCommand.java @@ -21,18 +21,22 @@ public DeleteTaskCommand(Storage storage, Ui ui, TaskList tasks, String index) { } @Override - public boolean execute() { + public String execute() { try { Task deletedTask = tasks.deleteTask(Integer.parseInt(index) - 1); - ui.showDeleteTaskResponse(deletedTask, tasks); storage.saveToFile(tasks.getList()); - return true; + + String response = String.format( + "Noted. I've removed this task:\n %s\nNow you have %d tasks in the list", + deletedTask, tasks.getSize() + ); + return response; } catch (TaskNotFoundException e) { - ui.showError(e); - return false; + String response = String.format("Oops! %s", e.getMessage()); + return response; } catch (NumberFormatException e) { - ui.showInvalidFormatError(index); - return false; + String response = String.format("Oops! I could not recognise this format: %s", index); + return response; } } } diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java index af139394c2..96c5eafcae 100644 --- a/src/main/java/duke/commands/ExitCommand.java +++ b/src/main/java/duke/commands/ExitCommand.java @@ -11,9 +11,8 @@ public ExitCommand(Ui ui) { } @Override - public boolean execute() { - ui.showBye(); + public String execute() { System.exit(0); - return true; + return ""; } } diff --git a/src/main/java/duke/commands/FindTaskCommand.java b/src/main/java/duke/commands/FindTaskCommand.java index 9fe0c5dfbb..f05a0238c6 100644 --- a/src/main/java/duke/commands/FindTaskCommand.java +++ b/src/main/java/duke/commands/FindTaskCommand.java @@ -20,7 +20,7 @@ public FindTaskCommand(TaskList tasks, Ui ui, String searchTerm) { } @Override - public boolean execute() { + public String execute() { List matchingTasks = tasks.find(searchTerm); StringBuilder sb = new StringBuilder(); @@ -30,8 +30,7 @@ public boolean execute() { sb.append(String.format("%d.%s\n", i, task.toString())); ++i; } - System.out.print(sb.toString()); - return true; + return sb.toString(); } } diff --git a/src/main/java/duke/commands/MarkTaskCommand.java b/src/main/java/duke/commands/MarkTaskCommand.java index a4e8c03c96..7ed2af9ca9 100644 --- a/src/main/java/duke/commands/MarkTaskCommand.java +++ b/src/main/java/duke/commands/MarkTaskCommand.java @@ -22,18 +22,21 @@ public MarkTaskCommand(Storage storage, Ui ui, TaskList tasks, String index) { } @Override - public boolean execute() { + public String execute() { try { Task markedTask = tasks.markTask(Integer.parseInt(index) - 1); - ui.showMarkTaskResponse(markedTask); storage.saveToFile(tasks.getList()); - return true; + + String response = String.format( + "Nice! I've marked this task as done:\n %s", + markedTask); + return response; } catch (TaskNotFoundException e) { - ui.showError(e); - return false; + String response = String.format("Oops! %s", e.getMessage()); + return response; } catch (NumberFormatException e) { - ui.showInvalidFormatError(index); - return false; + String response = String.format("Oops! I could not recognise this format: %s\n", index); + return response; } } diff --git a/src/main/java/duke/commands/PrintTasksCommand.java b/src/main/java/duke/commands/PrintTasksCommand.java index 0775e0f939..000603af70 100644 --- a/src/main/java/duke/commands/PrintTasksCommand.java +++ b/src/main/java/duke/commands/PrintTasksCommand.java @@ -14,8 +14,9 @@ public PrintTasksCommand(Ui ui, TaskList tasks) { } @Override - public boolean execute() { - ui.showTasks(tasks); - return true; + public String execute() { + StringBuilder sb = new StringBuilder(); + sb.append("Here are the tasks in your list:\n").append(tasks.toString()); + return sb.toString(); } } diff --git a/src/main/java/duke/commands/UnmarkTaskCommand.java b/src/main/java/duke/commands/UnmarkTaskCommand.java index 2e804819e0..a1a18a10a1 100644 --- a/src/main/java/duke/commands/UnmarkTaskCommand.java +++ b/src/main/java/duke/commands/UnmarkTaskCommand.java @@ -22,18 +22,21 @@ public UnmarkTaskCommand(Storage storage, Ui ui, TaskList tasks, String index) { } @Override - public boolean execute() { + public String execute() { try { Task unmarkedTask = tasks.unmarkTask(Integer.parseInt(index) - 1); - ui.showUnmarkTaskResponse(unmarkedTask); storage.saveToFile(tasks.getList()); - return true; + + String response = String.format( + "OK, I've marked this task as not done yet:\n %s`", + unmarkedTask); + return response; } catch (TaskNotFoundException e) { - ui.showError(e); - return false; + String response = String.format("Oops! %s", e.getMessage()); + return response; } catch (NumberFormatException e) { - ui.showInvalidFormatError(index); - return false; + String response = String.format("Oops! I could not recognise this format: %s\n", index); + return response; } } diff --git a/src/main/java/duke/commands/UnrecognisedCommand.java b/src/main/java/duke/commands/UnrecognisedCommand.java index ee68de30bd..69363daeed 100644 --- a/src/main/java/duke/commands/UnrecognisedCommand.java +++ b/src/main/java/duke/commands/UnrecognisedCommand.java @@ -11,8 +11,8 @@ public UnrecognisedCommand(Ui ui) { } @Override - public boolean execute() { - ui.showCommandUnknown(); - return true; + public String execute() { + String response = "Oops! I'm sorry, but I don't know what that means."; + return response; } } diff --git a/src/main/java/duke/controllers/DialogBox.java b/src/main/java/duke/controllers/DialogBox.java new file mode 100644 index 0000000000..39d144caa7 --- /dev/null +++ b/src/main/java/duke/controllers/DialogBox.java @@ -0,0 +1,69 @@ +package duke.controllers; + +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.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; + +/** + * 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); + displayPicture.setClip(new Circle(50, 50, 45)); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.setStyle("-fx-background-color: azure;"); + return db; + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.setStyle("-fx-background-color: blanchedalmond;"); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/duke/controllers/MainWindow.java b/src/main/java/duke/controllers/MainWindow.java new file mode 100644 index 0000000000..805ef03b09 --- /dev/null +++ b/src/main/java/duke/controllers/MainWindow.java @@ -0,0 +1,52 @@ +package duke.controllers; + +import duke.Duke; +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 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 Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private 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(); + } +} \ No newline at end of file diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..806b86f251 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..8c06d040a2 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,26 @@ + + + + + + + + + + + + +