diff --git a/build.gradle b/build.gradle index 90b465efb8..f6ba67c23e 100644 --- a/build.gradle +++ b/build.gradle @@ -12,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 { diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..2c9a9745c5 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Duke + 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 9c69781231..d14945b520 100644 --- a/src/main/java/duke/Duke.java +++ b/src/main/java/duke/Duke.java @@ -1,6 +1,25 @@ package duke; +import duke.command.Command; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.NoSuchElementException; +import java.util.Scanner; + public class Duke { + private ByteArrayOutputStream GUIOutput; + private ByteArrayOutputStream GUIError; + private Storage storageVar; + private TaskListHandler handlerVar; + private static final PrintStream originalOutput = System.out; + private static final PrintStream originalError = System.err; + + public Duke() { + storageVar = new Storage("./data"); + handlerVar = new TaskListHandler(storageVar.getListFromFile()); + } + public static void main(String[] args) { initialize(); } @@ -18,4 +37,37 @@ public static void initialize() { Ui userInterface = new Ui(handler, storage); userInterface.run(); } + + /** + * You should have your own function to generate a response to user input. + * Replace this stub with your completed method. + */ + String getResponse(String input) { + setGUIStreams(); + try { + Command c = Parser.parse(input, handlerVar); + c.execute(handlerVar, storageVar); + System.out.println(); + } catch (NoSuchElementException e1) { + System.out.println(e1.getMessage()); + } catch (DukeException e) { + System.out.println(e.getMessage()); + DukeException.tryAgain(); + } + resetGUIStreams(); + return GUIOutput.toString(); + } + + public void setGUIStreams() { + GUIOutput = new ByteArrayOutputStream(); + GUIError = new ByteArrayOutputStream(); + System.setOut(new PrintStream(GUIOutput)); + System.setErr(new PrintStream(GUIError)); + } + + public void resetGUIStreams() { + System.setOut(originalOutput); + System.setErr(originalError); + } + } diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..5483e63637 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +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); + } +} \ No newline at end of file diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..d5a703bfbd --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,32 @@ +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 final 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); + fxmlLoader.getController().showGreeting(); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..c2999a37e1 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,67 @@ +package duke; + +import javafx.animation.PauseTransition; +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; +import javafx.stage.Stage; +import javafx.util.Duration; + +/** + * 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.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Duke.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(); + if (input.equals("bye")) { + Stage stage = (Stage) scrollPane.getScene().getWindow(); + PauseTransition delay = new PauseTransition(Duration.seconds(2)); + delay.setOnFinished( event -> stage.close() ); + delay.play(); + } + String response = duke.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } + + public void showGreeting() { + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(Ui.getGreeting(), dukeImage) + ); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java index 2cbe9add5f..3c93c38c94 100644 --- a/src/main/java/duke/Storage.java +++ b/src/main/java/duke/Storage.java @@ -53,7 +53,7 @@ public void run() { checkIfSaveFileExists(); this.listFromFile = loadListFromFile(); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); this.listFromFile = resetSaveFile(); } } @@ -154,7 +154,7 @@ public ArrayList resetSaveFile() { this.saveToFile(newList); Ui.greet(); } catch (DukeException e1) { - e1.printStackTrace(System.out); + System.out.println(e1); } return newList; } else { diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java index 8436bf7814..f4e84c6d26 100644 --- a/src/main/java/duke/Ui.java +++ b/src/main/java/duke/Ui.java @@ -1,5 +1,7 @@ package duke; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.util.NoSuchElementException; import java.util.Scanner; @@ -14,6 +16,7 @@ public class Ui { protected static boolean isRunning = true; protected final TaskListHandler handler; protected final Storage storage; + protected static String logo; /** * Initializes the Ui with the task list and storage. @@ -50,7 +53,7 @@ public void run() { // Encounter end of file, terminate isRunning = false; } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } @@ -60,16 +63,14 @@ public void run() { * Draws the top border of the chatbot's response. */ public static void drawTopBorder() { - System.out.println(" ______________________________________________________________ "); - System.out.println("* *"); + System.out.println("___________________________________"); } /** * Draws the bottom border of the chatbot's response. */ public static void drawBottomBorder() { - System.out.println(); - System.out.println("*______________________________________________________________*"); + System.out.println("___________________________________"); } @@ -118,28 +119,31 @@ public static void printSuccess(String operation, Task currentTask, int listSize */ public static void greet() { drawTopBorder(); - String logo = "\n" - + " ___ ___ \n" - + " ( ) ( ) \n" - + " .-.| | ___ ___ | | ___ .--. \n" - + " / \\ | ( )( ) | | ( ) / \\ \n" - + "| .-. | | | | | | | ' / | .-. ; \n" - + "| | | | | | | | | |,' / | | | | \n" - + "| | | | | | | | | . '. | |/ | \n" - + "| | | | | | | | | | `. \\ | ' _.' \n" - + "| ' | | | | ; ' | | \\ \\ | .'.-. \n" - + "' `-' / ' `-' / | | \\ . ' `-' / \n" - + " `.__,' '.__.' (___ ) (___) `.__.' \n" - + " \n" - + " \n"; - System.out.println(logo); - String greeting = "Hey! I'm Duke the chatbot!"; - String doForYou = "What can I do for you?"; - indent(1); - System.out.println(greeting); - indent(1); - System.out.println(doForYou); + System.out.println(getGreeting()); drawBottomBorder(); } + public static String getGreeting() { + setLogo(); + String greeting = " Hey! I'm Duke the chatbot!"; + String doForYou = " What can I do for you?"; + return logo + "\n" + greeting + "\n" + doForYou; + } + + public static void setLogo() { + logo = "\n" + + " ___ ___ \n" + + " ( ) ( ) \n" + + " .-.| | ___ ___ | | ___ .--. \n" + + " / \\ | ( )( ) | | ( ) / \\ \n" + + "| .-. | | | | | | | ' / | .-. ; \n" + + "| | | | | | | | | |,' / | | | | \n" + + "| | | | | | | | | . '. | |/ | \n" + + "| | | | | | | | | | `. \\ | ' _.' \n" + + "| ' | | | | ; ' | | \\ \\ | .'.-. \n" + + "' `-' / ' `-' / | | \\ . ' `-' / \n" + + " `.__,' '.__.' (___ ) (___) `.__.' \n" + + " \n" + + " \n"; + } } diff --git a/src/main/java/duke/command/AddAbstractTaskCommand.java b/src/main/java/duke/command/AddAbstractTaskCommand.java index 7ddc65b591..3cc9662f74 100644 --- a/src/main/java/duke/command/AddAbstractTaskCommand.java +++ b/src/main/java/duke/command/AddAbstractTaskCommand.java @@ -34,7 +34,7 @@ public void execute(TaskListHandler handler, Storage storage) { Ui.printSuccess("add", newTask, taskList.size()); storage.saveToFile(taskList); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } diff --git a/src/main/java/duke/command/ByeCommand.java b/src/main/java/duke/command/ByeCommand.java index feb2893c05..2253a9fc80 100644 --- a/src/main/java/duke/command/ByeCommand.java +++ b/src/main/java/duke/command/ByeCommand.java @@ -1,5 +1,6 @@ package duke.command; +import duke.Duke; import duke.Storage; import duke.TaskListHandler; import duke.Ui; diff --git a/src/main/java/duke/command/ClearCommand.java b/src/main/java/duke/command/ClearCommand.java index eff404ac9b..f035c1327b 100644 --- a/src/main/java/duke/command/ClearCommand.java +++ b/src/main/java/duke/command/ClearCommand.java @@ -25,7 +25,7 @@ public void execute(TaskListHandler handler, Storage storage) { } storage.saveToFile(tasks); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java index 53ec68edef..1ef56ee49b 100644 --- a/src/main/java/duke/command/DeleteCommand.java +++ b/src/main/java/duke/command/DeleteCommand.java @@ -33,7 +33,7 @@ public void execute(TaskListHandler handler, Storage storage) { Ui.printSuccess("delete", task, tasks.size()); storage.saveToFile(tasks); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } diff --git a/src/main/java/duke/command/DoneCommand.java b/src/main/java/duke/command/DoneCommand.java index 648c10fc14..f6be181228 100644 --- a/src/main/java/duke/command/DoneCommand.java +++ b/src/main/java/duke/command/DoneCommand.java @@ -33,7 +33,7 @@ public void execute(TaskListHandler handler, Storage storage) { Ui.printSuccess("done", task, tasks.size()); storage.saveToFile(tasks); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } diff --git a/src/main/java/duke/command/InvalidCommand.java b/src/main/java/duke/command/InvalidCommand.java index 4dda928755..2238c2ae94 100644 --- a/src/main/java/duke/command/InvalidCommand.java +++ b/src/main/java/duke/command/InvalidCommand.java @@ -27,7 +27,7 @@ public void execute(TaskListHandler handler, Storage storage) { throw new DukeException("\u2639 Oops, I'm sorry but I don't know what " + '"' + invalidInput + '"' + " means :-("); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.println(e.getMessage()); DukeException.tryAgain(); } } diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java index fc9002d7aa..dccf796c6a 100644 --- a/src/main/java/duke/command/ListCommand.java +++ b/src/main/java/duke/command/ListCommand.java @@ -21,7 +21,7 @@ public void execute(TaskListHandler handler, Storage storage) { try { handler.printList(); } catch (DukeException e) { - e.printStackTrace(System.out); + System.out.print(e.getMessage()); } } } diff --git a/src/main/resources/images/Duke.png b/src/main/resources/images/Duke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/Duke.png differ diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/User.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..eb919c22dd --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..c7fc4b0f98 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +