diff --git a/build.gradle b/build.gradle index a555d6ea83..dc6c90748d 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,21 @@ 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 { @@ -31,7 +46,7 @@ test { } application { - mainClassName = "duke.Duke" + mainClassName = "duke.Launcher" } shadowJar { diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..1286339296 --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,61 @@ +package duke; + +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 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; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java index 0fdd998789..f6189ea0aa 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,7 +1,5 @@ package duke; -import java.io.IOException; - import duke.exceptions.DukeException; import duke.logic.CommandParser; import duke.logic.commands.Command; @@ -37,33 +35,17 @@ public Duke() { } /** - * This is the method that processes user input and looks out for the exit signal. - */ - public void run() { - ui.showWelcome();; - boolean isExit = false; - while (!isExit) { - try { - String fullCommand = ui.readCommand(); - ui.showLine(); - Command c = CommandParser.parse(fullCommand); - c.execute(tm, ui, storage); - isExit = c.isExit(); - } catch (DukeException e) { - ui.showError(e); - } finally { - ui.showLine(); - } - } - } - - /** - * This is the main method that runs Duke. + * Generates response to command input. * - * @param args Unused. - * @throws IOException On input error. + * @param input String input by user. + * @return Response after command is executed. */ - public static void main(String[] args) throws IOException { - new Duke().run(); + public String getResponse(String input) { + try { + Command c = CommandParser.parse(input); + return c.execute(tm, ui, storage); + } catch (DukeException e) { + return e.getMessage(); + } } } diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..b8c0f9c373 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,9 @@ +package duke; + +import javafx.application.Application; + +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..db593707b5 --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,31 @@ +package duke; + +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 Duke duke = new Duke(); + + @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.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..fb816a3800 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,52 @@ +package 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/User.jpg")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Duke.jpg")); + + @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(); + } +} diff --git a/src/main/java/duke/logic/commands/AddCommand.java b/src/main/java/duke/logic/commands/AddCommand.java index 8dc94378a6..32bb79140b 100644 --- a/src/main/java/duke/logic/commands/AddCommand.java +++ b/src/main/java/duke/logic/commands/AddCommand.java @@ -34,7 +34,7 @@ public AddCommand(String command) { * @throws DukeException If command is not properly formatted. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { if (command.startsWith("deadline")) { if (command.length() <= 9) { throw new DukeException("Description of a deadline cannot be empty!"); @@ -47,7 +47,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException LocalDate dlDate = LocalDate.parse(commandDetails[1]); Deadline deadline = new Deadline(commandDetails[0], dlDate); tm.addTask(deadline); - ui.showDetails("Task added: " + deadline); + return ("Task added: " + deadline); } catch (DateTimeParseException e) { throw new DukeException("Invalid DateTime format. Please use YYYY-MM-DD."); } @@ -63,7 +63,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException LocalDate eventDate = LocalDate.parse(commandDetails[1]); Event event = new Event(commandDetails[0], eventDate); tm.addTask(event); - ui.showDetails("Task added: " + event); + return ("Task added: " + event); } catch (DateTimeParseException e) { throw new DukeException("Invalid DateTime format. Please use YYYY-MM-DD."); } @@ -74,7 +74,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException String commandDetails = command.substring(5); ToDo todo = new ToDo(commandDetails); tm.addTask(todo); - ui.showDetails("Task added: " + todo); + return ("Task added: " + todo); } else { throw new DukeException("Command not recognised!"); } diff --git a/src/main/java/duke/logic/commands/Command.java b/src/main/java/duke/logic/commands/Command.java index 2d86a1cb7b..c3cf3a890c 100644 --- a/src/main/java/duke/logic/commands/Command.java +++ b/src/main/java/duke/logic/commands/Command.java @@ -28,7 +28,7 @@ public Command(String command) { * @param storage Storage class that handles saving and loading from file. * @throws DukeException If command is not properly formatted. */ - public abstract void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException; + public abstract String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException; /** * Returns the exit signal for Duke to terminate. diff --git a/src/main/java/duke/logic/commands/DeleteCommand.java b/src/main/java/duke/logic/commands/DeleteCommand.java index 9208fd3244..d6e39238dd 100644 --- a/src/main/java/duke/logic/commands/DeleteCommand.java +++ b/src/main/java/duke/logic/commands/DeleteCommand.java @@ -29,7 +29,7 @@ public DeleteCommand(String command) { * @throws DukeException If command is not properly formatted. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { String[] commandDetails = command.split(" ", 2); if (commandDetails.length == 1) { throw new DukeException("Delete Index not provided!"); @@ -38,7 +38,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException int delIndex = Integer.parseInt(commandDetails[1]) - 1; Task delTask = tm.getTask(delIndex); tm.deleteTask(delIndex); - ui.showDetails("Task deleted: " + delTask); + return ("Task deleted: " + delTask); } catch (NumberFormatException e) { throw new DukeException("Index is not a number!"); } diff --git a/src/main/java/duke/logic/commands/DoneCommand.java b/src/main/java/duke/logic/commands/DoneCommand.java index b4746fb0ec..3bc238aebe 100644 --- a/src/main/java/duke/logic/commands/DoneCommand.java +++ b/src/main/java/duke/logic/commands/DoneCommand.java @@ -29,7 +29,7 @@ public DoneCommand(String command) { * @throws DukeException If command is not properly formatted. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { String[] commandDetails = command.split(" ", 2); if (commandDetails.length == 1) { throw new DukeException("Done Index not provided!"); @@ -38,7 +38,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException int doneIndex = Integer.parseInt(commandDetails[1]) - 1; Task doneTask = tm.getTask(doneIndex); tm.markTaskDone(doneIndex); - ui.showDetails("Task marked as done: " + doneTask); + return ("Task marked as done: " + doneTask); } catch (NumberFormatException e) { throw new DukeException("Index is not a number!"); } diff --git a/src/main/java/duke/logic/commands/ExitCommand.java b/src/main/java/duke/logic/commands/ExitCommand.java index b5cd082ac1..7540c04f98 100644 --- a/src/main/java/duke/logic/commands/ExitCommand.java +++ b/src/main/java/duke/logic/commands/ExitCommand.java @@ -32,7 +32,7 @@ public ExitCommand(String command) { * @throws DukeException If command is not properly formatted. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { String[] commandDetails = command.split(" ", 2); if (commandDetails.length != 1) { throw new DukeException("Exit command should not include extra parameters!"); @@ -43,7 +43,7 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException } catch (IOException e) { e.printStackTrace(); } - ui.showGoodbye(); + return ui.showGoodbye(); } /** diff --git a/src/main/java/duke/logic/commands/FindCommand.java b/src/main/java/duke/logic/commands/FindCommand.java index 9b9e3adb83..9367169d1c 100644 --- a/src/main/java/duke/logic/commands/FindCommand.java +++ b/src/main/java/duke/logic/commands/FindCommand.java @@ -31,7 +31,7 @@ public FindCommand(String command) { * @throws DukeException If search term is not provided. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { String[] commandDetails = command.split(" ", 2); if (commandDetails.length == 1) { throw new DukeException("Search term not provided!"); @@ -40,11 +40,11 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException ArrayList mathingTasks = tm.findTasks(searchTerm); if (mathingTasks.isEmpty()) { String s = "No matching tasks found!"; - ui.showDetails(s); + return s; } else { - String s = "I found the following matching tasks:"; - ui.showDetails(s); - ui.showTaskList(mathingTasks); + String s = "I found the following matching tasks:\n"; + s = s + ui.buildTaskList(mathingTasks); + return s; } } } diff --git a/src/main/java/duke/logic/commands/ListCommand.java b/src/main/java/duke/logic/commands/ListCommand.java index 46913c1ab0..ce2c366b5a 100644 --- a/src/main/java/duke/logic/commands/ListCommand.java +++ b/src/main/java/duke/logic/commands/ListCommand.java @@ -31,7 +31,7 @@ public ListCommand(String command) { * @throws DukeException If command is not properly formatted. */ @Override - public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { + public String execute(TaskManager tm, Ui ui, Storage storage) throws DukeException { String[] commandDetails = command.split(" ", 2); if (commandDetails.length != 1) { throw new DukeException("List command should not include extra parameters!"); @@ -39,9 +39,9 @@ public void execute(TaskManager tm, Ui ui, Storage storage) throws DukeException ArrayList taskList = tm.getTaskList(); if (taskList.isEmpty()) { String s = "List is empty!"; - ui.showDetails(s); + return s; } else { - ui.showTaskList(taskList); + return ui.buildTaskList(taskList); } } } diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java index 81ae15b537..b4c181e727 100644 --- a/src/main/java/duke/ui/Ui.java +++ b/src/main/java/duke/ui/Ui.java @@ -11,65 +11,33 @@ public class Ui { static final String WELCOME = "Hello. I am Claude! What may I do for you today?"; static final String GOODBYE = "Goodbye! Hope to see you again soon!"; - static final String LINE = "______________________________"; - private Scanner sc; - public Ui() { - this.sc = new Scanner(System.in); - } - - /** - * Reads command input by user. - * - * @return String to be processed by parser. - */ - public String readCommand() { - if (sc.hasNext()) { - return sc.nextLine(); - } else { - return ""; - } - } - - /** - * Prints a line that serves as a divider. - */ - public void showLine() { - System.out.println(LINE); - } + public Ui() {} /** * Prints welcome message. */ - public void showWelcome() { - System.out.println(WELCOME); + public String showWelcome() { + return WELCOME; } /** * Prints goodbye message. */ - public void showGoodbye() { - System.out.println(GOODBYE); - } - - /** - * Prints output from a Command. - * @param s String to be printed. - */ - public void showDetails(String s) { - System.out.println(s); + public String showGoodbye() { + return (GOODBYE); } /** * Prints a given ArrayList of Tasks. * @param taskList ArrayList of Tasks to be printed. */ - public void showTaskList(ArrayList taskList) { + public String buildTaskList(ArrayList taskList) { String s = ""; for (int i = 0; i < taskList.size(); i++) { s = s = s + (i + 1) + ". " + taskList.get(i) + "\n"; } - showDetails(s); + return s; } /** diff --git a/src/main/resources/images/Duke.jpg b/src/main/resources/images/Duke.jpg new file mode 100644 index 0000000000..673c596ef8 Binary files /dev/null and b/src/main/resources/images/Duke.jpg differ diff --git a/src/main/resources/images/User.jpg b/src/main/resources/images/User.jpg new file mode 100644 index 0000000000..db6c4bd2dd Binary files /dev/null and b/src/main/resources/images/User.jpg differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..4ca787e966 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..f945c45829 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,46 @@ + + + + + + + + + + + +