diff --git a/.gitignore b/.gitignore index 823d175eb670..b034a08a64d9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ lib/* *.log.* *.csv config.json -src/test/data/sandbox/ +src/test/data/sandbox preferences.json .DS_Store ./screenshot*.png diff --git a/Collate-TUI.jar b/Collate-TUI.jar new file mode 100644 index 000000000000..2b9e032106dd Binary files /dev/null and b/Collate-TUI.jar differ diff --git a/README.adoc b/README.adoc index 03eff3a4d191..050e68551593 100644 --- a/README.adoc +++ b/README.adoc @@ -1,33 +1,29 @@ -= Address Book (Level 4) += TeachConnect ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] +https://travis-ci.org/CS2103JAN2018-W14-B1/main[image:https://travis-ci.org/CS2103JAN2018-W14-B1/main.svg?branch=master[Build Status]] https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] +https://coveralls.io/github/CS2103JAN2018-W14-B1/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-W14-B1/main/badge.svg?branch=master[Coverage Status]] https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] ifdef::env-github[] -image::docs/images/Ui.png[width="600"] +image::docs/images/StartupUI.jpg[width="600"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::images/StartupUI.jpg[width="600"] endif::[] +__TeachConnect - The only management application you'll ever need +__ -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* TeachConnect is a contact and schedule management application designed for teachers and educational professionals. +* TeachConnect provides an intuitive and efficient solution for managing students and parents' contact details, recording appointments, setting up reminders, and many more. +* The majority of user interaction is through a CLI (Command Line Interface), however a GUI is provided with many optimization options. == Site Map * <> * <> -* <> * <> * <> diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..a03270bf7ad9 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ jacocoTestReport { dependencies { String testFxVersion = '4.0.7-alpha' + compile fileTree(dir: 'libs', include: '*.jar') compile group: 'org.fxmisc.easybind', name: 'easybind', version: '1.0.3' compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' diff --git a/collated/functional/Sisyphus25-reused.md b/collated/functional/Sisyphus25-reused.md new file mode 100644 index 000000000000..d514fa88a217 --- /dev/null +++ b/collated/functional/Sisyphus25-reused.md @@ -0,0 +1,192 @@ +# Sisyphus25-reused +###### /resources/view/TagColour.css +``` css +Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + */ +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: red; +} + +#tags .yellow { + -fx-background-color: yellow; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: white; + -fx-background-color: blue; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: orange; +} + +#tags .brown { + -fx-text-fill: white; + -fx-background-color: brown; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: green; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: pink; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .grey { + -fx-text-fill: black; + -fx-background-color: grey; +} +``` +###### /java/seedu/address/ui/CalendarPanel.java +``` java + //Reused from https://github.com/CS2103AUG2017-T17-B2/main with modifications + private void setTime() { + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + } + + @Subscribe + private void handleToggleCalendarViewEvent(ToggleCalendarViewEvent event) { + Character c = event.viewMode; + Platform.runLater(() -> toggleView(c)); + } + + public CalendarView getRoot() { + return this.calendarView; + } + + /** + * Remove clutter from interface + */ + private void disableViews() { + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.setShowPageSwitcher(false); + calendarView.setShowSourceTrayButton(false); + calendarView.setShowPageToolBarControls(false); + calendarView.setShowToolBar(false); + calendarView.setShowSourceTray(false); + + calendarView.showDayPage(); + } + + /** + * Changes calendar view accordingly + */ + private void toggleView(Character c) { + switch(c) { + case ('d'): + calendarView.showDayPage(); + return; + case ('w'): + calendarView.showWeekPage(); + return; + case ('m'): + calendarView.showMonthPage(); + return; + default: + //should not reach here + assert (false); + } + } +``` +###### /java/seedu/address/ui/TaskListPanel.java +``` java +//Reuse from PersonListPanel class with modification +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView taskListView; + + public TaskListPanel(ObservableList taskList) { + super(FXML); + setConnections(taskList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList taskList) { + ObservableList mappedList = EasyBind.map(taskList, (task) -> + new TaskCard(task, taskList.indexOf(task) + 1)); + taskListView.setItems(mappedList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code TaskCard}. + */ + class TaskListViewCell extends ListCell { + + @Override + protected void updateItem(TaskCard task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(task.getRoot()); + } + } + } + +} +``` +###### /java/seedu/address/ui/PersonCard.java +``` java + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * Returns the color style for {@code tagName}'s label. + */ + private void initTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(tag.tagColorStyle); + tags.getChildren().add(tagLabel); + }); + } + +``` +###### /java/seedu/address/model/tag/Tag.java +``` java + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + private static final String[] TAG_COLOR_STYLES = {"teal", "red", "yellow", "blue", "orange", "brown", + "green", "pink", "black", "grey"}; +``` +###### /java/seedu/address/model/tag/Tag.java +``` java + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * Returns a color style for {@code tagName} + */ + private String getTagColorStyle(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between different runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + +} +``` diff --git a/collated/functional/Sisyphus25.md b/collated/functional/Sisyphus25.md new file mode 100644 index 000000000000..0b37e9a7ed5f --- /dev/null +++ b/collated/functional/Sisyphus25.md @@ -0,0 +1,2496 @@ +# Sisyphus25 +###### /resources/view/LightTheme.css +``` css + */ +.background { + -fx-background-color: #ffffff; + background-color: #ffffff; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: brown; + -fx-font-family: "Franklin Gothic Medium"; + -fx-font-weight: bold; + -fx-font-size: 36px +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent transparent transparent tomato; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffffff, 20%); + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #ffd0d0; +} + +.list-cell:filled:odd { + -fx-background-color: #ffd0d0; +} + +.list-cell:filled:selected { + -fx-background-color: #ffc2c2; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #ffc2c2; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: brown; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #ffd0d0; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #ffd0d0; +} + +.anchor-pane { + -fx-background-color: derive(#ffffff, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffd0d0, 20%); + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: brown; +} + +.result-display .label { + -fx-text-fill: white !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: brown; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffd0d0, 30%); + -fx-border-color: derive(#ffd0d0, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: brown; +} + +.grid-pane { + -fx-background-color: derive(#ffd0d0, 30%); + -fx-border-color: derive(#ffd0d0, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffd0d0, 30%); +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: #ffffff; + background-color: #ffffff; +} + +.calendar-panel .content { + -fx-border-color: transparent transparent tomato transparent; +} + +.calendar-panel .header { + -fx-border-color: tomato transparent transparent transparent; +} + +.context-menu { + -fx-background-color: derive(#ffd0d0, 50%); +} + +.context-menu .label { + -fx-text-fill: brown; +} + +.menu-bar { + -fx-background-color: derive(#ffd0d0, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: brown; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: brown; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffd0d0, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: brown; + -fx-text-fill: brown; +} + +.scroll-bar { + -fx-background-color: derive(#ffd0d0, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(tomato, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #ffd0d0 transparent #ffd0d0; + -fx-background-insets: 0; + -fx-border-color: #ffd0d0 #ffd0d0 brown #ffd0d0 ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: brown; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, brown, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffffff, transparent, #ffffff; + -fx-background-radius: 0; + -fx-border-color: #ffd0d0; +} +``` +###### /resources/view/DogeTheme.css +``` css + */ + +.root { + -fx-background-image: url("../images/doge.jpg"); + -fx-background-repeat: repeat; + -fx-background-position: center center; + -fx-effect: dropshadow(three-pass-box, black, 30, 0.5, 0, 0); +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: white; + -fx-font-family: "Franklin Gothic Heavy"; + -fx-font-size: 40px; +} + +.app-title .text { + -fx-stroke: black; + -fx-stroke-width: 1px; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: transparent; + -fx-background-color: transparent; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35px; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: transparent; + -fx-border-color: transparent transparent transparent #635615; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: transparent; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: transparent; + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:odd { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:selected { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #c1b05b; + -fx-border-width: 1; + +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #efdc7f; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #efdc7f; +} + +.anchor-pane { + -fx-background-color: transparent; +} + +.pane-with-border { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: transparent; + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: transparent !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: transparent; +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: transparent; + background-color: transparent; +} + +.context-menu { + -fx-background-color: derive(#efdc7f, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: transparent; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: transparent; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#efdc7f, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#efdc7f, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(#635615, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: #efdc7f; + -fx-background-insets: 0; + -fx-border-color: #efdc7f #efdc7f black #efdc7f ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay { + -fx-background-color: transparent; +} + +#resultDisplay .scroll-pane .viewport{ + -fx-background-color: transparent; +} + +#resultDisplay .content { + -fx-background-color: #efdc7f; + -fx-background-radius: 0; +} +``` +###### /resources/view/GalaxyTheme.css +``` css + */ + +.root { + -fx-background-image: url("../images/galaxy.jpg"); + -fx-background-size: cover; + -fx-background-position: center center; + -fx-effect: dropshadow(three-pass-box, black, 30, 0.5, 0, 0); +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: white; + -fx-font-family: "Franklin Gothic Heavy"; + -fx-font-size: 40px; +} + +.app-title .text { + -fx-stroke: black; + -fx-stroke-width: 1px; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: transparent; + -fx-background-color: transparent; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35px; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: transparent; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: transparent; + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #edf2f9; +} + +.list-cell:filled:odd { + -fx-background-color: #edf2f9; +} + +.list-cell:filled:selected { + -fx-background-color: #c0c5f9; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #edf2f9; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #edf2f9; +} + +.anchor-pane { + -fx-background-color: transparent; +} + +.pane-with-border { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: transparent; + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: transparent !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI"; + -fx-text-fill: #edf2f9; +} + +.status-bar-with-border { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: transparent; +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: transparent; + background-color: transparent; +} + +.context-menu { + -fx-background-color: derive(#070f60, 50%); +} + +.context-menu .label { + -fx-text-fill: #edf2f9; +} + +.menu-bar { + -fx-background-color: transparent; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI"; + -fx-text-fill: #edf2f9; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: transparent; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#edf2f9, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#edf2f9, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(#070f60, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: #edf2f9; + -fx-background-insets: 0; + -fx-border-color: #edf2f9 #edf2f9 black #edf2f9 ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay { + -fx-background-color: transparent; +} + +#resultDisplay .scroll-pane .viewport{ + -fx-background-color: transparent; +} + +#resultDisplay .content { + -fx-background-color: #edf2f9; + -fx-background-radius: 0; +} +``` +###### /java/seedu/address/ui/TaskCard.java +``` java +/** + * An UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + private static final String DATE_FORMAT = "EEE, MMMMM dd, HH:mm a"; + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + private static final Calendar CALENDAR = Calendar.getInstance(); + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label time; + @FXML + private FlowPane tags; + + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + title.setText(task.getTitle().value); + time.setText("Finish before: " + DATE_FORMATTER.format(task.getTime().value.getTime())); + if (task.getTime().isExpired()) { + addExpiredTag(); + } + } + + /** + * Add an expired tag to the Task Card + */ + private void addExpiredTag() { + Label expiredTask = new Label("Expired"); + expiredTask.getStyleClass().add("red"); + tags.getChildren().add(expiredTask); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} +``` +###### /java/seedu/address/ui/CalendarPanel.java +``` java +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + private static final String FXML = "CalendarPanel.fxml"; + + @FXML + private CalendarView calendarView; + private Calendar calendar; + + private ObservableList appointmentList; + + public CalendarPanel(ObservableList appointmentObservableList) { + super(FXML); + this.appointmentList = appointmentObservableList; + + calendarView = new CalendarView(); + CalendarSource calendarSource = new CalendarSource("My Calendar"); + calendar = new Calendar("Appointments"); + + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + + calendarView.getCalendarSources().add(calendarSource); + calendarSource.getCalendars().add(calendar); + calendar.setStyle(Calendar.Style.getStyle(0)); + calendar.setLookAheadDuration(Duration.ofDays(365)); + + updateCalendar(); + disableViews(); + registerAsAnEventHandler(this); + } + + /** + * Clear the entry list in the CalendarFX calendar and + * populate it with appointment in the updated appointmentList + */ + private void updateCalendar() { + calendar.clear(); + ArrayList entries = getEntries(); + for (Entry entry : entries) { + calendar.addEntry(entry); + } + } + + private ArrayList getEntries() { + ArrayList entries = new ArrayList<>(); + for (Appointment appointment : appointmentList) { + entries.add(getEntry(appointment)); + } + return entries; + } + + private Entry getEntry(Appointment appointment) { + LocalDateTime ldtstart = LocalDateTime.ofInstant( + appointment.getTime().value.getTime().toInstant(), ZoneId.systemDefault()); + LocalDateTime ldtend = LocalDateTime.ofInstant( + appointment.getEndTime().value.getTime().toInstant(), ZoneId.systemDefault()); + String description = appointment.getTitle().value; + return new Entry(description, new Interval(ldtstart, ldtend)); + } + + @Subscribe + private void handleAppointmentListChangedEvent(AppointmentListChangedEvent event) { + appointmentList = event.appointmentList; + Platform.runLater( + this::updateCalendar + ); + } + + +``` +###### /java/seedu/address/ui/ThemeList.java +``` java +/** + * Provide list of themes and respective URL to their CSS stylesheet + */ +public class ThemeList { + private HashMap themeList; + + public ThemeList() { + themeList = new HashMap<>(); + themeList.put("dark", "view/DarkTheme.css"); + themeList.put("light", "view/LightTheme.css"); + themeList.put("doge", "view/DogeTheme.css"); + themeList.put("galaxy", "view/GalaxyTheme.css"); + } + + public String getThemeStyleSheet(String theme) { + if (!themeList.containsKey(theme)) { + return themeList.get("light"); + } + return themeList.get(theme); + } +} +``` +###### /java/seedu/address/ui/AppointmentCard.java +``` java +/** + * An UI component that displays information of a {@code Appointment}. + */ +public class AppointmentCard extends UiPart { + + private static final String FXML = "AppointmentListCard.fxml"; + private static final String DATE_FORMAT = "EEE, MMMMM dd, HH:mm a"; + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + public final Appointment appointment; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label time; + @FXML + private Label endTime; + @FXML + private Label personToMeet; + + public AppointmentCard(Appointment appointment, int displayedIndex) { + super(FXML); + this.appointment = appointment; + id.setText(displayedIndex + ". "); + title.setText(appointment.getTitle().value); + time.setText("From: " + DATE_FORMATTER.format(appointment.getTime().value.getTime())); + endTime.setText("To: " + DATE_FORMATTER.format(appointment.getEndTime().value.getTime())); + if (appointment.getPersonToMeet() != null) { + personToMeet.setText("With " + appointment.getPersonToMeet().getName()); + } else { + personToMeet.setText(""); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AppointmentCard)) { + return false; + } + + // state check + AppointmentCard card = (AppointmentCard) other; + return id.getText().equals(card.id.getText()) + && appointment.equals(card.appointment); + } +} +``` +###### /java/seedu/address/ui/AppointmentListPanel.java +``` java +/** + * Panel containing the list of appointments. + */ +public class AppointmentListPanel extends UiPart { + private static final String FXML = "AppointmentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(AppointmentListPanel.class); + + @FXML + private ListView appointmentListView; + + public AppointmentListPanel(ObservableList appointmentList) { + super(FXML); + setConnections(appointmentList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList appointmentList) { + ObservableList mappedList = EasyBind.map(appointmentList, (appointment) -> + new AppointmentCard(appointment, appointmentList.indexOf(appointment) + 1)); + appointmentListView.setItems(mappedList); + appointmentListView.setCellFactory(listView -> new AppointmentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code AppointmentCard}. + */ + class AppointmentListViewCell extends ListCell { + + @Override + protected void updateItem(AppointmentCard appointment, boolean empty) { + super.updateItem(appointment, empty); + + if (empty || appointment == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(appointment.getRoot()); + } + } + } + +} +``` +###### /java/seedu/address/ui/MainWindow.java +``` java + private void setTheme() { + setTheme(DEFAULT_THEME); + } + + private void setTheme(String theme) { + primaryStage.getScene().getStylesheets().add(EXTENSIONS_STYLESHEET); + primaryStage.getScene().getStylesheets().add(TAG_COLOUR_STYLESHEET); + primaryStage.getScene().getStylesheets().add(THEME_LIST.getThemeStyleSheet(theme)); + } + + @Subscribe + private void handleThemeChangeEvent(ThemeChangeEvent event) { + theme = event.theme; + Platform.runLater( + this::changeTheme + ); + } + + private void changeTheme() { + primaryStage.getScene().getStylesheets().clear(); + setTheme(theme); + } +``` +###### /java/seedu/address/commons/events/ui/ToggleListEvent.java +``` java + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to toggle List + */ +public class ToggleListEvent extends BaseEvent { + public final String list; + + public ToggleListEvent(String list) { + this.list = list; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### /java/seedu/address/commons/events/ui/ToggleCalendarViewEvent.java +``` java +/** + * Indicates a request to toggle Calendar view mode + */ +public class ToggleCalendarViewEvent extends BaseEvent { + public final Character viewMode; + + public ToggleCalendarViewEvent(Character viewMode) { + this.viewMode = viewMode; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### /java/seedu/address/commons/events/ui/ThemeChangeEvent.java +``` java +/** + * Indicates a request to change them + */ +public class ThemeChangeEvent extends BaseEvent { + public final String theme; + + public ThemeChangeEvent(String theme) { + this.theme = theme; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### /java/seedu/address/commons/events/model/AppointmentListChangedEvent.java +``` java +/** + * Indicates the appointment list has changed + */ +public class AppointmentListChangedEvent extends BaseEvent { + public final ObservableList appointmentList; + + public AppointmentListChangedEvent(ObservableList appointmentList) { + this.appointmentList = appointmentList; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### /java/seedu/address/logic/parser/RemoveCommandParser.java +``` java +/** + * Parses input arguments and creates a new RemoveCommand object + */ +public class RemoveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommand + * and returns an RemoveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] parameterGetterArray = trimmedArgs.split(" "); + if (trimmedArgs.isEmpty() || parameterGetterArray.length != 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + try { + if (!isValidEventType(parameterGetterArray[0])) { + throw new IllegalValueException("Invalid event type"); + } + Index index = ParserUtil.parseIndex(parameterGetterArray[1]); + return new RemoveCommand(index, parameterGetterArray[0]); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + } + + private boolean isValidEventType(String type) { + return type.equals("appointment") || type.equals("task"); + } +} + + + +``` +###### /java/seedu/address/logic/parser/ToggleCalendarViewParser.java +``` java +/** + * Parser for ToggleCalendarViewCommand + */ +public class ToggleCalendarViewParser implements Parser { + /** + * Parses the given {@code viewMode} of arguments in the context of the ToggleCalendarViewParser + * and returns an ToggleCalendarViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ToggleCalendarViewCommand parse(String args) throws ParseException { + String viewMode = args.trim(); + if (viewMode.isEmpty() || !isValidViewMode(viewMode)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ToggleCalendarViewCommand.MESSAGE_USAGE)); + } + return new ToggleCalendarViewCommand(viewMode.charAt(0)); + } + + /** + * + * @param str + * @return whether if the string is a valid view mode or not + */ + private boolean isValidViewMode(String str) { + if (str.length() != 1) { + return false; + } + switch (str.charAt(0)) { + case('w'): + case('d'): + case('m'): + return true; + default: + return false; + } + } +} +``` +###### /java/seedu/address/logic/parser/SetAppointmentCommandParser.java +``` java +/** + * Parses input arguments and creates a new SetAppointmentCommand object + */ +public class SetAppointmentCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SetAppointmentCommand + * and returns a SetAppointmentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetAppointmentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_START_TIME, + PREFIX_END_TIME, PREFIX_PERSON_TO_MEET_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_START_TIME, PREFIX_END_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SetAppointmentCommand.MESSAGE_USAGE)); + } + + try { + Index index = null; + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + EventTime startTime = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_START_TIME)).get(); + EventTime endTime = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_END_TIME)).get(); + Optional optionalIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PERSON_TO_MEET_INDEX)); + if (optionalIndex.isPresent()) { + index = optionalIndex.get(); + } + Appointment appointment = new Appointment(title, startTime, endTime); + + return new SetAppointmentCommand(appointment, index); + } catch (IllegalValueException | IllegalArgumentException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +``` +###### /java/seedu/address/logic/parser/ListCommandParser.java +``` java +/** + * Parser for ListCommand + */ +public class ListCommandParser implements Parser { + /** + * Parses the given {@code args} of arguments in the context of the ListCommandParser + * and returns an ListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListCommand parse(String args) throws ParseException { + String item = args.trim(); + if (!isValidItem(item)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListCommand.MESSAGE_USAGE)); + } + return new ListCommand(item); + } + + /** + * @param str + * @return whether if the string is a valid view mode or not + */ + private boolean isValidItem(String str) { + switch (str) { + case(ListCommand.TYPE_CONTACT): + case(ListCommand.TYPE_STUDENT): + case(ListCommand.TYPE_APPOINTMENT): + case(ListCommand.TYPE_TASK): + case(ListCommand.TYPE_SHORTCUT): + return true; + default: + return false; + } + } +} +``` +###### /java/seedu/address/logic/parser/ParserUtil.java +``` java + /** + * Parses a {@code Optional title} into an {@code Optional} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Title> parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional<String> eventTime} into an {@code Optional<EventTime>} if {@code eventTime} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<EventTime> parseEventTime(Optional<String> eventTime) throws IllegalArgumentException { + requireNonNull(eventTime); + return eventTime.isPresent() ? Optional.of(parseEventTime(eventTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String eventTime} into a {@code EventTime}. + * Leading and trailing whitespaces will be trimmed. + */ + public static EventTime parseEventTime(String eventTime) throws IllegalArgumentException { + requireNonNull(eventTime); + String trimmedEventTime = eventTime.trim(); + return new EventTime(trimmedEventTime); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws IllegalValueException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new IllegalValueException(Tag.MESSAGE_TAG_NAME_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + /** + * Parses {@code Collection<String> tags} into a {@code Set<Tag>}. + */ + public static Set<Tag> parseTags(Collection<String> tags) throws IllegalValueException { + requireNonNull(tags); + final Set<Tag> tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } +} +``` +###### /java/seedu/address/logic/parser/ChangeThemeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ChangeThemeCommand object + */ +public class ChangeThemeCommandParser implements Parser<ChangeThemeCommand> { + /** + * Parses the given {@code viewMode} of arguments in the context of the ChangeThemeCommandParser + * and returns an ChangeThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ChangeThemeCommand parse(String args) throws ParseException { + String theme = args.trim(); + if (!isValidTheme(theme)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ChangeThemeCommand.MESSAGE_INVALID_THEME)); + } + return new ChangeThemeCommand(theme); + } + + /** + * + * @param theme + * @return whether if {@code theme} is a valid theme name + */ + private boolean isValidTheme(String theme) { + return !theme.isEmpty() && Arrays.asList(THEME_LIST).contains(theme); + } +} +``` +###### /java/seedu/address/logic/parser/SetTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new SetTaskCommand object + */ +public class SetTaskCommandParser implements Parser<SetTaskCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the SetTaskCommand + * and returns a SetTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_END_TIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_END_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SetTaskCommand.MESSAGE_USAGE)); + } + + try { + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + EventTime time = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_END_TIME)).get(); + + Task task = new Task(title, time); + + return new SetTaskCommand(task); + } catch (IllegalValueException | IllegalArgumentException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### /java/seedu/address/logic/commands/RemoveCommand.java +``` java +/** + * Remove an appointment or task identified using its last displayed index from the address book. + */ +public class RemoveCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "remove"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes the event identified by the index number used in the last event listing.\n" + + "Parameters: " + + " EVENT_TYPE (could be appointment or task)" + + "INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " appointment " + " 1"; + + public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Removed %1$s: %2$s"; + + private final Index targetIndex; + + private String eventTypeOfDeletedTarget; + + private Object eventToBeDeleted; + + public RemoveCommand(Index targetIndex, String eventTypeOfDeletedTarget) { + this.eventTypeOfDeletedTarget = eventTypeOfDeletedTarget; + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(eventToBeDeleted); + try { + if (eventTypeOfDeletedTarget.equals(LIST_TYPE_APPOINTMENT)) { + model.deleteAppointment((Appointment) eventToBeDeleted); + } else if (eventTypeOfDeletedTarget.equals(LIST_TYPE_TASK)) { + model.deleteTask((Task) eventToBeDeleted); + } + } catch (EventNotFoundException ive) { + throw new AssertionError(String.format("The target %s cannot be missing", eventTypeOfDeletedTarget)); + } + return new CommandResult( + String.format(MESSAGE_DELETE_EVENT_SUCCESS, eventTypeOfDeletedTarget, eventToBeDeleted)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (eventTypeOfDeletedTarget.equals(LIST_TYPE_APPOINTMENT)) { + List<Appointment> lastShownList = model.getFilteredAppointmentList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + eventToBeDeleted = lastShownList.get(targetIndex.getZeroBased()); + } else if (eventTypeOfDeletedTarget.equals(LIST_TYPE_TASK)) { + List<Task> lastShownList = model.getFilteredTaskList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + eventToBeDeleted = lastShownList.get(targetIndex.getZeroBased()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveCommand // instanceof handles nulls + && this.targetIndex.equals(((RemoveCommand) other).targetIndex) // state check + && Objects.equals(this.eventToBeDeleted, ((RemoveCommand) other).eventToBeDeleted)); + } +} +``` +###### /java/seedu/address/logic/commands/ToggleCalendarViewCommand.java +``` java +/** + * Command to change calendar view + */ +public class ToggleCalendarViewCommand extends Command { + + public static final String COMMAND_WORD = "calendar"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Toggles calendar view. \n" + + "Parameter: VIEW_MODE\n" + + "View mode: Day view: d, Week view: w, Month view: m\n" + + "Example: " + COMMAND_WORD + " d"; + + public static final String MESSAGE_VIEW_TOGGLE_SUCCESS = "View changed."; + + private Character viewMode; + + public ToggleCalendarViewCommand(Character viewMode) { + requireNonNull(viewMode); + this.viewMode = viewMode; + } + @Override + public CommandResult execute() throws CommandException { + EventsCenter.getInstance().post(new ToggleCalendarViewEvent(viewMode)); + return new CommandResult(MESSAGE_VIEW_TOGGLE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ToggleCalendarViewCommand // instanceof handles nulls + && this.viewMode == ((ToggleCalendarViewCommand) other).viewMode); // state check + } +} +``` +###### /java/seedu/address/logic/commands/ChangeThemeCommand.java +``` java +/** + * Change theme of the GUI. + */ +public class ChangeThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change the theme of TeachConnect.\n" + + "Parameters: THEME\n" + + "Example: " + COMMAND_WORD + " dark"; + + public static final String MESSAGE_CHANGE_THEME_SUCCESS = "Theme changed"; + + public static final String MESSAGE_INVALID_THEME = "Not a valid theme"; + + private final String theme; + + public ChangeThemeCommand(String theme) { + requireNonNull(theme); + this.theme = theme; + } + + @Override + public CommandResult execute() throws CommandException { + EventsCenter.getInstance().post(new ThemeChangeEvent(theme)); + return new CommandResult(MESSAGE_CHANGE_THEME_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeThemeCommand // instanceof handles nulls + && this.theme.equals(((ChangeThemeCommand) other).theme)); // state check + } +} +``` +###### /java/seedu/address/logic/commands/SetTaskCommand.java +``` java +/** + * Adds a task to the address book. + */ +public class SetTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "set_task"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to the address book.\n" + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_END_TIME + "DATE TIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Mark papers " + + PREFIX_END_TIME + "20/05/2018 12:00 "; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book"; + + private final Task toAdd; + + /** + * Creates a SetTaskCommand to add the specified {@code Task} + */ + public SetTaskCommand(Task task) { + requireNonNull(task); + toAdd = task; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateEventException e) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SetTaskCommand // instanceof handles nulls + && toAdd.equals(((SetTaskCommand) other).toAdd)); + } +} +``` +###### /java/seedu/address/logic/commands/SetAppointmentCommand.java +``` java +/** + * Adds an appointment with the person at {@code index} in the person list to the address book. + */ +public class SetAppointmentCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "set_appointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds an appoinment to the address book.\n" + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_START_TIME + "START-DATE START-TIME " + + PREFIX_END_TIME + "END-DATE END-TIME " + + PREFIX_PERSON_TO_MEET_INDEX + "PERSON TO MEET\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Meet James " + + PREFIX_START_TIME + "20/05/2018 10:00 " + + PREFIX_END_TIME + "20/05/2018 12:00 " + + PREFIX_PERSON_TO_MEET_INDEX + "3 "; + + public static final String MESSAGE_SUCCESS = "New appointment added: %1$s"; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "This appointment already exists in the address book"; + + private final Appointment baseAppointmentWithoutPerson; + private final Index index; + + private PersonToMeet personToMeet; + + /** + * Creates a SetAppointmentCommand without any PersonToMeet + */ + public SetAppointmentCommand(Appointment baseAppointmentWithoutPerson) { + this(baseAppointmentWithoutPerson, null); + } + + /** + * Creates a SetAppointmentCommand to add the specified {@code Appointment} + */ + public SetAppointmentCommand(Appointment baseAppointmentWithoutPerson, Index index) { + requireNonNull(baseAppointmentWithoutPerson); + this.baseAppointmentWithoutPerson = baseAppointmentWithoutPerson; + this.index = index; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + Appointment toAdd; + if (personToMeet != null) { + toAdd = new Appointment(baseAppointmentWithoutPerson.getTitle(), baseAppointmentWithoutPerson.getTime(), + baseAppointmentWithoutPerson.getEndTime(), personToMeet); + } else { + toAdd = baseAppointmentWithoutPerson; + } + model.addAppointment(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateEventException e) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT); + } + + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (index != null) { + List<Person> lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person person = lastShownList.get(index.getZeroBased()); + personToMeet = new PersonToMeet(person.getName().fullName, person.getEmail().value); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SetAppointmentCommand // instanceof handles nulls + && baseAppointmentWithoutPerson.equals(((SetAppointmentCommand) other).baseAppointmentWithoutPerson)); + } +} +``` +###### /java/seedu/address/storage/XmlAdaptedAppointment.java +``` java +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedAppointment { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String startTime; + @XmlElement(required = true) + private String endTime; + @XmlElement(required = true) + private String personToMeet; + + /** + * Constructs an XmlAdaptedAppointment. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAppointment() {} + + public XmlAdaptedAppointment(String title, String startTime, String endTime) { + this(title, startTime, endTime, null); + } + + /** + * Constructs an {@code XmlAdaptedAppointment} with the given appointment details. + */ + public XmlAdaptedAppointment(String title, String startTime, String endTime, String personToMeet) { + this.title = title; + this.startTime = startTime; + this.endTime = endTime; + if (personToMeet != null) { + this.personToMeet = personToMeet; + } + } + + /** + * Converts a given Appointment into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedAppointment + */ + public XmlAdaptedAppointment(Appointment source) { + title = source.getTitle().toString(); + startTime = source.getTime().toString(); + endTime = source.getEndTime().toString(); + if (source.getPersonToMeet() != null) { + personToMeet = source.getPersonToMeet().toString(); + } + } + + /** + * Converts this jaxb-friendly adapted person object into the model's Appointment object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted appointment + */ + public Appointment toModelType() throws IllegalValueException { + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(this.title)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + final Title title = new Title(this.title); + + if (this.startTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Start Time")); + } + if (this.endTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "End Time")); + } + + final EventTime startTime = new EventTime(this.startTime); + final EventTime endTime = new EventTime(this.endTime); + + if (!Appointment.isValidTime(startTime, endTime)) { + throw new IllegalValueException(Appointment.MESSAGE_TIME_PERIOD_CONSTRAINTS); + } + + if (this.personToMeet != null) { + String[] components = this.personToMeet.split(EMAIL_SPLITTER); + PersonToMeet personToMeet = new PersonToMeet(components[0], components[1]); + return new Appointment(title, startTime, endTime, personToMeet); + } + + return new Appointment(title, startTime, endTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAppointment)) { + return false; + } + + XmlAdaptedAppointment otherAppointment = (XmlAdaptedAppointment) other; + return Objects.equals(title, otherAppointment.title) + && Objects.equals(startTime, otherAppointment.startTime) + && Objects.equals(endTime, otherAppointment.endTime) + && Objects.equals(personToMeet, otherAppointment.personToMeet); + } +} +``` +###### /java/seedu/address/storage/XmlAdaptedTask.java +``` java +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String time; + + /** + * Constructs an XmlAdaptedTask. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedTask() {} + + /** + * Constructs an {@code XmlAdaptedTask} with the given task details. + */ + public XmlAdaptedTask(String title, String time) { + this.title = title; + this.time = time; + } + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedTask(Task source) { + title = source.getTitle().toString(); + time = source.getTime().toString(); + } + + /** + * Converts this jaxb-friendly adapted person object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(this.title)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + final Title title = new Title(this.title); + + if (this.time == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Time")); + } + final EventTime time = new EventTime(this.time); + + return new Task(title, time); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTask)) { + return false; + } + + XmlAdaptedTask otherTask = (XmlAdaptedTask) other; + return Objects.equals(title, otherTask.title) + && Objects.equals(time, otherTask.time); + } +} +``` +###### /java/seedu/address/model/AddressBook.java +``` java + //event operations + /** + * Adds an appointment to the address book. + * + * @throws DuplicateEventException if an equivalent appointment already exists. + */ + public void addAppointment(Appointment e) throws DuplicateEventException { + appointments.add(e); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws EventNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeAppointment(Appointment key) throws EventNotFoundException { + if (appointments.remove(key)) { + return true; + } else { + throw new EventNotFoundException(); + } + } + + /** + * Adds a task to the address book. + * + * @throws DuplicateEventException if an equivalent appointment already exists. + */ + public void addTask(Task e) throws DuplicateEventException { + tasks.add(e); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws EventNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeTask(Task key) throws EventNotFoundException { + if (tasks.remove(key)) { + return true; + } else { + throw new EventNotFoundException(); + } + } +} +``` +###### /java/seedu/address/model/event/Appointment.java +``` java +/** + * Represent an appointment in the schedule, contains time of the appointment as well as details and personMeet. + */ +public class Appointment { + public static final String MESSAGE_TIME_PERIOD_CONSTRAINTS = "The end time should be after the start time"; + + private final Title title; + private final EventTime time; + private final EventTime endTime; + private final PersonToMeet personToMeet; + + //Every field must be present and not null + public Appointment(Title title, EventTime startTime, EventTime endTime) { + this(title, startTime, endTime, null); + } + + //Every field except personToMeet must be present and not null + public Appointment(Title title, EventTime startTime, EventTime endTime, PersonToMeet personToMeet) { + requireAllNonNull(title, startTime, endTime); + checkArgument(isValidTime(startTime, endTime), MESSAGE_TIME_PERIOD_CONSTRAINTS); + this.title = title; + this.time = startTime; + this.endTime = endTime; + this.personToMeet = personToMeet; + } + + public Title getTitle() { + return title; + } + + public EventTime getTime() { + return time; + } + + public EventTime getEndTime() { + return endTime; + } + + public PersonToMeet getPersonToMeet() { + return personToMeet; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Appointment)) { + return false; + } + + Appointment otherAppointment = (Appointment) other; + return otherAppointment.getTitle().equals(this.getTitle()) + && otherAppointment.getTime().equals(this.getTime()) + && otherAppointment.getEndTime().equals(this.getEndTime()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(", Start Time: ") + .append(getTime().toString()) + .append(", End Time: ") + .append(getEndTime().toString()); + if (personToMeet != null) { + builder.append(", With: ") + .append(personToMeet.getName()); + } + return builder.toString(); + } + + /** + * Returns true if the given time is valid + */ + public static boolean isValidTime(EventTime startTime, EventTime endTime) { + return endTime.value.after(startTime.value); + } +} +``` +###### /java/seedu/address/model/event/Task.java +``` java +/** + * Represent a Task in the schedule, contains deadline as well as the title + */ +public class Task { + private Title title; + private EventTime time; + + //Every field must be present and not null + public Task(Title title, EventTime deadline) { + requireAllNonNull(title, deadline); + this.title = title; + this.time = deadline; + } + + public Title getTitle() { + return title; + } + + public EventTime getTime() { + return time; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getTitle().equals(this.getTitle()) + && otherTask.getTime().equals(this.getTime()); + } + + @Override + public String toString() { + return title + ", Deadline: " + time; + } +} +``` diff --git a/collated/functional/randypx.md b/collated/functional/randypx.md new file mode 100644 index 000000000000..5b252855418a --- /dev/null +++ b/collated/functional/randypx.md @@ -0,0 +1,120 @@ +# randypx +###### /java/seedu/address/model/person/UniquePersonList.java +``` java + /** + * Add a listener to the list for any changes. + * Update {@code contacts} for any changes made. + */ + public void addListener(UniqueContactList contacts) { + internalList.addListener(new ListChangeListener<Person>() { + @Override + public void onChanged(Change<? extends Person> c) { + contacts.updateList(c); + } + }); + } + + @Override + public Iterator<Person> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePersonList // instanceof handles nulls + && this.internalList.equals(((UniquePersonList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} +``` +###### /java/seedu/address/model/person/UniqueContactList.java +``` java +/** + * A list that is the aggregation of {@code UniquePersonList} and {@code UniqueStudentList} + * and is the list displayed in the GUI. + * This list remains up-to-date by listening to the changes of both lists and is not changed by anything else. + */ +public class UniqueContactList { + private final UniquePersonList persons; + private final UniqueStudentList students; + private final ObservableList<Person> combinedList = FXCollections.observableArrayList(); + + public UniqueContactList(UniquePersonList p, UniqueStudentList s) { + persons = p; + students = s; + persons.addListener(this); + students.addListener(this); + } + + /** + * This method is called when there is a change in eithor {@code UniquePersonList} or {@code UniqueStudentList}. + * @param c this contains the change(s) that has occured. + */ + public void updateList(ListChangeListener.Change<? extends Person> c) { + while (c.next()) { + if (c.wasReplaced()) { + for (int i = 0; i < c.getRemovedSize(); i++) { + int index = combinedList.indexOf(c.getRemoved().get(i)); + combinedList.set(index, c.getAddedSubList().get(i)); + } + if (c.getTo() > c.getRemovedSize()) { + for (int i = c.getRemovedSize(); i < c.getTo(); i++) { + combinedList.add(c.getAddedSubList().get(i)); + } + } + } else if (c.wasRemoved()) { + combinedList.removeAll(c.getRemoved()); + } else if (c.wasAdded()) { + combinedList.addAll(c.getAddedSubList()); + } + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Person> asObservableList() { + return FXCollections.unmodifiableObservableList(combinedList); + } + +} +``` +###### /java/seedu/address/model/person/UniqueStudentList.java +``` java + /** + * Add a listener to the list for any changes. + * Update {@code contacts} for any changes made. + */ + public void addListener(UniqueContactList contacts) { + internalList.addListener(new ListChangeListener<Student>() { + @Override + public void onChanged(Change<? extends Student> c) { + contacts.updateList(c); + } + }); + } + + @Override + public Iterator<Student> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && this.internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` diff --git a/collated/functional/shanmu9898.md b/collated/functional/shanmu9898.md new file mode 100644 index 000000000000..cc7ceca97025 --- /dev/null +++ b/collated/functional/shanmu9898.md @@ -0,0 +1,1091 @@ +# shanmu9898 +###### /resources/view/ShortcutListCard.fxml +``` fxml +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="80" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15" /> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="command" text="\$command" styleClass="cell_big_label" /> + <Label fx:id="shortcut" styleClass="cell_big_label_label" text="\$shortcut" /> + </HBox> + + </VBox> + </GridPane> +</HBox> +``` +###### /resources/view/ShortcutListPanel.fxml +``` fxml +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="shortcutListView" VBox.vgrow="ALWAYS" /> +</VBox> +``` +###### /java/seedu/address/ui/ShortcutListPanel.java +``` java +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * Panel containing the list of Shortcuts. + */ +public class ShortcutListPanel extends UiPart<Region> { + private static final String FXML = "ShortcutListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + + @FXML + private ListView<ShortcutCard> shortcutListView; + + public ShortcutListPanel(ObservableList<ShortcutDoubles> shortcutList) { + super(FXML); + setConnections(shortcutList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<ShortcutDoubles> shortcutList) { + ObservableList<ShortcutCard> mappedList = EasyBind.map(shortcutList, (shortcutDoubles) -> + new ShortcutCard(shortcutDoubles, shortcutList.indexOf(shortcutDoubles) + 1)); + shortcutListView.setItems(mappedList); + shortcutListView.setCellFactory(listView -> new ShortcutListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code AppointmentCard}. + */ + class ShortcutListViewCell extends ListCell<ShortcutCard> { + + @Override + protected void updateItem(ShortcutCard shortcutCard, boolean empty) { + super.updateItem(shortcutCard, empty); + + if (empty || shortcutCard == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(shortcutCard.getRoot()); + } + } + } +} +``` +###### /java/seedu/address/ui/ShortcutCard.java +``` java +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * An UI component that displays information of a {@code Shortcut Double} + */ +public class ShortcutCard extends UiPart<Region> { + + private static final String FXML = "ShortcutListCard.fxml"; + + public final ShortcutDoubles shortcutDoubles; + + @FXML + private HBox cardPane; + @FXML + private Label shortcut; + @FXML + private Label command; + @FXML + private Label id; + + public ShortcutCard(ShortcutDoubles shortcutDoubles, int displayedIndex) { + super(FXML); + + this.shortcutDoubles = shortcutDoubles; + id.setText(displayedIndex + ". "); + shortcut.setText("===> " + shortcutDoubles.shortcutWord); + command.setText(shortcutDoubles.commandWord); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ShortcutCard)) { + return false; + } + + // state check + ShortcutCard card = (ShortcutCard) other; + return id.getText().equals(card.id.getText()) + && shortcutDoubles.equals(card.shortcutDoubles); + } +} +``` +###### /java/seedu/address/logic/Logic.java +``` java + /** Returns an unmodifiable view of the filtered list of Shortcuts */ + ObservableList<ShortcutDoubles> getFilteredShortcutList(); +} +``` +###### /java/seedu/address/logic/parser/DeleteShortcutCommandParser.java +``` java +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DeleteShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteShortcutCommand object + */ +public class DeleteShortcutCommandParser implements Parser<DeleteShortcutCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteShortcutCommand + * and returns a DeleteShortcutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteShortcutCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] splitWords = trimmedArgs.split(" "); + if (splitWords.length > 2 || splitWords.length < 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteShortcutCommand.MESSAGE_USAGE)); + } else { + return new DeleteShortcutCommand(splitWords[0], splitWords[1]); + } + } +} +``` +###### /java/seedu/address/logic/parser/ImportCommandParser.java +``` java +/** + * Parses input arguments and creates a new ImportCommand object + */ +public class ImportCommandParser implements Parser<ImportCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ImportCommand + * and returns an ImportCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ImportCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } + + String[] parameterGetterArray = trimmedArgs.split(" "); + + if (parameterGetterArray.length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } else { + return new ImportCommand(parameterGetterArray[0]); + } + + } + + + +} +``` +###### /java/seedu/address/logic/parser/ExportCommandParser.java +``` java +/** + * Parses input arguments and creates a new ExportCommand object + */ +public class ExportCommandParser implements Parser<ExportCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ExportCommand + * and returns an ExportCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ExportCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_RANGE, + PREFIX_TAG_EXPORT, PREFIX_PATH, PREFIX_TYPE); + + String[] preambleArgs = argMultiMap.getPreamble().split(" "); + if (!arePrefixesPresent(argMultiMap, PREFIX_NAME, PREFIX_RANGE, PREFIX_PATH, PREFIX_TYPE) + || preambleArgs.length > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } + + String name = argMultiMap.getValue(PREFIX_NAME).orElse(""); + String range = argMultiMap.getValue(PREFIX_RANGE).orElse("all"); + String tag = argMultiMap.getValue(PREFIX_TAG_EXPORT).orElse("shouldnotbethistag"); + String path = argMultiMap.getValue(PREFIX_PATH).orElse(""); + String type = argMultiMap.getValue(PREFIX_TYPE).orElse("normal"); + + if (!(type.equals("excel") || type.equals("normal"))) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } + + Tag tagExport = new Tag(tag); + return new ExportCommand(range, tagExport, path, name, type); + + + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + + +} +``` +###### /java/seedu/address/logic/parser/ShortcutCommandParser.java +``` java +/** + * Parser + */ +public class ShortcutCommandParser implements Parser<ShortcutCommand> { + /** + * Parses the given {@code String} of arguments in the context of the ShortcutCommand + * and returns an ShortcutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShortcutCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] splitWords = trimmedArgs.split(" "); + if (splitWords.length > 2 || splitWords.length < 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShortcutCommand.MESSAGE_USAGE)); + } else { + return new ShortcutCommand(splitWords[0], splitWords[1]); + } + } +} +``` +###### /java/seedu/address/logic/commands/DeleteShortcutCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +/** + * Deletes a specific shortcut from the addressbook. + */ +public class DeleteShortcutCommand extends UndoableCommand { + public static final String COMMAND_WORD = "delete_shortcut"; + public static final String MESSAGE_USAGE = COMMAND_WORD + " CommandWord " + " ShortcutWord " + + " :Deletes a shortcut for any command word"; + public static final String MESSAGE_DELETE_SHORTCUT_SUCCESS = "The shortcut has been deleted!"; + private final String shortcutWord; + + private final String commandWord; + + private ShortcutDoubles commandShortcut; + + + public DeleteShortcutCommand(String commandWord, String shortcutWord) { + requireNonNull(commandWord); + requireNonNull(shortcutWord); + this.commandWord = commandWord; + this.shortcutWord = shortcutWord; + commandShortcut = new ShortcutDoubles(shortcutWord, commandWord); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(commandShortcut); + try { + model.deleteCommandShortcut(commandShortcut); + } catch (UniqueShortcutDoublesList.CommandShortcutNotFoundException csnf) { + throw new CommandException("Please enter a valid Shortcut Command you have saved"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_SHORTCUT_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteShortcutCommand // instanceof handles nulls + && this.shortcutWord.equals(((DeleteShortcutCommand) other).shortcutWord) // state check + && this.commandWord.equals(((DeleteShortcutCommand) other).commandWord) // state check + && Objects.equals(this.commandShortcut, ((DeleteShortcutCommand) other).commandShortcut)); + } +} +``` +###### /java/seedu/address/logic/commands/ExportCommand.java +``` java +/** + * + * Exports people to an XML file of choice based on tag, index or range + */ +public class ExportCommand extends Command { + + public static final String MESSAGE_FAIL = "TeachConnect faced some error while exporting! Please try again!"; + public static final String MESSAGE_OUT_OF_BOUNDS = "Please check the index bounds!"; + public static final String MESSAGE_SUCCESS = "Contacts have been successfully exported!"; + public static final String MESSAGE_RANGE_ERROR = "Please input valid range"; + + public static final String COMMAND_WORD = "export"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": exports contacts to the TeachConnect Book based " + + "on index, range or tag \n" + + "Parameters: " + + PREFIX_NAME + " NAME " + + PREFIX_RANGE + " RANGE " + + PREFIX_TAG_EXPORT + " TAG " + + PREFIX_PATH + " PATH " + + PREFIX_TYPE + "FORMAT \n" + + "Example 1: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "all " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "Excel/Normal \n" + + "Example 2: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "1 " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "Excel/Normal \n" + + "Example 3: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "1,2 " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "Excel/normal \n"; + + + private Tag tag; + private final String range; + private final String path; + private AddressBook teachConnectBook; + private AddressBookStorage teachConnectStorage; + private final String nameOfExportFile; + private final String type; + + /** + * Creates an ExportCommand to export the specified {@code Persons} + */ + public ExportCommand(String range, Tag tag, String path, String nameOfExportFile, String type) { + requireNonNull(range); + requireNonNull(tag); + requireNonNull(path); + requireNonNull(nameOfExportFile); + requireNonNull(type); + + this.range = range; + this.path = path; + this.tag = tag; + this.nameOfExportFile = nameOfExportFile; + this.type = type; + + + teachConnectBook = new AddressBook(); + } + + @Override + public CommandResult execute() { + String[] rangeGiven; + try { + rangeGiven = handleRange(); + } catch (IOException e) { + return new CommandResult(MESSAGE_RANGE_ERROR); + } + CommandResult handledRangeSituation; + try { + handledRangeSituation = handleRangeArray(rangeGiven); + } catch (DuplicatePersonException e) { + return new CommandResult(MESSAGE_FAIL); + } catch (IndexOutOfBoundsException e) { + return new CommandResult(MESSAGE_OUT_OF_BOUNDS); + } + if (handledRangeSituation != null) { + return handledRangeSituation; + } + + if (!tryStorage(type)) { + return new CommandResult(MESSAGE_FAIL); + } + return new CommandResult(MESSAGE_SUCCESS); + + } + + /** + * This method tries creating and storing the export file. + * @return + */ + private boolean tryStorage(String type) { + teachConnectStorage = new XmlAddressBookStorage(path + "/" + nameOfExportFile + ".xml"); + try { + teachConnectStorage.saveAddressBook(teachConnectBook); + } catch (IOException e) { + return false; + } + if (type.equals("excel")) { + return saveAsCsv(); + } + return true; + } + + /** + * Will save as a CSV file depending on the type of input + * @return boolean + */ + private boolean saveAsCsv() { + File stylesheet = new File("./src/main/resources/Util/style.xsl"); + File xmlSource = new File(path + "/" + nameOfExportFile + ".xml"); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + return false; + } + Document document; + try { + document = builder.parse(xmlSource); + } catch (SAXException e) { + return false; + } catch (IOException e) { + return false; + } + + StreamSource styleSource = new StreamSource(stylesheet); + Transformer transformer; + try { + transformer = TransformerFactory.newInstance().newTransformer(styleSource); + } catch (TransformerConfigurationException e) { + return false; + } + Source source = new DOMSource(document); + Result outputTarget = new StreamResult(new File(path + "/" + nameOfExportFile + ".csv")); + try { + transformer.transform(source, outputTarget); + } catch (TransformerException e) { + return false; + } + + return true; + } + + /** + * Handles the range array returned by the handleRange() function + * @param rangeGiven + * @return + */ + private CommandResult handleRangeArray(String[] rangeGiven) throws DuplicatePersonException, + IndexOutOfBoundsException { + if (rangeGiven[0].equals("all")) { + exportAllRange(tag); + } else { + if (rangeGiven.length != 1) { + for (int i = 0; i < rangeGiven.length; i++) { + int low = Integer.parseInt(rangeGiven[0]); + int high = Integer.parseInt(rangeGiven[1]); + if (low >= high) { + return new CommandResult(MESSAGE_RANGE_ERROR); + } else { + exportRange(low, high, tag); + } + } + } else { + int low = Integer.parseInt(rangeGiven[0]); + exportSpecific(low); + } + + + } + return null; + } + + /** + * Adds a specific person to the teachConnectBook + * + * @param low + * @throws DuplicatePersonException + * @throws IndexOutOfBoundsException + */ + private void exportSpecific(int low) throws DuplicatePersonException, IndexOutOfBoundsException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + teachConnectBook.addPerson(exportPeople.get(low - 1)); + } + + /** + * Exports a range of people based on the tag and the index range given + * + * @param low + * @param high + * @param tag + * @throws DuplicatePersonException + * @throws IndexOutOfBoundsException + */ + private void exportRange(int low, int high, Tag tag) throws DuplicatePersonException, IndexOutOfBoundsException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + ArrayList<Person> exportAddition = new ArrayList<Person>(); + if (tag.equals(new Tag("shouldnotbethistag"))) { + for (int i = low; i < high; i++) { + exportAddition.add(exportPeople.get(i - 1)); + } + teachConnectBook.setPersons(exportAddition); + } else { + for (int i = low; i < high; i++) { + if (exportPeople.get(i - 1).getTags().contains(tag)) { + exportAddition.add(exportPeople.get(i - 1)); + } + + } + } + + teachConnectBook.setPersons(exportAddition); + } + + /** + * Exports all the contacts in the TeachConnect book if contain certain tag + * + * @param tag + * @throws DuplicatePersonException + */ + private void exportAllRange(Tag tag) throws DuplicatePersonException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + if (tag.equals(new Tag("shouldnotbethistag"))) { + teachConnectBook.setPersons(exportPeople); + } else { + ArrayList<Person> exportAddition = new ArrayList<Person>(); + for (int i = 0; i < exportPeople.size(); i++) { + if (exportPeople.get(i).getTags().contains(tag)) { + exportAddition.add(exportPeople.get(i)); + } + } + teachConnectBook.setPersons(exportAddition); + } + } + + /** + * Helper method to identify the lower and higher end of the range given + * + * @return rangeStringArray + */ + public String[] handleRange() throws IOException { + String[] rangeStringArray = this.range.split(","); + if (rangeStringArray.length > 2) { + throw new IOException(); + } + return rangeStringArray; + + } + + /** + * + * @param other [in this case ExportCommand] + * @return a boolean value + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof ExportCommand)) { + return false; + } + + ExportCommand e = (ExportCommand) other; + return range.equals(e.range) && path.equals(e.path); + } + +} +``` +###### /java/seedu/address/logic/commands/ShortcutCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +/** + * + */ +public class ShortcutCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "shortcut"; + public static final String MESSAGE_USAGE = COMMAND_WORD + " CommandWord " + " ShortcutWord " + + " :Creates a shortcut for any command word"; + public static final String MESSAGE_SHORTCUT_AVAILABLE = "This shortcut already exists!"; + public static final String MESSAGE_SUCCESS = "Successfully added the shortcut"; + public static final String MESSAGE_NO_COMMAND_TO_MAP = "The command statement is invalid and hence cant be mapped!"; + + private final String shortcutWord; + + private final String commandWord; + + private List<ShortcutDoubles> commandsList; + + private final String[] commandsPresent = {"add", "clear", "theme", "delete", "edit", "exit", "export", "find", + "help", "history", "import", "list", "redo", "undo", "select", + "set_appointment", "set_task", "shortcut", "undo", "calendar", + "delete_shortcut", "remove"}; + + public ShortcutCommand(String commandWord, String shortcutWord) { + requireNonNull(commandWord); + requireNonNull(shortcutWord); + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + commandsList = model.getFilteredCommandsList(); + if (commandsList != null) { + if (checkIfCommandPresent()) { + return new CommandResult(String.format(MESSAGE_SHORTCUT_AVAILABLE)); + } + + } + + ShortcutDoubles toAdd = new ShortcutDoubles(shortcutWord, commandWord); + try { + model.addCommandShortcut(toAdd); + } catch (UniqueShortcutDoublesList.DuplicateShortcutDoublesException e) { + return new CommandResult(String.format(MESSAGE_SHORTCUT_AVAILABLE)); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + /** + * Checks if the shortcut command is valid or not + * @return whether true or false + */ + private boolean checkIfCommandPresent() throws CommandException { + if (!containsKeyWord(commandWord) || containsKeyWord(shortcutWord)) { + throw new CommandException(MESSAGE_NO_COMMAND_TO_MAP); + } + for (ShortcutDoubles s : commandsList) { + if (s.shortcutWord.equals(shortcutWord)) { + return true; + } + } + return false; + } + + /** + * Checks if the command word is in the Array of commands present + * @param commandWord + * @return whether true if the command is present in the command word list or false otherwise + */ + private boolean containsKeyWord(String commandWord) { + for (String s : commandsPresent) { + if (s.equals(commandWord)) { + return true; + } + } + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShortcutCommand // instanceof handles nulls + && this.shortcutWord.equals(((ShortcutCommand) other).shortcutWord) // state check + && this.commandWord.equals(((ShortcutCommand) other).commandWord)); // state check + } +} +``` +###### /java/seedu/address/logic/commands/ImportCommand.java +``` java +/** + * Imports contacts from a different TeachConnect XML file + */ +public class ImportCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "import"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": imports contacts to the address book." + + "Parameters: file location...\n" + + "Example: " + COMMAND_WORD + " main/src/test/data/sandbox/somerandomfile.xml"; + public static final String MESSAGE_SUCCESS = "%1$s contacts have been successfully imported " + + "and %2$s have been left out!"; + protected static final String MESSAGE_INVALID_FILE = "Please input a valid file location"; + protected Storage storage; + private AddressBook addressBookImported; + private AddressBookStorage addressBookStorage; + private String filePath; + private int numberAdded = 0; + private int numberNotAdded = 0; + + /** + * Creates an ImportCommand to import the specified TeachConnect XML file + */ + public ImportCommand(String importPath) { + requireNonNull(importPath); + this.filePath = importPath; + addressBookStorage = new XmlAddressBookStorage(filePath); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + if (addressBookStorage.readAddressBook(filePath).isPresent()) { + this.addressBookImported = new AddressBook(addressBookStorage.readAddressBook().get()); + ObservableList<Person> people = addressBookImported.getPersonList(); + for (int i = 0; i < people.size(); i++) { + try { + model.addPerson(people.get(i)); + numberAdded++; + } catch (DuplicatePersonException e) { + numberNotAdded++; + } + } + } else { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } + } catch (DataConversionException e) { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } catch (IOException e) { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, numberAdded, numberNotAdded)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ImportCommand // instanceof handles nulls + && filePath.equals(((ImportCommand) other).filePath)); + } + + +} + + + + +``` +###### /java/seedu/address/storage/XmlAdaptedShortcutDouble.java +``` java +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * + */ +public class XmlAdaptedShortcutDouble { + @XmlElement + private String shortcutWord; + @XmlElement + private String commandWord; + + public XmlAdaptedShortcutDouble() {} + + public XmlAdaptedShortcutDouble(String shortcutWord, String commandWord) { + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + public XmlAdaptedShortcutDouble(ShortcutDoubles source) { + shortcutWord = source.shortcutWord; + commandWord = source.commandWord; + } + + public ShortcutDoubles toModelType() throws IllegalValueException { + return new ShortcutDoubles(shortcutWord, commandWord); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedShortcutDouble)) { + return false; + } + + return commandWord.equals(((XmlAdaptedShortcutDouble) other).commandWord) + && shortcutWord.equals(((XmlAdaptedShortcutDouble) other).shortcutWord); + } + +} +``` +###### /java/seedu/address/model/AddressBook.java +``` java + public void setShorcutCommands(List<ShortcutDoubles> shorcutCommands) { + this.shorcutCommands.setCommandsList(shorcutCommands); + } +``` +###### /java/seedu/address/model/AddressBook.java +``` java + /** + * + * @param commandShortcut + * @return a boolean variable + * @throws UniqueShortcutDoublesList.CommandShortcutNotFoundException + */ + public boolean removeShortcutDouble(ShortcutDoubles commandShortcut) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + if (shorcutCommands.remove(commandShortcut)) { + return true; + } else { + throw new UniqueShortcutDoublesList.CommandShortcutNotFoundException(); + } + } + //author + + //// tag-level operations + + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } +``` +###### /java/seedu/address/model/AddressBook.java +``` java + public void addShortcutDoubles(ShortcutDoubles s) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + shorcutCommands.add(s); + } +``` +###### /java/seedu/address/model/AddressBook.java +``` java + /** + * Removes the particular tag for all people in the AddressBook. + */ + public void removeTag(Tag tag) throws DuplicatePersonException, PersonNotFoundException { + for (Person person : persons) { + removeTagFromPerson(tag, person); + } + for (Student student : students) { + removeTagFromStudent(tag, student); + } + + } + + + /** + * Removes the particular tag for that particular person in the AddressBook. + */ + private void removeTagFromPerson(Tag tag, Person person) throws PersonNotFoundException, DuplicatePersonException { + Set<Tag> listOfTags = new HashSet<>(person.getTags()); + + if (listOfTags.contains(tag)) { + listOfTags.remove(tag); + } else { + return; + } + + Person updatedPerson = new Person(person.getName(), person.getPhone(), person.getEmail(), + person.getAddress(), listOfTags); + + updatePerson(person, updatedPerson); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + @Override + public synchronized void addCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + addressBook.addShortcutDoubles(shortcutDoubles); + indicateAddressBookChanged(); + } + + @Override + public synchronized void deleteCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + addressBook.removeShortcutDouble(shortcutDoubles); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + @Override + public ObservableList<ShortcutDoubles> getFilteredCommandsList() { + return FXCollections.unmodifiableObservableList(filteredShortcutCommands); + } +``` +###### /java/seedu/address/model/shortcuts/ShortcutDoubles.java +``` java +package seedu.address.model.shortcuts; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Command Double + */ +public class ShortcutDoubles { + + public final String shortcutWord; + public final String commandWord; + + public ShortcutDoubles(String shortcutWord, String commandWord) { + requireNonNull(shortcutWord); + requireNonNull(commandWord); + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ShortcutDoubles)) { + return false; + } + + ShortcutDoubles otherShortcut = (ShortcutDoubles) other; + return otherShortcut.commandWord.equals(this.commandWord) + && otherShortcut.shortcutWord.equals(this.shortcutWord); + } +} +``` +###### /java/seedu/address/model/shortcuts/UniqueShortcutDoublesList.java +``` java +package seedu.address.model.shortcuts; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.exceptions.DuplicateDataException; +import seedu.address.commons.util.CollectionUtil; + +/** + * + */ +public class UniqueShortcutDoublesList { + + private final ObservableList<ShortcutDoubles> internalList = FXCollections.observableArrayList(); + + public UniqueShortcutDoublesList(){ + + } + + /** + * Adds Shortcut Doubles to the internal list + * @param toAdd + * @throws DuplicateShortcutDoublesException + */ + public void add(ShortcutDoubles toAdd) throws DuplicateShortcutDoublesException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateShortcutDoublesException(); + } + internalList.add(toAdd); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns an ObservableList of the internallist + * @return + */ + public ObservableList<ShortcutDoubles> asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Gives a duplicate Except + */ + public static class DuplicateShortcutDoublesException extends DuplicateDataException { + protected DuplicateShortcutDoublesException() { + super("Operation would result in duplicate Doubles"); + } + } + + /** + * Helps in checking if there are duplicates + * @param toCheck + * @return + */ + public boolean contains(ShortcutDoubles toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof UniqueShortcutDoublesList // instanceof handles nulls + && this.internalList.equals(((UniqueShortcutDoublesList) other).internalList)); + } + + public void setCommandsList(List<ShortcutDoubles> commandsList) { + requireNonNull(commandsList); + internalList.setAll(commandsList); + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Removes the equvivalent command shortcut from the list. + * @param shortcutDoubles + * + * @throws UniqueShortcutDoublesList.CommandShortcutNotFoundException + */ + public boolean remove(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + requireNonNull(shortcutDoubles); + final boolean shortcutToBeDeleted = internalList.remove(shortcutDoubles); + if (!shortcutToBeDeleted) { + throw new UniqueShortcutDoublesList.CommandShortcutNotFoundException(); + } + return shortcutToBeDeleted; + } + + /** + * Exception when the command shortcut is not present in the list of stored commands + */ + public static class CommandShortcutNotFoundException extends Exception {} +} +``` diff --git a/collated/test/Sisyphus25-reused.md b/collated/test/Sisyphus25-reused.md new file mode 100644 index 000000000000..ca27d38124a8 --- /dev/null +++ b/collated/test/Sisyphus25-reused.md @@ -0,0 +1,19 @@ +# Sisyphus25-reused +###### /java/guitests/guihandles/PersonCardHandle.java +``` java + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * + * @param tag Text value of the tag label + * @return List of style classes for tag label with text value {@code tag} + */ + public List<String> getTagStyleClasses(String tag) { + return tagLabels + .stream() + .filter(label -> label.getText().equals(tag)) + .map(Label::getStyleClass) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No such tag.")); + } +} +``` diff --git a/collated/test/Sisyphus25.md b/collated/test/Sisyphus25.md new file mode 100644 index 000000000000..ea831a1bad9b --- /dev/null +++ b/collated/test/Sisyphus25.md @@ -0,0 +1,933 @@ +# Sisyphus25 +###### /java/seedu/address/ui/AppointmentCardTest.java +``` java +public class AppointmentCardTest extends GuiUnitTest { + + @Test + public void equals() { + Appointment appointment = TYPICAL_APPOINTMENT_2; + AppointmentCard appointmentCard = new AppointmentCard(appointment, 0); + + // same appointment, same index -> returns true + AppointmentCard copy = new AppointmentCard(appointment, 0); + assertTrue(appointmentCard.equals(copy)); + + // same object -> returns true + assertTrue(appointmentCard.equals(appointmentCard)); + + // null -> returns false + assertFalse(appointmentCard.equals(null)); + + // different types -> returns false + assertFalse(appointmentCard.equals(0)); + + // different appointment, same index -> returns false + Appointment differentAppointment = TYPICAL_APPOINTMENT_3; + assertFalse(appointmentCard.equals(new AppointmentCard(differentAppointment, 0))); + + // same appointment, different index -> returns false + assertFalse(appointmentCard.equals(new AppointmentCard(appointment, 1))); + } + +} +``` +###### /java/seedu/address/ui/TaskCardTest.java +``` java +public class TaskCardTest extends GuiUnitTest { + + @Test + public void equals() { + Task task = TYPICAL_TASK_2; + TaskCard taskCard = new TaskCard(task, 0); + + // same task, same index -> returns true + TaskCard copy = new TaskCard(task, 0); + assertTrue(taskCard.equals(copy)); + + // same object -> returns true + assertTrue(taskCard.equals(taskCard)); + + // null -> returns false + assertFalse(taskCard.equals(null)); + + // different types -> returns false + assertFalse(taskCard.equals(0)); + + // different task, same index -> returns false + Task differentTask = TYPICAL_TASK_1; + assertFalse(taskCard.equals(new TaskCard(differentTask, 0))); + + Task expiredTask = TYPICAL_TASK_EXPIRED; + TaskCard expiredTaskCard = new TaskCard(TYPICAL_TASK_EXPIRED, 1); + // same task, different index -> returns false + assertFalse(taskCard.equals(expiredTaskCard)); + } +} +``` +###### /java/seedu/address/logic/parser/RemoveCommandParserTest.java +``` java +public class RemoveCommandParserTest { + private RemoveCommandParser parser = new RemoveCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "appointment" + " 1", new RemoveCommand(INDEX_FIRST, "appointment")); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "not valid", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "Appointment" + " 1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "appointment" + " -1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/parser/ChangeThemeCommandParserTest.java +``` java +/** + * Test scope: similar to ToggleCalendarViewCommandParser Test + */ +public class ChangeThemeCommandParserTest { + private ChangeThemeCommandParser parser = new ChangeThemeCommandParser(); + + @Test + public void parse_validArgs_returnsToggleCalendarViewCommand() { + assertParseSuccess(parser, "dark ", new ChangeThemeCommand("dark")); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "not a theme", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_INVALID_THEME)); + assertParseFailure(parser, "x", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_INVALID_THEME)); + } +} +``` +###### /java/seedu/address/logic/parser/SetAppointmentCommandParserTest.java +``` java +public class SetAppointmentCommandParserTest { + private SetAppointmentCommandParser parser = new SetAppointmentCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Appointment expectedAppointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + new SetAppointmentCommand(expectedAppointment)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // no personToMeet + Appointment expectedAppointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + assertParseSuccess(parser, TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + new SetAppointmentCommand((expectedAppointment))); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetAppointmentCommand.MESSAGE_USAGE); + + // missing title prefix + assertParseFailure(parser, VALID_TITLE + START_TIME_DESC + END_TIME_DESC, + expectedMessage); + + // missing start time prefix + assertParseFailure(parser, TITLE_DESC + VALID_START_TIME + END_TIME_DESC, + expectedMessage); + + // missing start time prefix + assertParseFailure(parser, TITLE_DESC + START_TIME_DESC + VALID_END_TIME, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_TITLE + VALID_START_TIME + VALID_END_TIME, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid title + assertParseFailure(parser, INVALID_TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + Title.MESSAGE_TITLE_CONSTRAINTS); + + // invalid start time + assertParseFailure(parser, TITLE_DESC + INVALID_START_TIME_DESC + END_TIME_DESC, + EventTime.MESSAGE_TIME_CONSTRAINTS); + + // invalid end time + assertParseFailure(parser, TITLE_DESC + START_TIME_DESC + INVALID_END_TIME_DESC, + EventTime.MESSAGE_TIME_CONSTRAINTS); + } +} +``` +###### /java/seedu/address/logic/parser/SetTaskCommandParserTest.java +``` java +public class SetTaskCommandParserTest { + private SetTaskCommandParser parser = new SetTaskCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Task expectedTask = new Task(new Title(VALID_TITLE), new EventTime(VALID_END_TIME)); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + TITLE_DESC + END_TIME_DESC, + new SetTaskCommand(expectedTask)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetTaskCommand.MESSAGE_USAGE); + + // missing title prefix + assertParseFailure(parser, VALID_TITLE + END_TIME_DESC, + expectedMessage); + + // missing end time prefix + assertParseFailure(parser, TITLE_DESC + VALID_END_TIME, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_TITLE + VALID_END_TIME, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid title + assertParseFailure(parser, INVALID_TITLE_DESC + END_TIME_DESC, Title.MESSAGE_TITLE_CONSTRAINTS); + + // invalid end time + assertParseFailure(parser, TITLE_DESC + INVALID_END_TIME_DESC, EventTime.MESSAGE_TIME_CONSTRAINTS); + } +} +``` +###### /java/seedu/address/logic/parser/ListCommandParserTest.java +``` java +public class ListCommandParserTest { + private ListCommandParser parser = new ListCommandParser(); + + @Test + public void parse_validArgs_returnsListCommand() { + assertParseSuccess(parser, "contacts", new ListCommand(ListCommand.TYPE_CONTACT)); + assertParseSuccess(parser, "students", new ListCommand(ListCommand.TYPE_STUDENT)); + assertParseSuccess(parser, "tasks", new ListCommand(ListCommand.TYPE_TASK)); + assertParseSuccess(parser, "appointments", new ListCommand(ListCommand.TYPE_APPOINTMENT)); + assertParseSuccess(parser, "shortcuts", new ListCommand(ListCommand.TYPE_SHORTCUT)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "ffffffd", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "event", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/parser/AddressBookParserTest.java +``` java + @Test + public void parseCommand_toggleCalendarView() throws Exception { + ToggleCalendarViewCommand command = + (ToggleCalendarViewCommand) parser.parseCommand(ToggleCalendarViewCommand.COMMAND_WORD + " " + "m"); + assertEquals(new ToggleCalendarViewCommand('m'), command); + } + + @Test + public void parseCommand_setAppointment() throws Exception { + SetAppointmentCommand command = + (SetAppointmentCommand) parser.parseCommand(SetAppointmentCommand.COMMAND_WORD + + TITLE_DESC + START_TIME_DESC + END_TIME_DESC); + Appointment appointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + assertEquals(new SetAppointmentCommand(appointment), command); + } + + @Test + public void parseCommand_setTask() throws Exception { + SetTaskCommand command = + (SetTaskCommand) parser.parseCommand(SetTaskCommand.COMMAND_WORD + TITLE_DESC + END_TIME_DESC); + Task task = new Task(new Title(VALID_TITLE), new EventTime(VALID_END_TIME)); + assertEquals(new SetTaskCommand(task), command); + } + + @Test + public void parseCommand_changeTheme() throws Exception { + ChangeThemeCommand command = + (ChangeThemeCommand) parser.parseCommand(ChangeThemeCommand.COMMAND_WORD + " " + "dark"); + assertEquals(new ChangeThemeCommand("dark"), command); + } + + @Test + public void parseCommand_remove() throws Exception { + RemoveCommand commandRemoveAppointment = + (RemoveCommand) parser.parseCommand(RemoveCommand.COMMAND_WORD + " " + "appointment" + " " + "1"); + RemoveCommand commandRemoveTask = + (RemoveCommand) parser.parseCommand(RemoveCommand.COMMAND_WORD + " " + "task" + " " + "2"); + assertEquals(new RemoveCommand(Index.fromOneBased(1), "appointment"), commandRemoveAppointment); + assertEquals(new RemoveCommand(Index.fromOneBased(2), "task"), commandRemoveTask); + } +``` +###### /java/seedu/address/logic/parser/ParserUtilTest.java +``` java + @Test + public void parseTitle_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTitle((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTitle((Optional<String>) null)); + } + + @Test + public void parseTitle_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTitle(" ")); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTitle(Optional.of(" "))); + } + + @Test + public void parseTitle_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseTitle(Optional.empty()).isPresent()); + } + + @Test + public void parseTitle_validValue_returnsTitle() throws Exception { + String validTitle = "Hanging out"; + Title expectedTitle = new Title(validTitle); + assertEquals(expectedTitle, ParserUtil.parseTitle(validTitle)); + assertEquals(Optional.of(expectedTitle), ParserUtil.parseTitle(Optional.of(validTitle))); + } + + @Test + public void parseEventTime_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEventTime((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEventTime((Optional<String>) null)); + } + + @Test + public void parseEventTime_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseEventTime(Optional.empty()).isPresent()); + } + + @Test + public void parseEventTime_validValue_returnsEventTime() throws Exception { + String validTime = "20/10/2018 10:00"; + EventTime expectedEventTime = new EventTime(validTime); + assertEquals(expectedEventTime, ParserUtil.parseEventTime(validTime)); + assertEquals(Optional.of(expectedEventTime), ParserUtil.parseEventTime(Optional.of(validTime))); + } + +``` +###### /java/seedu/address/logic/parser/ToggleCalendarViewParserTest.java +``` java +public class ToggleCalendarViewParserTest { + private ToggleCalendarViewParser parser = new ToggleCalendarViewParser(); + + @Test + public void parse_validArgs_returnsToggleCalendarViewCommand() { + assertParseSuccess(parser, "d", new ToggleCalendarViewCommand('d')); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "day", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ToggleCalendarViewCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "x", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ToggleCalendarViewCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/commands/RemoveCommandTest.java +``` java + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; + +/** + * Contains Test for {@code RemoveCommand} + */ +public class RemoveCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexRemoveAppointment_success() throws Exception { + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Appointment appointmentToDelete = model.getFilteredAppointmentList().get(INDEX_FIRST.getZeroBased()); + RemoveCommand removeCommandRemovingAppointment = prepareCommand(INDEX_FIRST, "appointment"); + String expectedMessage = + String.format(RemoveCommand.MESSAGE_DELETE_EVENT_SUCCESS, "appointment", appointmentToDelete); + expectedModel.deleteAppointment(appointmentToDelete); + assertCommandSuccess(removeCommandRemovingAppointment, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexRemoveTask_success() throws Exception { + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST.getZeroBased()); + RemoveCommand removeCommandRemovingTask = prepareCommand(INDEX_FIRST, "task"); + String expectedMessage = + String.format(RemoveCommand.MESSAGE_DELETE_EVENT_SUCCESS, "task", taskToDelete); + expectedModel.deleteTask(taskToDelete); + assertCommandSuccess(removeCommandRemovingTask, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + RemoveCommand removeCommandRemovingTask = prepareCommand(outOfBoundIndex, "task"); + + Index outOfBoundIndex2 = Index.fromOneBased(model.getFilteredAppointmentList().size() + 1); + RemoveCommand removeCommandRemovingAppointment = prepareCommand(outOfBoundIndex2, "appointment"); + + assertCommandFailure(removeCommandRemovingTask, model, Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + assertCommandFailure(removeCommandRemovingAppointment, + model, Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + @Test + public void equals() throws Exception { + RemoveCommand removeCommandRemovingAppointment = prepareCommand(INDEX_FIRST, "appointment"); + RemoveCommand removeCommandRemovingTask = prepareCommand(INDEX_SECOND, "task"); + + // same object -> returns true + assertTrue(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointment)); + + // same values -> returns true + RemoveCommand removeCommandRemovingAppointmentCopy = prepareCommand(INDEX_FIRST, "appointment"); + assertTrue(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointmentCopy)); + + // one command preprocessed when previously equal -> returns false + removeCommandRemovingAppointmentCopy.preprocessUndoableCommand(); + assertFalse(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointmentCopy)); + + // different types -> returns false + assertFalse(removeCommandRemovingAppointment.equals(1)); + + // null -> returns false + assertFalse(removeCommandRemovingAppointment.equals(null)); + + // different person -> returns false + assertFalse(removeCommandRemovingAppointment.equals(removeCommandRemovingTask)); + } + + /** + * Returns a {@code RemoveCommand} with the parameter {@code index}, {@code eventType}. + */ + private RemoveCommand prepareCommand(Index index, String eventType) { + RemoveCommand removeCommand = new RemoveCommand(index, eventType); + removeCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return removeCommand; + } +} +``` +###### /java/seedu/address/logic/commands/ToggleCalendarViewCommandTest.java +``` java +public class ToggleCalendarViewCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + @Test + public void execute_help_success() throws CommandException { + Character viewMode = 'd'; + CommandResult result = new ToggleCalendarViewCommand(viewMode).execute(); + assertEquals(MESSAGE_VIEW_TOGGLE_SUCCESS, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleCalendarViewEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } +} +``` +###### /java/seedu/address/logic/commands/SetTaskCommandTest.java +``` java +public class SetTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullAppointment_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new SetTaskCommand(null); + } + + @Test + public void execute_appointmentAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingTaskAdded modelStub = new ModelStubAcceptingTaskAdded(); + + CommandResult commandResult = getSetTaskCommand(TYPICAL_TASK_2, modelStub).execute(); + + assertEquals(String.format(SetTaskCommand.MESSAGE_SUCCESS, TYPICAL_TASK_2), commandResult.feedbackToUser); + assertEquals(Arrays.asList(TYPICAL_TASK_2), modelStub.tasksAdded); + } + + @Test + public void execute_duplicateEvent_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicateEventException(); + + thrown.expect(CommandException.class); + thrown.expectMessage(SetTaskCommand.MESSAGE_DUPLICATE_TASK); + + getSetTaskCommand(TYPICAL_TASK_1, modelStub).execute(); + } + + @Test + public void equals() { + SetTaskCommand addTask1 = + new SetTaskCommand(TYPICAL_TASK_1); + SetTaskCommand addTask2 = + new SetTaskCommand(TYPICAL_TASK_2); + + // same object -> returns true + assertTrue(addTask1.equals(addTask1)); + + // same values -> returns true + SetTaskCommand addAppointment1Copy = + new SetTaskCommand(TYPICAL_TASK_1); + assertTrue(addTask1.equals(addAppointment1Copy)); + + // different types -> returns false + assertFalse(addTask1.equals(1)); + + // null -> returns false + assertFalse(addTask1.equals(null)); + + // different tasks -> returns false + assertFalse(addTask1.equals(addTask2)); + } + + /** + * Generates a new SetTaskCommand with the details of the given task. + */ + private SetTaskCommand getSetTaskCommand(Task task, Model model) { + SetTaskCommand command = new SetTaskCommand(task); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} +``` +###### /java/seedu/address/logic/commands/SetAppointmentCommandTest.java +``` java +public class SetAppointmentCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void constructor_nullAppointment_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new SetAppointmentCommand(null, null); + } + + @Test + public void execute_invalidPersonToMeetIndex_failure() { + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + SetAppointmentCommand command = getSetAppointmentCommand(TYPICAL_APPOINTMENT_3, outOfBoundsIndex, model); + + try { + command.execute(); + fail("The expected CommandException was not thrown."); + } catch (CommandException ce) { + assertEquals(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, ce.getMessage()); + } + } + + @Test + public void execute_appointmentWithoutPersonToMeetAccepted_addSuccessful() throws Exception { + CommandResult commandResult = + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_3, null, model).execute(); + + assertEquals(String.format( + SetAppointmentCommand.MESSAGE_SUCCESS, APPOINTMENT_WITHOUT_PERSON_3), commandResult.feedbackToUser); + } + + @Test + public void execute_appointmentWithPersonToMeetAccepted_addSuccessful() throws Exception { + CommandResult commandResult = + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_3, INDEX_THIRD, model).execute(); + + assertEquals(String.format( + SetAppointmentCommand.MESSAGE_SUCCESS, TYPICAL_APPOINTMENT_3), commandResult.feedbackToUser); + } + + @Test + public void execute_duplicateAppointmentsameIndex_throwsCommandException() throws Exception { + thrown.expect(CommandException.class); + thrown.expectMessage(SetAppointmentCommand.MESSAGE_DUPLICATE_APPOINTMENT); + + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_1, INDEX_FIRST, model).execute(); + } + + @Test + public void equals() { + SetAppointmentCommand addAppointment1 = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_1); + SetAppointmentCommand addAppointment2 = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_2); + + // same object -> returns true + assertTrue(addAppointment1.equals(addAppointment1)); + + // same values -> returns true + SetAppointmentCommand addAppointment1Copy = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_1); + assertTrue(addAppointment1.equals(addAppointment1Copy)); + + // different types -> returns false + assertFalse(addAppointment1.equals(1)); + + // null -> returns false + assertFalse(addAppointment1.equals(null)); + + // different appointments -> returns false + assertFalse(addAppointment1.equals(addAppointment2)); + } + + /** + * Generates a new SetAppointmentCommand with the details of the given appointment. + */ + private SetAppointmentCommand getSetAppointmentCommand(Appointment baseAppointment, Index index, Model model) { + SetAppointmentCommand command = new SetAppointmentCommand(baseAppointment, index); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} +``` +###### /java/seedu/address/logic/commands/ChangeThemeCommandTest.java +``` java +public class ChangeThemeCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + @Test + public void execute_help_success() throws CommandException { + String theme = "dark"; + CommandResult result = new ChangeThemeCommand(theme).execute(); + assertEquals(MESSAGE_CHANGE_THEME_SUCCESS, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ThemeChangeEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } +} +``` +###### /java/seedu/address/storage/XmlAdaptedTaskTest.java +``` java +public class XmlAdaptedTaskTest { + + private static final String INVALID_TITLE = " "; + private static final String INVALID_TIME = "not a time stamp"; + + @Test + public void toModelType_validTaskDetails_returnsPerson() throws Exception { + XmlAdaptedTask task = new XmlAdaptedTask(TYPICAL_TASK_1); + assertEquals(TYPICAL_TASK_1, task.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(INVALID_TITLE, VALID_END_TIME); + String expectedMessage = Title.MESSAGE_TITLE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidTime_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_TITLE, INVALID_TIME); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(null, VALID_END_TIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullTime_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_TITLE, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void equals() { + XmlAdaptedTask task = new XmlAdaptedTask(TYPICAL_TASK_1); + + //same object + assertTrue(task.equals(task)); + + //same value + XmlAdaptedTask taskCopy = new XmlAdaptedTask(TYPICAL_TASK_1); + assertTrue(task.equals(taskCopy)); + + //different type + assertFalse(task.equals(TYPICAL_TASK_1)); + + //different obj + XmlAdaptedTask anotherTask = new XmlAdaptedTask(TYPICAL_TASK_2); + assertFalse(task.equals(anotherTask)); + } +} +``` +###### /java/seedu/address/storage/XmlAdaptedAppointmentTest.java +``` java +public class XmlAdaptedAppointmentTest { + + private static final String INVALID_TITLE = " "; + private static final String VALID_PERSON_TO_MEET = "Alice Email: alice@gmail.com"; + private static final String INVALID_TIME = "not a time stamp"; + + @Test + public void toModelType_validAppointmentDetails_returnsPerson() throws Exception { + XmlAdaptedAppointment appointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + assertEquals(TYPICAL_APPOINTMENT_1, appointment.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(INVALID_TITLE, VALID_START_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = Title.MESSAGE_TITLE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidStartTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, INVALID_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidStartEndTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, VALID_START_TIME, INVALID_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(null, VALID_START_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullStartTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, null, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Start Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullEndTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, VALID_START_TIME, null, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "End Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidTimePeriod_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, "20/10/2018 10:00", "20/10/2018 09:00"); + String expectedMessage = Appointment.MESSAGE_TIME_PERIOD_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void equals() { + XmlAdaptedAppointment appointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + + //same object + assertTrue(appointment.equals(appointment)); + + //same value + XmlAdaptedAppointment appointmentCopy = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + assertTrue(appointment.equals(appointmentCopy)); + + //different type + assertFalse(appointment.equals(TYPICAL_APPOINTMENT_1)); + + //different obj + XmlAdaptedAppointment anotherAppointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_2); + assertFalse(appointment.equals(anotherAppointment)); + } +} +``` +###### /java/seedu/address/model/event/AppointmentTest.java +``` java +public class AppointmentTest { + private static final Title VALID_TITLE = new Title("Meet Student"); + private static final EventTime VALID_START_TIME = new EventTime("05/04/2018 10:00"); + private static final EventTime VALID_END_TIME = new EventTime("05/04/2018 11:00"); + private static final EventTime INVALID_END_TIME = new EventTime("05/04/2018 09:00"); + + @Test + public void constructor_invalidAppointmentTime_throwsIllegalArgumentException() { + Assert.assertThrows(IllegalArgumentException.class, () -> + new Appointment(VALID_TITLE, VALID_START_TIME, INVALID_END_TIME)); + } + + @Test + public void isValidTime() { + // invalid time stamps + assertFalse(Appointment.isValidTime(VALID_START_TIME, INVALID_END_TIME)); //End time is before Start Time + + // valid time stamps + assertTrue(Appointment.isValidTime(VALID_START_TIME, VALID_END_TIME)); + } +} +``` +###### /java/seedu/address/testutil/modelstub/ModelStubThrowingDuplicateEventException.java +``` java +/** + * A Model stub that always throw a DuplicateEventException when trying to add an appointment/task. + */ +public class ModelStubThrowingDuplicateEventException extends ModelStub { + @Override + public void addAppointment (Appointment appointment) throws DuplicateEventException { + throw new DuplicateEventException(); + } + + @Override + public void addTask (Task task) throws DuplicateEventException { + throw new DuplicateEventException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } +} +``` +###### /java/seedu/address/testutil/modelstub/ModelStubAcceptingTaskAdded.java +``` java +/** + * A Model stub that always accept the task being added. + */ +public class ModelStubAcceptingTaskAdded extends ModelStub { + public final ArrayList<Task> tasksAdded = new ArrayList<>(); + + @Override + public void addTask(Task event) throws DuplicateEventException { + requireNonNull(event); + tasksAdded.add(event); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } +} +``` +###### /java/seedu/address/testutil/AppointmentBuilder.java +``` java +/** + * A utility class to help with building Appointment objects. + */ +public class AppointmentBuilder { + private Title title; + private EventTime time; + private EventTime endTime; + private PersonToMeet personToMeet; + + public AppointmentBuilder(String title, String time, String endTime) { + this(title, time, endTime, (String) null); + } + + public AppointmentBuilder(String title, String time, String endTime, Person personToMeet) { + this(title, time, endTime, personToMeet.getName() + EMAIL_SPLITTER + personToMeet.getEmail()); + } + + public AppointmentBuilder(String title, String time, String endTime, String personToMeet) { + this.title = new Title(title); + this.time = new EventTime(time); + this.endTime = new EventTime(endTime); + if (personToMeet != null) { + String[] components = personToMeet.split(EMAIL_SPLITTER); + this.personToMeet = new PersonToMeet(components[0], components[1]); + } + } + + /** + * @return an {@code Appointment} from the data feed to constructor + */ + public Appointment build() { + return new Appointment(title, time, endTime, personToMeet); + } +} +``` +###### /java/seedu/address/testutil/TypicalEvents.java +``` java +/** + * A utility class containing a list of event objects to be used in tests. + */ +public class TypicalEvents { + public static final Appointment TYPICAL_APPOINTMENT_1 = + new AppointmentBuilder("Meeting with parents", "09/10/2018 10:00", "09/10/2018 11:00", ALICE).build(); + public static final Appointment TYPICAL_APPOINTMENT_2 = + new AppointmentBuilder("Consultation session", "04/07/2018 10:00", "04/07/2018 11:00", BOB).build(); + public static final Appointment TYPICAL_APPOINTMENT_3 = + new AppointmentBuilder("Tutoring session", "30/04/2018 10:00", "30/04/2018 11:00", CARL).build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_1 = + new AppointmentBuilder("Meeting with parents", "09/10/2018 10:00", "09/10/2018 11:00").build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_2 = + new AppointmentBuilder("Consultation session", "04/07/2018 10:00", "04/07/2018 11:00", BOB).build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_3 = + new AppointmentBuilder("Tutoring session", "30/04/2018 10:00", "30/04/2018 11:00").build(); + + + public static final Task TYPICAL_TASK_1 = + new Task(new Title("To do"), new EventTime("10/10/2018 10:00")); + public static final Task TYPICAL_TASK_2 = + new Task(new Title("Mark papers"), new EventTime("15/04/2018 23:00")); + public static final Task TYPICAL_TASK_3 = + new Task(new Title("Purchase markers"), new EventTime("19/04/2018 10:00")); + public static final Task TYPICAL_TASK_EXPIRED = + new Task(new Title("Expired task"), new EventTime("19/04/2013 10:00")); + + public static List<Appointment> getTypicalAppointments() { + return new ArrayList<>(Arrays.asList(TYPICAL_APPOINTMENT_1, TYPICAL_APPOINTMENT_2)); + } + public static List<Task> getTypicalTasks() { + return new ArrayList<>(Arrays.asList(TYPICAL_TASK_1, TYPICAL_TASK_2)); + } +} +``` diff --git a/collated/test/shanmu9898.md b/collated/test/shanmu9898.md new file mode 100644 index 000000000000..834409284f75 --- /dev/null +++ b/collated/test/shanmu9898.md @@ -0,0 +1,1226 @@ +# shanmu9898 +###### /java/seedu/address/ui/ShortcutCardTest.java +``` java +//package seedu.address.ui; +// +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertTrue; +//import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_3; +//import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_5; +// +//import org.junit.Test; +// +//import seedu.address.model.shortcuts.ShortcutDoubles; +// +//public class ShortcutCardTest { +// +// +// @Test +// public void equals() { +// ShortcutDoubles shortcutDoubles = SHORTCUT_DOUBLES_5; +// //ShortcutCard shortcutCard = new ShortcutCard(shortcutDoubles, 0); +// +// // same shortcut, same index -> returns true +// ShortcutCard copy = new ShortcutCard(shortcutDoubles, 0); +// assertTrue(shortcutCard.equals(copy)); +// +// // same object -> returns true +// assertTrue(shortcutCard.equals(shortcutCard)); +// +// // null -> returns false +// assertFalse(shortcutCard.equals(null)); +// +// // different types -> returns false +// assertFalse(shortcutCard.equals(0)); +// +// // different shortcut, same index -> returns false +// ShortcutDoubles differentshortcut = SHORTCUT_DOUBLES_3; +// assertFalse(shortcutCard.equals(new ShortcutCard(differentshortcut, 0))); +// +// // same shortcut, different index -> returns false +// assertFalse(shortcutCard.equals(new ShortcutCard(shortcutDoubles, 1))); +// } +//} +``` +###### /java/seedu/address/logic/parser/ImportCommandParserTest.java +``` java +public class ImportCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ImportCommandParser importCommandParser = new ImportCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + importCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + importCommandParser.parse(" "); + + } + + @Test + public void parse_moreThanOneBlockOfString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + importCommandParser.parse("invalid two strings"); + } + + @Test + public void parse_validString_success() { + String input = "./src/test/data/XmlAddressBookStorgageTest/importsamplefile.xml"; + ImportCommand expectedCommand = new ImportCommand(input); + assertParseSuccess(importCommandParser, input, expectedCommand); + } + + + + +} +``` +###### /java/seedu/address/logic/parser/AddressBookParserTest.java +``` java + @Test + public void parseCommand_export() throws Exception { + ExportCommand command = (ExportCommand) parser.parseCommand( + ExportCommand.COMMAND_WORD + " " + PREFIX_NAME + NAME_NEEDED + " " + PREFIX_RANGE + RANGE_ALL + + " " + PREFIX_TAG_EXPORT + TAG_NEEDED + " " + PREFIX_PATH + PATH_NEEDED + " " + PREFIX_TYPE + + TYPE_NEEDED); + assertEquals (new ExportCommand ("all", new Tag ("friends"), "./data", + "name", "normal"), command); + } + + @Test + public void parseCommand_import() throws Exception { + ImportCommand command = (ImportCommand) parser.parseCommand( + ImportCommand.COMMAND_WORD + " " + + "src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"); + assertEquals(new ImportCommand("src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"), + command); + } + + @Test + public void parseCommand_shortcut() throws Exception { + ShortcutCommand command = (ShortcutCommand) parser.parseCommand( + ShortcutCommand.COMMAND_WORD + " " + "list" + " " + "l"); + assertEquals(new ShortcutCommand("list", "l"), command); + } + + @Test + public void parseCommand_deleteShortcut() throws Exception { + DeleteShortcutCommand command = (DeleteShortcutCommand) parser.parseCommand( + DeleteShortcutCommand.COMMAND_WORD + " " + "list" + " " + "l"); + assertEquals(new DeleteShortcutCommand("list", "l"), command); + } +``` +###### /java/seedu/address/logic/parser/DeleteShortcutCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.DeleteShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class DeleteShortcutCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DeleteShortcutCommandParser deleteShortcutCommandParser = new DeleteShortcutCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + deleteShortcutCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + deleteShortcutCommandParser.parse(" "); + + } + + @Test + public void parse_validString_success() { + String inputCommandWord = "list"; + String inputShortcutWord = "l"; + String input = "list l"; + DeleteShortcutCommand expectedCommand = new DeleteShortcutCommand(inputCommandWord, inputShortcutWord); + assertParseSuccess(deleteShortcutCommandParser, input, expectedCommand); + } +} +``` +###### /java/seedu/address/logic/parser/ExportCommandParserTest.java +``` java +public class ExportCommandParserTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ExportCommandParser exportCommandParser = new ExportCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + exportCommandParser.parse(null); + } + + @Test + public void parse_differentType_invalidFormatCommandResult() throws ParseException { + Tag testingTag = new Tag("shouldnotbethistag"); + String testingInput = " n/name r/all p/./data te/random"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "normal"); + assertParseFailure(exportCommandParser, testingInput, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ExportCommand.MESSAGE_USAGE)); + + } + + @Test + public void parse_optionalFieldsMissing_success() { + Tag testingTag = new Tag("shouldnotbethistag"); + String testingInput = " n/name r/all p/./data te/normal"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "normal"); + assertParseSuccess(exportCommandParser, testingInput, expectedCommand); + } + + + + @Test + public void parse_allfieldsPresent_success() { + Tag testingTag = new Tag("friends"); + String testingInput = " n/name r/all t/friends p/./data te/normal"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "normal"); + assertParseSuccess(exportCommandParser, testingInput, expectedCommand); + } + +} +``` +###### /java/seedu/address/logic/parser/ShortcutCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.ShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class ShortcutCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ShortcutCommandParser shortcutCommandParser = new ShortcutCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + shortcutCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + shortcutCommandParser.parse(" "); + + } + + @Test + public void parse_validString_success() { + String inputCommandWord = "list"; + String inputShortcutWord = "l"; + String input = "list l"; + ShortcutCommand expectedCommand = new ShortcutCommand(inputCommandWord, inputShortcutWord); + assertParseSuccess(shortcutCommandParser, input, expectedCommand); + } +} +``` +###### /java/seedu/address/logic/commands/ImportCommandTest.java +``` java +public class ImportCommandTest { + + + private static final String INVALID_FILE_LOCATION = "./data/samplefile.xml"; + private static final String VALID_FILE_LOCATION = + "src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullString_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ImportCommand(null); + } + + @Test + public void execute_importFailure_throwsException() { + ImportCommand command = prepareCommand(INVALID_FILE_LOCATION); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandFailure(command, model, String.format(command.MESSAGE_INVALID_FILE)); + } + + @Test + public void execute_acceptedSuccess_successfulImport() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + ClearCommand clearCommand = new ClearCommand(); + clearCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + clearCommand.executeUndoableCommand(); + + ImportCommand command = prepareCommand(VALID_FILE_LOCATION); + + assertCommandSuccess(command, model, String.format (command.MESSAGE_SUCCESS, "7", "0"), model); + } + + @Test + public void equals() { + final ImportCommand comparableCommand = new ImportCommand(VALID_FILE_LOCATION); + + // same values -> returns true + ImportCommand comparedToCommand = new ImportCommand(VALID_FILE_LOCATION); + assertTrue(comparableCommand.equals(comparedToCommand)); + + // same object -> returns true + assertTrue(comparableCommand.equals(comparableCommand)); + + // null -> returns false + assertFalse(comparableCommand.equals(null)); + + // different types -> returns false + assertFalse(comparableCommand.equals(new ClearCommand())); + + // different range -> returns false + assertFalse(comparableCommand.equals(new ImportCommand("./data/sampleimportfile.xml"))); + } + + + /** + * A method to prepare the Import command based on the path given + */ + private ImportCommand prepareCommand(String path) { + ImportCommand importCommand = new ImportCommand(path); + importCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + return importCommand; + } + +} +``` +###### /java/seedu/address/logic/commands/DeleteShortcutCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class DeleteShortcutCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + private Model expectedModel; + private final String validtestingCommandWord = "list"; + private final String validtestingShortcutWord = "l"; + private final String invalidtestingCommandWord = "king"; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new DeleteShortcutCommand(null, validtestingShortcutWord); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new DeleteShortcutCommand(validtestingCommandWord, null); + } + + @Test + public void executeUndoCommand_invalidCommandWord_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + CommandResult commandResult = getDeleteShortcutForCommand(validtestingShortcutWord, + invalidtestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_inputNotPresent_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + CommandResult commandResult = getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_validInput_commandSuccess() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validtestingShortcutWord, validtestingCommandWord); + model.addCommandShortcut(shortcutDoubles); + assertCommandSuccess(getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model), + model, + String.format(DeleteShortcutCommand + .MESSAGE_DELETE_SHORTCUT_SUCCESS), + expectedModel); + } + + @Test + public void equals() throws Exception { + DeleteShortcutCommand deleteFirstCommand = getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model); + DeleteShortcutCommand deleteSecondCommand = getDeleteShortcutForCommand("c", + "clear", + model); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteShortcutCommand deleteFirstCommandCopy = getDeleteShortcutForCommand("l", + "list", + model); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Generates a new AddCommand with the details of the given person. + */ + private DeleteShortcutCommand getDeleteShortcutForCommand(String shortcutWord, String commandWord, Model model) { + DeleteShortcutCommand command = new DeleteShortcutCommand(commandWord, shortcutWord); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### /java/seedu/address/logic/commands/ListCommandTest.java +``` java + @Test + public void execute_listShortcut_success() throws CommandException { + listCommand = new ListCommand(TYPE_SHORTCUT); + listCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + CommandResult result = listCommand.execute(); + assertEquals(MESSAGE_SUCCESS + TYPE_SHORTCUT, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleListEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } +} +``` +###### /java/seedu/address/logic/commands/ShortcutCommandTest.java +``` java +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class ShortcutCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + private Model expectedModel; + private final String validTestingCommandWord = "list"; + private final String validTestingShortcutWord = "l"; + private final String invalidTestingCommandWord = "king"; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutCommand(null, validTestingShortcutWord); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutCommand(validTestingCommandWord, null); + } + + @Test + public void executeUndoCommand_invalidCommandWord_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + thrown.expectMessage(ShortcutCommand.MESSAGE_NO_COMMAND_TO_MAP); + + CommandResult commandResult = getAddShortcutForCommand(validTestingShortcutWord, + invalidTestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_shortcutWordPresent_throwsCommandException() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException, CommandException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validTestingShortcutWord, validTestingCommandWord); + model.addCommandShortcut(shortcutDoubles); + CommandResult commandResult = getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model).executeUndoableCommand(); + assertEquals(commandResult.feedbackToUser, String.format(ShortcutCommand.MESSAGE_SHORTCUT_AVAILABLE)); + } + + @Test + public void executeUndoCommand_validInput_commandSuccess() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validTestingShortcutWord, validTestingCommandWord); + expectedModel.addCommandShortcut(shortcutDoubles); + assertCommandSuccess(getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model), model, + String.format(ShortcutCommand.MESSAGE_SUCCESS), expectedModel); + } + + /** + * Generates a new AddCommand with the details of the given person. + */ + private ShortcutCommand getAddShortcutForCommand(String shortcutWord, String commandWord, Model model) { + ShortcutCommand command = new ShortcutCommand(commandWord, shortcutWord); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + @Test + public void equals() throws Exception { + ShortcutCommand deleteFirstCommand = getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model); + ShortcutCommand deleteSecondCommand = getAddShortcutForCommand("c", + "clear", + model); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + ShortcutCommand deleteFirstCommandCopy = getAddShortcutForCommand("l", + "list", + model); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + +} +``` +###### /java/seedu/address/logic/commands/ExportCommandTest.java +``` java +public class ExportCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final Tag testingTag = new Tag("testingTag"); + private final String testingPath = "./test/data/XmlAddressBookStorageTest"; + private final String name = "testingName"; + private final String testingRange = "1,5"; + private final String fileTypeNormal = "normal"; + private final String fileTypeExcel = "excel"; + + + @Test + public void constructor_nullRange_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(null, testingTag, testingPath, name, fileTypeExcel); + } + + @Test + public void constructor_nullPath_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, null, name, fileTypeNormal); + } + + @Test + public void constructor_nullName_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, testingPath, null, fileTypeNormal); + } + + @Test + public void constructor_nullType_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, testingPath, name, null); + } + + @Test + public void execute_multipleRange_showsMessageError() { + String testingMultiRange = "1,2,3"; + ExportCommand exportCommand = new ExportCommand(testingMultiRange, testingTag, testingPath, + name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + + } + + @Test + public void execute_outOfRange_showsMessageError() { + String testingOutOfRange = "0,10000000"; + ExportCommand exportCommand = new ExportCommand(testingOutOfRange, testingTag, testingPath, + name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_OUT_OF_BOUNDS), model); + + } + + @Test + public void execute_successfulExport_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand(testingRange, testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithAllRange_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("all", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithSingleRange_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("2", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithExcel_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("1,6", testingTag, testingPath, name, fileTypeExcel); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_rangeNotCorrect_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("2,1", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGiven_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("all", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGivnAndRangeError_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("2,1", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGivenAndRangeGiven_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand(testingRange, new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + + + @Test + public void execute_whenRangeIsSelectiveAndOutOfRange_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("10000000", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_OUT_OF_BOUNDS), model); + } + + + + @Test + public void equals() { + final ExportCommand comparableCommand = new ExportCommand(testingRange, testingTag, testingPath, + name, fileTypeNormal); + + // same values -> returns true + ExportCommand comparedToCommand = new ExportCommand(testingRange, testingTag, testingPath, + name, fileTypeNormal); + assertTrue(comparableCommand.equals(comparedToCommand)); + + // same object -> returns true + assertTrue(comparableCommand.equals(comparableCommand)); + + // null -> returns false + assertFalse(comparableCommand.equals(null)); + + // different types -> returns false + assertFalse(comparableCommand.equals(new ClearCommand())); + + // different range -> returns false + assertFalse(comparableCommand.equals(new ExportCommand("1,2", testingTag, testingPath, name, + fileTypeNormal))); + } + + + +} +``` +###### /java/seedu/address/model/shortcut/ShortcutDoubleTest.java +``` java +package seedu.address.model.shortcut; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +public class ShortcutDoubleTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutDoubles("l", null); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutDoubles(null , "list"); + } + + @Test + public void equals() throws Exception { + ShortcutDoubles deleteFirstCommand = new ShortcutDoubles("l", "list"); + ShortcutDoubles deleteSecondCommand = new ShortcutDoubles("c", "clear"); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + ShortcutDoubles deleteFirstCommandCopy = new ShortcutDoubles("l", "list"); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } +} +``` +###### /java/seedu/address/model/UniqueShortcutDoublesListTest.java +``` java +package seedu.address.model; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class UniqueShortcutDoublesListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueShortcutDoublesList uniqueShortcutDoublesList = new UniqueShortcutDoublesList(); + thrown.expect(UnsupportedOperationException.class); + uniqueShortcutDoublesList.asObservableList().remove(0); + } +} +``` +###### /java/seedu/address/model/AddressBookTest.java +``` java + @Test + public void getShortcutList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getCommandsList().remove(0); + } +``` +###### /java/seedu/address/model/AddressBookTest.java +``` java + + /** + * A stub ReadOnlyAddressBook whose persons, tags and events lists can violate interface constraints. + */ + private static class AddressBookStub implements ReadOnlyAddressBook { + private final ObservableList<Person> persons = FXCollections.observableArrayList(); + private final ObservableList<Student> students = FXCollections.observableArrayList(); + private final ObservableList<Person> contacts = FXCollections.observableArrayList(); + private final ObservableList<Tag> tags = FXCollections.observableArrayList(); + private final ObservableList<Appointment> appointments = FXCollections.observableArrayList(); + private final ObservableList<Task> tasks = FXCollections.observableArrayList(); + private final ObservableList<ShortcutDoubles> commandslist = FXCollections.observableArrayList(); + + AddressBookStub(Collection<Person> persons, Collection<Student> students, + Collection<? extends Tag> tags, Collection<Appointment> appointments, + Collection<Task> tasks, Collection<ShortcutDoubles> commands) { + this.persons.setAll(persons); + this.students.setAll(students); + this.contacts.setAll(persons); + this.contacts.addAll(students); + this.tags.setAll(tags); + this.tasks.setAll(tasks); + this.appointments.setAll(appointments); + this.commandslist.setAll(commands); + } + + @Override + public ObservableList<Person> getPersonList() { + return persons; + } + + @Override + public ObservableList<Student> getStudentList() { + return students; + } + + @Override + public ObservableList<Person> getContactList() { + return contacts; + } + + @Override + public ObservableList<Tag> getTagList() { + return tags; + } + + @Override + public ObservableList<Appointment> getAppointmentList() { + return appointments; + } + + @Override + public ObservableList<Task> getTaskList() { + return tasks; + } + + @Override + public ObservableList<ShortcutDoubles> getCommandsList() { + return commandslist; + } + } + + @Test + public void updatePerson_modifiedAddressBooks_noError() throws PersonNotFoundException, DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).build(); + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(AMY).build(); + + testAddressBook.updatePerson(BOB, AMY); + + assertEquals(testAddressBook, expectedAddressBook); + } + +``` +###### /java/seedu/address/model/AddressBookTest.java +``` java + @Test + public void removeTag_tagNotPresent_addressBookUnchanged() throws PersonNotFoundException, + DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + + testAddressBook.removeTag(new Tag(VALID_TAG_NOTUSED)); + + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + + assertEquals(expectedAddressBook, testAddressBook); + } + + @Test + public void removeTag_tagUsedByMultiplePeople_tagRemoved() throws PersonNotFoundException, + DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + testAddressBook.removeTag(new Tag(VALID_TAG_FRIEND)); + + Person amyWithoutFriendTag = new PersonBuilder(AMY).withTags().build(); + Person bobWithoutFriendTag = new PersonBuilder(BOB).withTags(VALID_TAG_HUSBAND).build(); + + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(bobWithoutFriendTag) + .withPerson(amyWithoutFriendTag).build(); + + assertEquals(expectedAddressBook, testAddressBook); + } +``` +###### /java/seedu/address/model/ModelManagerTest.java +``` java + @Test + public void getFilteredCommandList_modifyList_throwsUnsupportedOperationException() { + ModelManager modelManager = new ModelManager(); + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredCommandsList().remove(0); + } +``` +###### /java/seedu/address/model/ModelManagerTest.java +``` java + @Test + public void addShortcut_addShortcutToAddressBook_evokeAddressBookChangedEvent() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.addCommandShortcut(SHORTCUT_DOUBLES_1); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } +``` +###### /java/seedu/address/model/ModelManagerTest.java +``` java + + @Test + public void equals() { + AddressBook differentAddressBook = new AddressBook(); + + // same values -> returns true + ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); + assertTrue(modelManager.equals(modelManagerCopy)); + + // same object -> returns true + assertTrue(modelManager.equals(modelManager)); + + // null -> returns false + assertFalse(modelManager.equals(null)); + + // different types -> returns false + assertFalse(modelManager.equals(5)); + + // different addressBook -> returns false + assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); + + // different filteredList -> returns false + String[] keywords = ALICE.getName().fullName.split("\\s+"); + modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + + // resets modelManager to initial state for upcoming tests + modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // different userPrefs -> returns true + UserPrefs differentUserPrefs = new UserPrefs(); + differentUserPrefs.setAddressBookName("differentName"); + assertTrue(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); + } +``` +###### /java/seedu/address/model/ModelManagerTest.java +``` java + @Test + public void deleteTag_tagNotPresent_modelUnchanged() throws DuplicatePersonException, PersonNotFoundException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(AMY).withPerson(BOB).build(); + UserPrefs userPrefs = new UserPrefs(); + ModelManager modelManager = new ModelManager(testAddressBook, userPrefs); + modelManager.deleteTag(new Tag(VALID_TAG_NOTUSED)); + + assertEquals(new ModelManager(testAddressBook, userPrefs), modelManager); + } + + + @Test + public void deleteTag_tagUsedByMultiplePeople_tagRemoved() throws DuplicatePersonException, + PersonNotFoundException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(AMY).withPerson(BOB).build(); + ModelManager modelManager = new ModelManager(testAddressBook, userPrefs); + modelManager.deleteTag(new Tag(VALID_TAG_FRIEND)); + + Person amyWithoutFriendTag = new PersonBuilder(AMY).withTags().build(); + Person bobWithoutFriendTag = new PersonBuilder(BOB).withTags(VALID_TAG_HUSBAND).build(); + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(amyWithoutFriendTag) + .withPerson(bobWithoutFriendTag).build(); + + assertEquals(new ModelManager(expectedAddressBook, userPrefs), modelManager); + } +``` +###### /java/seedu/address/testutil/ExportCommandHelper.java +``` java +/** + * A utility class containing a list of {@code Index} objects to be used in tests. + */ +public class ExportCommandHelper { + public static final String RANGE_ALL = "all"; + public static final String TAG_NEEDED = "friends"; + public static final String PATH_NEEDED = "./data"; + public static final String NAME_NEEDED = "name"; + public static final String TYPE_NEEDED = "normal"; + + +} +``` +###### /java/seedu/address/testutil/TypicalImportedPersons.java +``` java +package seedu.address.testutil; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_STUDENT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.person.exceptions.DuplicatePersonException; + +``` +###### /java/seedu/address/testutil/TypicalImportedPersons.java +``` java +/** + * Special Util class to get people for ImportCommandTest class. + */ +public class TypicalImportedPersons { + public static final Person FLICE = new PersonBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").build(); + public static final Person FENSON = new PersonBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").build(); + public static final Person FARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").build(); + public static final Person FANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").build(); + public static final Person FLLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").build(); + public static final Person GIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").build(); + public static final Student FEORGE = new StudentBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").build(); + public static final Student FVAN = new StudentBuilder().withName("Ivan Kutz").withPhone("9867723") + .withEmail("wolf@example.com").withAddress("Centre Street").build(); + public static final Student FOHN = new StudentBuilder().withName("John Blake").withPhone("9575232") + .withEmail("star@example.com").withAddress("Hollywood").build(); + + // Manually added + public static final Person FOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Person FDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + public static final Student FTUDENT_HOON = new StudentBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Student FTUDENT_IDA = new StudentBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Person FMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + public static final Person FOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .build(); + + public static final Student FTUDENT_AMY = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + public static final Student FTUDENT_BOB = new StudentBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_STUDENT) + .build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalImportedPersons() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical persons. + */ + public static AddressBook getImportedAddressBook() { + AddressBook ab = new AddressBook(); + for (Person person : getImportedPersons()) { + try { + ab.addPerson(person); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List<Person> getImportedPersons() { + return new ArrayList<>(Arrays.asList(FLICE, FENSON, FARL, FANIEL, FLLE, GIONA, FEORGE)); + } +} +``` +###### /java/seedu/address/testutil/TypicalShortcuts.java +``` java +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * Few Typical Shortcuts + */ +public class TypicalShortcuts { + public static final ShortcutDoubles SHORTCUT_DOUBLES_1 = + new ShortcutCommandBuilder("l", "list").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_2 = + new ShortcutCommandBuilder("c", "clear").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_3 = + new ShortcutCommandBuilder("ll", "list").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_4 = + new ShortcutCommandBuilder("cc", "clear").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_5 = + new ShortcutCommandBuilder("a", "add").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_6 = + new ShortcutCommandBuilder("aa", "add").build(); + + public static List<ShortcutDoubles> getTypicalShortcuts() { + return new ArrayList<>(Arrays.asList(SHORTCUT_DOUBLES_1, SHORTCUT_DOUBLES_2)); + } + +} +``` +###### /java/seedu/address/testutil/ShortcutCommandBuilder.java +``` java +package seedu.address.testutil; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * A utility class to help with building Shortcut objects. + */ +public class ShortcutCommandBuilder { + + private String shortcutWord; + private String commandWord; + + public ShortcutCommandBuilder(String shortcutWord, String commandWord) { + this.commandWord = commandWord; + this.shortcutWord = shortcutWord; + } + + + /** + * @return an {@code Appointment} from the data feed to constructor + */ + public ShortcutDoubles build() { + return new ShortcutDoubles(shortcutWord, commandWord); + } +} +``` +###### /java/seedu/address/testutil/modelstub/ModelStub.java +``` java + @Override + public void addCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + fail("This method should not be called."); + } + + @Override + public void deleteCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + fail("This method should not be called"); + } +``` diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce25..7eee31a8ddfb 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,7 +1,7 @@ Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob Copyright by Susumu Yoshida - http://www.mcdodesign.com/ -- address_book_32.png +- Tc_logo.png - AddressApp.ico Copyright by Jan Jan Kovařík - http://glyphicons.com/ diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..df3688efa5e1 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,52 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +TeachConnect was developed by a team of students, with assistance from the Project Supervisor, as a project +for the module CS2103T. -== Project Team +It was built upon the AddressBook - Level 4 project developed by the https://se-edu.github.io/docs/Team.html[se-edu] +team. -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<<johndoe#, portfolio>>] +We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. -Role: Project Advisor +== Project Team -''' +=== Mukesh Gadupudi +image::shanmu9898.jpg[width="150", align="left"] +{empty}[https://github.com/shanmu9898/main/blob/DocsUpdate/docs/team/MukeshGadupudi.adoc[portfolio]] [https://github.com/shanmu9898[github]] -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<<johndoe#, portfolio>>] +Role: Team Lead, -Role: Team Lead + -Responsibilities: UI +Responsibilities: Model + Deliverable and Deadlines + Integration + Tools Expert + Storage ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<<johndoe#, portfolio>>] +=== Rachel Ngo Phuong Thao +image::sisyphus.jpg[width="150", align="left"] +{empty}[http://github.com/Sisyphus[github]] [<<johndoe#, portfolio>>] + +Role: Developer -Role: Developer + -Responsibilities: Data +Responsibilities: UI + Storage + Scheduling and Tracking + Documentation + Reviewing ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<<johndoe#, portfolio>>] +=== Randy Pang Chung +image::randypx.jpg[width="150", align="left"] +{empty}[http://github.com/randypx[github]] [<<johndoe#, portfolio>>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Developer + +Responsibilities: Logic + Testing + Reviewing + Design Consideration and Implementation ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<<johndoe#, portfolio>>] +=== Jonathan Lim +image::LimShiMinJonathan.jpg[width="150", align="left"] +{empty}[http://github.com/LimShiMinJonathan[github]] [<<johndoe#, portfolio>>] + +Role: Developer -Role: Developer + -Responsibilities: UI +Responsibilities: Documentation ''' diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..be4e1d26dd33 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += TeachConnect - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,9 +10,11 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +:repoURL: https://github.com/CS2103JAN2018-W14-B1/main/tree/master + + +By: `Team W14-B1` Since: `Jan 2018` Licence: `MIT` == Setting up @@ -66,10 +68,6 @@ This project follows https://github.com/oss-generic/process/blob/master/docs/Cod Optionally, you can follow the <<UsingCheckstyle#, UsingCheckstyle.adoc>> document to configure Intellij to check style-compliance as you write code. -==== Updating documentation to match your fork - -After forking the repo, links in the documentation will still point to the `se-edu/addressbook-level4` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `se-edu/addressbook-level4`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. - ==== Setting up CI Set up Travis to perform Continuous Integration (CI) for your fork. See <<UsingTravis#, UsingTravis.adoc>> to learn how to set it up. @@ -140,7 +138,7 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `AddressBookChangedEvent` when the TeachConnect data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. @@ -204,7 +202,7 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. +* stores TeachConnect data. * exposes an unmodifiable `ObservableList<Person>` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. @@ -219,7 +217,7 @@ image::StorageClassDiagram.png[width="800"] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save TeachConnect data in xml format and read it back. [[Design-Commons]] === Common classes @@ -234,13 +232,13 @@ This section describes some noteworthy details on how certain features are imple === Undo/Redo feature ==== Current Implementation -The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of the address book (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. +The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of TeachConnect (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. `UndoRedoStack` only deals with `UndoableCommands`. Commands that cannot be undone will inherit from `Command` instead. The following diagram shows the inheritance diagram for commands: image::LogicCommandClassDiagram.png[width="800"] -As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the address book before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. +As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of TeachConnect before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. Commands that are not undoable are implemented this way: [source,java] @@ -275,7 +273,7 @@ public class DeleteCommand extends UndoableCommand { Suppose that the user has just launched the application. The `UndoRedoStack` will be empty at the beginning. -The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th person in the address book. The current state of the address book is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). +The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th person in TeachConnect. The current state of TeachConnect is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). image::UndoRedoStartingStackDiagram.png[width="800"] @@ -288,7 +286,7 @@ If a command fails its execution, it will not be pushed to the `UndoRedoStack` a The user now decides that adding the person was a mistake, and decides to undo that action using `undo`. -We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the address book to the state before the `add` command executed. +We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore TeachConnect to the state before the `add` command executed. image::UndoRedoExecuteUndoStackDiagram.png[width="800"] @@ -299,7 +297,7 @@ The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the address book to the state after the command is executed). +The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores TeachConnect to the state after the command is executed). [NOTE] If the `redoStack` is empty, then there are no other commands left to be redone, and an `Exception` will be thrown when popping the `redoStack`. @@ -339,7 +337,7 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: Type of commands that can be undone/redone -* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `clear`, `edit`). +* **Alternative 1 (current choice):** Only include commands that modifies TeachConnect (`add`, `clear`, `edit`). ** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). ** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. * **Alternative 2:** Include all commands. @@ -357,8 +355,390 @@ image::UndoRedoActivityDiagram.png[width="650"] ** Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase. ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. // end::undoredo[] +// tag::sort[] +=== Sorting mechanism + +The `SortCommand` uses a `SortedList` that is initialised with the current `filteredPerson` and is sorted using a comparator. Below is the code snippet: + +[source,java] +---- +sortedfilteredPersons = new SortedList<>(filteredPersons); + + public void sortFilteredPersonList(){ + + Comparator<Person> sortByName = new Comparator<Person>() { + @Override + public int compare(Person o1, Person o2) { + return o1.getName().fullName.compareTo(o2.getName().fullName); + } + }; + + sortedFilteredPersons.setComparator(sortByName); + indicateAddressBookChanged(); + } +---- +// end::sort[] +// tag::import[] +=== Import Contacts +==== Current Implementation +The ImportCommand uses `XmlAddressBookStorage` to generate a temporary `AddressBook` object from a given path. It takes in a String value path. The command then adds the contacts found in the temporary `AddressBook` object into the main address book object. Below is the rough idea of the constructor for the class: +[source,java] +public ImportCommand(String importPath) { + requireNonNull(importPath); + this.filePath = importPath; + addressBookStorage = new XmlAddressBookStorage(filePath); +} + +image::ImportCommandFlowChart.png[align="center"] + +Import command extends `Undoable Command` and hence Undo can be called on it. It initially checks if the given file path is valid and if so initialised the contacts from there, creates a `Person` object and adds it to the current `TeachConnect` with the help of `model`. The code can be found below. +[source, java] +---- +public CommandResult executeUndoableCommand() throws CommandException { + ObservableList<Person> people = addressBookImported.getPersonList(); + for (int i = 0; i < people.size(); i++) { + try { + model.addPerson(people.get(i)); + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + } + return new CommandResult(MESSAGE_SUCCESS); +} +---- + +==== Design Considerations +===== Aspects : Implementation Import Command + +* **Alternative 1 (current choice):** Only import from an `XML` file. +** Pros: Goes well with the idea of TeachConnect. Easier to implement and also clear distinction of the file that needs to be imported by the user with the help of the .XML extension. +** Cons: User might want to import from Excel only to realise this isn't possible. +* **Alternative 2:** Include import from an `Excel` file too. +** Pros: Might be more intuitive for the user and might come in handy. +** Cons: Complex implementation ther by giving rise to more bugs and slower response rate. + +// tag::export[] +=== Export contacts +==== Current Implementation + +The `ExportCommand` uses `XmlAddressBookStorage` class to generate a xml file based on a given range/index/tag and save it to the location specified with the chosen file name. It takes in String `name` String `range` Tag `tag` String `path` String `type`. The tag is not compulsory and can be excluded or included depending on the user. Below is the basic idea of the constructor for the class: + +[source,java] +---- +ExportCommand(String range, Tag tag, String path, String nameOfExportFile, String type) { + this.range = range; + this.path = path; + thispublic.tag = tag; + this.nameOfExportFile = nameOfExportFile; + this.type = type; + + teachConnectBook = new AddressBook(); +} +---- + +image::ExportCommandDiagram.png[align="center"] + +The method `handleRange()` splits the range using a separator [, in this case] and returns a `String` array with the upper bound and lower bound as values. In some cases it also returns `all` or the single `index` that has to be exported. Based on the type it also exports to an excel format or XML format. + +Below is an extract of the method `handleRange()`: + +[source,java] +---- +public String[] handleRange() throws IOException { + String[] rangeStringArray = this.range.split(","); + if (rangeStringArray.length > 2) { + throw new IOException(); + } + return rangeStringArray; +} +---- + +Any range with more than 2 values in the String array returned throws an IO Exception. To add the contacts to the export file, contacts are added to the teachConnectBook. There are 4 individual cases and multiple combinations of these: + +* All (Without a tag) +** if the word `all` is present in the user input, we will just export all the contacts from the last shown list. +* All (With a Tag) +** if the word `all` is present along with a tag specified in the user input, we will just export all the contacts with that particular tag from the last shown list +* Specific index (e.g. 1, 2, 3) +** if the user input contains a specific index, we will add that index (one-based) to the `teachConnectBook`. +* Range of indexes (e.g. 1,5) +** if the user input contains a range which is identified by the `,` character, we will add that range of index (one-based) to the `teachConnectBook` including the lower range but excluding the upper bound. +* Range of indexes (with a tag) +** if the user input contains a range which is identified by the `,` character along with the tag, we will add that range of index (one-based) to the `teachConnectBook` if that contact contains that particular tag including the lower range but excluding the upper bound. + + +Below is the code snippet to identify the three cases in the user input: + +[source,java] +---- +String[] rangeGiven; + try { + rangeGiven = handleRange(); + } catch (IOException e) { + return new CommandResult(MESSAGE_RANGE_ERROR); + } + + + try { + handledRangeSituation = handleRangeArray(rangeGiven); + } catch (DuplicatePersonException e) { + return new CommandResult(MESSAGE_FAIL); + } catch (IndexOutOfBoundsException e) { + return new CommandResult(MESSAGE_OUT_OF_BOUNDS); + } + + if (handledRangeSituation != null) { + return handledRangeSituation; + } + ....Storage part comes here +---- + +The final step is to create the xml/excel file from the `teachConnectBook`. + +Below is the code snippet to export the data into an xml file using `AddressBookStorage`. +[source,java] +---- +teachConnectStorage = new XmlAddressBookStorage(path + "/" + nameOfExportFile + ".xml"); + try { + teachConnectStorage.saveAddressBook(teachConnectBook); + } catch (IOException e) { + return new CommandResult(MESSAGE_FAIL); + } + return new CommandResult(MESSAGE_SUCCESS); +---- + +Depending on the type of export it can also be exported to an excel format using an arrayList called `exportAddition`. + +Below is the code snipped to export the data into an excel file. +[source,java] +---- +CSVPrinter csvPrinter; + try { + csvPrinter = csvFileToBeWritten(); + } catch (IOException e) { + handle exception... + } + + try { + for (Person p : exportAddition) { + csvPrinter.printRecord(p.getName(), p.getEmail(), p.getPhone(), p.getAddress(), p.getTags()); + } + + csvPrinter.flush(); + + } + exceptions are to be handled... +---- + +==== Design Considerations +===== Aspects : Implementation Export Command +* **Alternative 1:** Only export to an `XML` file. +** Pros: Easier implementation and very helpful for the import command as import can only be done from an XML file. +** Cons: The exported file might not be very user friendly to read in the xml file format and hence later referencing to the file after exporting can be a nightmare. +* **Alternative 2 (current choice):** Include export to an `Excel` file too. +** Pros: Might be more intuitive for the user and might come in handy especially when the user wants to print it or later read the contents in a user friendly format. +** Cons: Complex implementation there by giving rise to more boundary cases to consider. +// end::export[] + +// tag::shortcut[] +=== Personalised Shortcut +==== Current Implementation + +The personalised shortcut uses a `ShortcutDouble` to hold the shortcut word and the command word. There is a `UniqueShortcutDoublesList` to which these `ShortcutDoubles` are added. The comparator in the `ShortcutDouble` accounts to check for any duplicates in the `UniqueShortcutDoublesList`. This list is then added to the `addressbook.xml` so as to load the shortcuts on initialisation. Below is a short code snippet of the constructor of the ShortcutDouble: + +[source,java] +---- +public ShortcutDoubles(String shortcutWord, String commandWord) { + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; +} +---- + +This ShortcutDouble is called using the `ShortcutCommand`. Below is the constructor to the ShortcutCommand: +[source,java] +---- +public ShortcutCommand(String commandWord, String shortcutWord) { + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; +} +---- + +Shortcut command extends `UndoableCommand` and hence is undoable. It initially calls a filtered commandsList to which a new `ShortcutDouble` is added based on the checks. Below is the implementation of the `executeUndoableCommand()` method in the ShortcutCommand class. +[source,java] +---- +commandsList = model.getFilteredCommandsList(); + checks for checking if the command is already present... + + ShortcutDoubles toAdd = new ShortcutDoubles(shortcutWord, commandWord); + try { + model.addCommandShortcut(toAdd); + } catch (UniqueShortcutDoublesList.DuplicateShortcutDoublesException e) { + return new CommandResult(String.format(MESSAGE_SHORTCUT_AVAILABLE)); + } + + returns the success message.... +---- + +There is a check to find if the command is already present and the method used for this is called `checkIfCommandPresent()`.By default it returns false. Below is a small code snippet to take notice of: +[source,java] +---- +if (!containsKeyWord(commandWord) || containsKeyWord(shortcutWord)) { + throw new CommandException(MESSAGE_NO_COMMAND_TO_MAP); +} +for (ShortcutDoubles s : commandsList) { + if (s.shortcutWord.equals(shortcutWord)) { + return true; + } +} +return false; +---- + +You can also choose to list all the shortcuts created uptil now. This displays the `UniqueShortcutDoublesList` instead of the contacts in the list panel. A high level sequence diagram is given below : + +image::ListShortcutsHighLevelSequenceDiagrams.png[width="600"] + + + +As of now the conditions to take note of are: +* Shortcut can be only one word. +* The command word should already exist. +* New commands are to be added in the `commandsPresent` String array. + +==== Design Considerations +===== Aspects : Implementation Shortcut Command +* **Alternative 1:** Restricting the number of aliases of a command word. +** Pros: Allows for setting up a shortcut word there by increasing the usability of the app. +** Cons: Wouldn't help much if the user keeps forgetting the shortcut word too because there is only one shortcut alias and the user might forget it. +* **Alternative 2 (current choice):** Allowing multiple number of shortcut words for a single command word +** Pros: User can create multiple aliases there by giving the user more personalisation and the flexibility of forgetting the words as he can create more of them. +** Cons: Need to consider several cases for duplicate shortcuts and maintain a dynamic list without forgetting the shortcuts when the app is closed without hardcoding the shortcut word into each command. +// end::shortcut[] + +// tag::deleteshortcut[] +=== Deleting Personalised Shortcut +==== Current Implementation +The personalised shortcut uses a `ShortcutDouble` to hold the shortcut word and the command word. There is a `UniqueShortcutDoublesList` to which these `ShortcutDoubles` are added. The comparator in the `ShortcutDouble` accounts to check for any duplicates in the `UniqueShortcutDoublesList`. This list is then added to the `addressbook.xml` so as to load the shortcuts on initialisation. Below is a short code snippet of the constructor of the ShortcutDouble: + +[source,java] +---- +public ShortcutDoubles(String shortcutWord, String commandWord) { + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; +} +---- + +This ShortcutDouble can be deleted using the `DeleteShortcutCommand`. The sequence diagram is below : + +image::DeleteShortcutSequenceDiagram.png[width="600"] + +Below is the constructor to the DeleteShortcutCommand: +[source,java] +---- +public DeleteShortcutCommand(String commandWord, String shortcutWord) { + this.commandWord = commandWord; + this.shortcutWord = shortcutWord; + commandShortcut = new ShortcutDoubles(shortcutWord, commandWord); +} +---- + +DeleteShortcut command extends `UndoableCommand` and hence is undoable. It calls the method `deleteCommandShortcut()` in the model class to achieve its objective. Below is a code snippet of the `executeUndoableCommand()` used to for deleting the shortcut: +[source,java] +---- + try { + model.deleteCommandShortcut(commandShortcut); + } catch (UniqueShortcutDoublesList.CommandShortcutNotFoundException csnf) { + throw new CommandException("Please enter a valid Shortcut Command you have saved"); + } + returns the success message.... +---- + +There is a check to find if the shortcut is already present or not and `CommandShortcutNotFoundException` is thrown if the shortcut is not present. + +As of now the conditions to take note of are: +* DeleteShortcut can be only delete something if the command is already present. + +==== Design Considerations +===== Aspects : Validity of the Delete Shortcut Command +* **Alternative 1:** Omit the delete shortcut command. +** Pros: Would require less implementation considering the fact that the shortcut has been added by the user. +** Cons: Would not give the user any room for mistake or change of mind as once added cannot be deleted. +* **Alternative 2 (current choice):** Include the delete shortcut command. +** Pros: Will give the user the room to make mistake and change the shortcuts if needed. Would also help him in clearing the clutter of shortcuts which would have developed over time. +** Cons: Will have to take care of various edge cases when the shortcuts are not present and keep modifying the dynamic list. Several relevant exceptions have to be thrown and taken care of. +// end::endshortcut[] + +// tag::studentmanagement[] +=== [Incomplete] Student Management +==== Current Implementation + +The student manangement allows the user of TeachConnect to manage a particular type of contact, a student. The user is capable of interacting with the student contact just like with any other contact, for example: adding, editing, deleting and so on. In addition, users will be able to form classes to group students of the same class together. An overview of the Model Class after implementation is shown below: + +image::EditedModelClassDiagram.png[align="center"] + +As shown above, `student` extends from `person`, giving `student` access to its constructor and getter methods for `name`, `phone`, `email`, `address` whereas only `student` will have access to `class`. + +==== Design Considerations +===== Aspects : Implementation of student + +* **Alternative 1 (current choice):** student extends person +** Pros: Allow students access to person methods while restricting person from accessing student methods. +** Cons: A separate UniqueStudentList is required to store the student contacts. +* **Alternative 2:** only use a tag to show its a student +** Pros: Simpler to implement and tags are visible to user +** Cons: Requires every operation to check the tags. Tags can be removed. +// end::studentmanagement[] + +// tag::eventmanagement[] + +=== Event Management +==== Current Implementation +There are two types of events: an `Appointment` or a `Task`. +The model for event are shown below: + +image::EventModelClassDiagram.png[align="center"] + +`Appointment` consist of 4 variables: + +* Title: Hold description for the appointment +* Start Time: Hold the starting time of the appointment +* End time: Hold the end time of the appointment +* Person to meet: (optional) Hold the target in the appointment + +`Task` consist of 2 variables: + +* Title: Hold description for the task +* Time: Hold the time the task is expected to be finished + +*Commands supported for each event* + +* Set +* Remove +* Edit + +Similar to `UniquePersonList` and `UniqueTagList`, `UniqueEventList` is linked +to `AddressBook`. Request to change to the `AddressBook` model is signalled through `ModelManager`. + +==== Design Considerations +===== Aspects : Implementation of set event +* **Alternative 1 (current choice):** user can still set appointment/task with the starting time/deadline already elapsed. +** Pros: Might be useful if the users want to keep track of past events. +** Cons: Not the most intuitive implementation and might be prone to error from the user side. +* **Alternative 2:** user can only set appointment/task with the starting time/deadline in the future +** Pros: Is the more intuitive approach and can prevent the user from keying in "redundant" events +** Cons: As TeachConnect fetches the current time from the user's system, if the user for some purposes set the system's time to deviate from world clock, some difficulties may arise when he/she wants to add new event. + +// end::eventmanagement[] + +// tag::changingguitheme[] + +=== [Proposed] Changing GUI theme + +// end::changingguitheme[] + +_{to be finished later}_ // tag::dataencryption[] + === [Proposed] Data Encryption _{Explain here how the data encryption feature will be implemented}_ @@ -381,6 +761,7 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is * `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size [[Implementation-Configuration]] + === Configuration Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). @@ -493,7 +874,7 @@ Here are the steps to create a new release. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, TeachConnect depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) @@ -536,12 +917,12 @@ Do take a look at <<Design-Logic>> before attempting to modify the `Logic` compo [discrete] ==== `Model` component -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in TeachConnect, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. [TIP] Do take a look at <<Design-Model>> before attempting to modify the `Model` component. -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. +. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in TeachConnect. + **** * Hints @@ -610,7 +991,7 @@ image::getting-started-ui-result-after.png[width="200"] *** Do read the commits one at a time if you feel overwhelmed. **** -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in TeachConnect. + **Before** + @@ -623,7 +1004,7 @@ image::getting-started-ui-status-after.png[width="500"] **** * Hints ** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. +** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever TeachConnect is updated. * Solution ** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. ** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. @@ -635,12 +1016,12 @@ image::getting-started-ui-status-after.png[width="500"] [discrete] ==== `Storage` component -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving TeachConnect to the cloud. However, the current implementation of the application constantly saves TeachConnect after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for TeachConnect storage. [TIP] Do take a look at <<Design-Storage>> before attempting to modify the `Storage` component. -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that TeachConnect can be saved in a fixed temporary location. + **** * Hint @@ -655,7 +1036,7 @@ Do take a look at <<Design-Storage>> before attempting to modify the `Storage` c By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +*Scenario:* You are a software maintainer for `TeachConnect`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. ==== Description Edits the remark for a person specified in the `INDEX`. + @@ -780,15 +1161,84 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- [appendix] == Product Scope -*Target user profile*: +*Target user profile*: teachers or educational professionals who -* has a need to manage a significant number of contacts +* is a teacher or educational professional +* has a need to manage a significant number of students and parents contact details +* has a need to keep track of appointments with parents, students or other staff +* has a need to keep track of tasks and their deadlines * prefer desktop apps over other types * can type fast -* prefers typing over mouse input +* prefer typing over mouse input * is reasonably comfortable using CLI apps -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app + +*Value proposition*: TeachConnect provides a simple and intuitive interface to help teachers manage their contacts, events and tasks. + + +*Feature Contribution* + +1. Mukesh Gadupudi + +** Major Feature : Sharing of Contacts + +*** Contacts can be imported or exported +*** They can be exported based on the tag or index +*** Import can be done given the file path of an XML file + +** Minor Feature : Email contacts + +*** Contacts can be emailed by either by tag or an individual contact. + +** How the features fit into the product scope : + +*** Major Feature: This feature can help teachers share contacts with other teachers. This is especially useful when teachers change classes or pass on the class to other teachers. Updating and losing data is also a common problem and to overcome this a backup can be stored by using this feature. + +*** Minor Feature: This feature can help teachers email contacts. This might be really helpful when the teacher wants to remind parents with appointments or remind students with the work they need to finish. This also helps the teachers send group messages to class or parents regarding some important announcements. + +2. Rachel Ngo Phuong Thao +** Major Feature : Managing Appointments & Tasks + +*** Users can add, edit, and remove appointments & tasks in TeachConnect +*** The appointments & tasks would be rendered in a calendar in the GUI +** Minor Feature : Changing the GUI theme + +*** Users can set the theme of the GUI to dark theme or light theme + +** How the features fit into the product scope : + +*** Major Feature: This feature can help teachers keeping track of any upcoming appointment or task they have. This can be useful for teachers or teaching associates who frequently need to meet up with students and parents for counselling or administrative purposes. + +*** Minor Feature: This feature increases the aesthetic sense and helps people set the theme according to their taste. + +3. Jonathan + +** Major Feature : Data Encryption +*** Encrypts the data for increased safety + +** Minor Feature : Sort Contacts + +*** Contacts can be sorted in alphabetical order of the name/tag or the phone number of the contacts. + +** How the features fit into the product scope : + +*** Major Feature: Since TeachConnect has a lot of personal details of students and parents, the owner of the address book would want to encrypt the application data file to prevent outside access to sensitive information. + +*** Minor Feature: This helps teacher relate and understand the index of the contacts in the TeachConnect better. Indexing becomes easy when they later want to export or set appointments. + +4. Randy Pang Pang + +** Major Feature : Management of student contacts +*** Student contact can be created and be added into classes + +** Minor Feature : Multiple tabs for the list in GUI +*** The list in GUI will hava tabs for multiple different lists + +** How the features fit into the product scope : + +*** Major Feature: This feature is essential to TeachConnect as it helps teachers to remember which class did they taught a particular student. + +*** Minor Feature: This feature allows the teacher to keep multiple list, eg. one for students and one for all contacts. It also allows them to switch between multiple lists without having to type another list command. [appendix] == User Stories @@ -802,13 +1252,40 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un |`* * *` |user |add a new person | -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |user |delete a person |remove contacts that I no longer need |`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |user |edit the details of a person + |easily make changes to their details when they update their contact + +|`* * *` |teacher |create a class |group and manage students who are taking the same class + +|`* * *` |teacher |add persons to a class |group them for easy perusal + +|`* * *` |teacher | list all the students in a particular class |know all the students taking that class + +|`* *` |teacher |add appointment with a student to my schedule |be reminded of the appointment + +|`* *` |teacher |delete appointments from my schedule |clear appointments I no longer need to be reminded about + +|`* *` |teacher |list all appointments in my schedule |check all the appointments I have + |`* *` |user |hide <<private-contact-detail,private contact details>> by default |minimize chance of someone else seeing them by accident -|`*` |user with many persons in the address book |sort persons by name |locate a person easily +|`*` |user with many persons in TeachConnect |sort persons by name |locate a person easily + +|`*` |user |tag a person |mark their contact with details + +|`*` |user |find all person with a given tag |see all persons with contact marked with a certain detail + +|`*` |user |change the colour of a tag |make it easier for me to distinguish the tags + +|`*` |user |change the background colour of the application |make the application more pleasing to my eyes + +|`*` |user |export persons from TeachConnect to an external file| have persons' contacts ready for import + +|`*` |user |import persons from an external file to TeachConnect | have persons' contact details added without having to reenter the information |======================================================================= _{More to be added}_ @@ -816,17 +1293,17 @@ _{More to be added}_ [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is `TeachConnect` and the *Actor* is the `teacher`, unless specified otherwise) [discrete] === Use case: Delete person *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Teacher requests to list persons +2. TeachConnect shows a list of persons +3. Teacher requests to delete a specific person in the list +4. TeachConnect deletes the person + Use case ends. @@ -840,10 +1317,94 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. TeachConnect shows an error message. + Use case resumes at step 2. +[discrete] +=== Use case: Create class + +*MSS* + +1. Teacher requests to list persons +2. TeachConnect shows a list of persons +3. Teacher requests to create a class of a subject for a specified duration +4. TeachConnect prompts for student(s) to be added into the class +5. Teacher enters index of student(s) as shown in the list +5. TeachConnect creates the class ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given subject or duration is invalid. ++ +[none] +** 3a1. TeachConnect shows an error message. ++ +Use case resumes at step 2. + +* 5a. One or more given index(s) is invalid. ++ +[none] +** 5a1. TeachConnect shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: Add appointment + +*MSS* + +1. Teacher requests to add an appointment at a specified time +2. TeachConnect prompts for a title +3. Teacher enters a title +4. TeachConnect adds the appointment ++ +Use case ends. + +*Extensions* + +[none] +* 1a. Time given is invalid. ++ +[none] +** 1a1. TeachConnect shows error message. ++ +Use case ends. + +[discrete] +=== Use case: Delete appointment + +*MSS* + +1. Teacher requests to list appointments +2. TeachConnect shows list of appointments +3. Teacher requests to delete a specific appointment in the list +4. TeachConnect deletes appointment ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. TeachConnect shows error message. ++ +Use case resumes at step 2. + + _{More to be added}_ [appendix] @@ -852,6 +1413,10 @@ _{More to be added}_ . Should work on any <<mainstream-os,mainstream OS>> as long as it has Java `1.8.0_60` or higher installed. . Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. Should be intutive for any first time user. +. Should be able to handle any invalid input i.e should be able to inform the user and guide the user for valid input. +. Should respond within a second + _{More to be added}_ diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..253e0dddd853 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += TeachConnect - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,37 +11,46 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +:repoURL: https://github.com/CS2103JAN2018-W14-B1/main/ + +By: `Team W14-B1` Since: `Jan 2018` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <<Quick Start>> to get started. Enjoy! +TeachConnect (TC) is created to assist teachers and other educational professionals in *managing their contacts, appointments and tasks*. TeachConnect is also tailored for teachers who would *prefer to use a Desktop App for managing contacts*. Most importantly, it is *optimized for those who prefer to enter input using keyboard while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TeachConnect will manage your contacts faster than traditional GUI apps. Interested? Jump to <<Quick Start>> to get started. Enjoy! == Quick Start . Ensure you have Java version `1.8.0_60` or later installed in your Computer. + [NOTE] -Having any Java 8 version is not enough. + -This app will not work with earlier versions of Java 8. +This app will not work with earlier versions of Java 8. + +You can download the latest Java version link:https://java.com/en/download/[here]. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `Teachconnect.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your TeachConnect. . Double-click the file to start the app. The GUI should appear in a few seconds. + -image::Ui.png[width="790"] +image::StartupUI.jpg[width="800"] + Figure 1: Successful start up UI with Light Theme selected + . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ++ +image::commandbox.jpg[width="800"] + Figure 2: Position of the command box ++ + . Some example commands you can try: + * *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. +* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe`. * **`delete`**`3` : deletes the 3rd contact shown in the current list * *`exit`* : exits the app + . Refer to <<Features>> for details of each command. [[Features]] @@ -52,63 +61,321 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Items with `…` after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. ==== -=== Viewing help : `help` +=== Viewing manual : `help` +Accesses the User Guide for TeachConnect. + Format: `help` -=== Adding a person: `add` +=== Sorting all contacts : `sort` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Sorts and shows a list of all contacts in TeachConnect lexographically + +Format: `sort` + +=== Adding a contact: `add` + +Adds a contact. + +Format: `add [TYPE] n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + +**** +* `[TYPE]` field represents the type of contact you wish to add. + +* It can be `student`, or `{nbsp}` (empty for a default contact). +**** [TIP] -A person can have any number of tags (including 0) +A contact can have any number of tags (including 0) + +Examples: + +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 t/friend` + +Adds a default contact to TeachConnect's contact list. +* `add student n/Betsy Crowe e/betsycrowe@example.com a/Centre Street, block 238, #02-02` + +Adds a student contact to TeachConnect's contact list. + + +=== Forming a class `[coming in v1.5]` + +Forms a class of students for a specified subject and time period. + +Format: `form subj/SUBJECT s/START_DATE e/END_DATE i/INDEX...` + +**** +* Students specified by the `INDEX` are added to the class. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* `1, 2, 3, ...`. +* Minimum of one student must be entered. There can be more than one student +* Only a student contact can be entered, default and guardian contacts are not allowed. +* Dates must be in the format: `DD/MM/YYYY`. +**** Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `list students` + +`form 1,2,5 subj/English s/18/01/2018 e/17/07/2019` + +Forms an English class containing the first, second and fifth contact in the list that starts in 18 Jan 2018 to 17 July 2019. -=== Listing all persons : `list` +// tag::appointment[] +=== Setting up an appointment : `set_appointment` `[since v1.3]` -Shows a list of all persons in the address book. + -Format: `list` +Sets up an appointment with the specified contact. + +Format: `set_appointment t/TITLE s/START_DATE START_TIME e/END_DATE END_TIME i/INDEX` -=== Editing a person : `edit` +**** +* This sets an appointment with `TITLE`, from `START_DATE` at `START_TIME` to `END_DATE` at `END_TIME`, with contact at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* `1, 2, 3, ...`. +* `START_DATE` and `END_DATE` must be in the format `DD/MM/YYYY`. +* `START_TIME` and `END_TIME` must be in the 24-hr format: HH:MM. +* The new appointment will be listed on both the appointment list and the TeachConnect Calendar. +**** + +Examples: + +* `set_appointment t/Tutoring session s/02/04/2018 19:00 e/02/04/2018 20:00` + +Sets up an appointment on April 2nd, 2018, from 7pm to 8pm. ++ +image::appointmentAdded.jpg[width="800"] + Figure 3: Appointment added successfully example ++ + +// end::appointment[] +// tag::task[] +=== Setting up a task : `set_task` `[since v1.3]` + +Sets up a task to be done by a deadline. + +Format: `set_task t/TITLE e/END_DATE END_TIME` + +**** +* Task with `TITLE` which needs to completed before `END_DATE` at `END_TIME` is added. +* `END_DATE` must be in the format `DD/MM/YYYY`. +* `END_TIME` must be in the 24-hr format: HH:MM. +**** + +Examples: + +* `set_task t/Mark papers e/05/04/2018 10:00` + +Sets a task which needs to be completed before April 5th, 2018, 10am. +// end::task[] + +=== Removing an appointment or a task: `remove` `[since v1.4]` + +Removes an appointment or a task by the index number used from their respective listing. + +Format: `remove EVENT_TYPE INDEX`. + +**** +* `EVENT_TYPE` can be either `appointment` or `task`. +* The index *must be a positive integer* `1, 2, 3, ...`. +**** + +Examples: + +* `list appointments` + +`remove appointment 2` + +Removes the appointment with the index 2 in the appointment list. ++ +image::appointmentRemoved.jpg[width="800"] + Figure 4: Remove command usage example ++ + +// tag::theme[] +=== Changing GUI theme : `theme` `[since v1.4]` + +Changes the theme of the GUI. + +Format: `theme THEME_NAME` + +**** +* This changes the theme of the GUI to `THEME_NAME`. +* `THEME_NAME` can be `dark`, `light` or `galaxy`. + +**** +Examples: + +* `theme dark` + +Changes the theme of TeachConnect to Dark Theme. ++ +image::themeDark.jpg[width="800"] + Figure 5: GUI with Dark Theme ++ +* `theme galaxy` + +Changes the theme of TeachConnect to Galaxy Theme. ++ +image::themeGalaxy.jpg[width="800"] + Figure 6: GUI with Galaxy Theme ++ +// end::theme[] + +=== Switching Calendar Viewmode : `calendar` `[since v1.4]` + +Switches the Calendar view mode. + +Format: `calendar VIEW_MODE` + +**** +* This changes the view mode of the TeachConnect Calendar. +* `VIEW_MODE` can be `d`, `w` or `m`, which are respective short forms for day, week and month. +**** +Examples: + +* `calendar d` + +Changes the view mode of TeachConnect Calendar to Day View. ++ +image::calendarDay.jpg[width="800"] + Figure 7: Calendar Day View ++ +* `calendar w` + +Changes the view mode of TeachConnect Calendar to Week View. ++ +image::calendarWeek.jpg[width="800"] + Figure 8: Calendar Week View ++ +* `calendar m` + +Changes the view mode of TeachConnect Calendar to Month View. ++ +image::calendarMonth.jpg[width="800"] + Figure 9: Calendar Month View ++ + +// tag::list[] +=== Listing all contact/task/appointment/student : `list` `[since v1.4]` + +Shows a list of all of the specified `TYPE`. + +Format: `list TYPE`. + +**** +* `TYPE` can be of the following: `contacts`, `students`, `tasks`, `appointments`, `shortcuts`. +* `TYPE` cannot be empty. +**** + +Examples: + +* `list students` + +Lists all student. +* `list tasks` + +Lists all task. +* `list shortcuts` + +Lists all command shortcuts. + +// end::list[] + +// tag::import[] +=== Importing the contacts : `import` `[since v1.4]` + +Imports contacts from a different TeachConnect file by specifying the location of the file. + + +Format: `import [TYPE] pathname` + +Examples: + +* Imports Contacts : `import ./data/importsample.xml` + +// end::import[] + +// tag::export[] +=== Exporting the contacts : `export` `[since v1.4]` + +Exports contacts from your TeachConnect by specifying the name of the file you want to save it in and the path where you want to save it. It can export the contacts based on a given range of indexes or a given tag or a given tag in a range of indexes. By specifying the type of the export you want it saves either only the xml file or both the xml and Csv file. + +Format: `export n/NAME r/RANGE t/TAG p/PATH te/normal` + +Format: `export n/NAME r/RANGE p/PATH te/excel` + +[TIP] +You can export all the people at once, all the people with a certain tag at once, all the people with a certain tag in a range at once or all the people in a range with any tags in a single command. + +You can also choose to export it in Csv format which you can later open in Excel. + +[WARNING] +You can only export all or people based on one or zero tags. + +Be careful about the parameter value for the format type. It has to exactly be either `normal` or `excel`. + +Examples: + +* Exports contacts : `export n/StudentsFile1 r/all t/students p/./data te/normal` +* Exports contacts : `export n/StudentsFile2 r/1,10 t/students p/./data te/excel` + +// end::export[] + +// tag::shortcut[] +=== Adding your own shortcut : `shortcut` `[since v1.4]` + +Sets your own personal shortcut for any of the commands above. + +Format: `shortcut [command word] [shortcut word]` + +[TIP] +You can choose multiple shortcuts for the same command. + +You can later use these shortcuts in place of the original command even after closing and reopening the app. + +You can also set shortcut for the shortcut command. + +[WARNING] +You cannot set the shortcut word to a already preregistered command. + +Your shortcut word cannot be more than a single word. + +Examples: + +* `shortcut list l` + +Sets `l` as the Personalised Alias for `list` command. +* `shortcut add a` + +Sets `a` as the Personalised Alias for `add` command. + +// end::shortcut[] + +// tag::deleteshortcut[] +=== Deleting your personalised Alias : `delete_shortcut` `[since v1.4]` + +Deletes your personalised Alias if you don't want them or if you created them by mistake. + +Format: `delete_shortcut [command word] [shortcut word]` + +[TIP] +You can choose to just undo the delete_shortcut if you delete a shortcut by mistake. + +Listing all the shortcuts using the `list shortcuts` command as mentioned above might help in seeing all the shortcuts at once. + + +[WARNING] +You can only delete shortcuts that you have already added. + +Examples: + +* `delete_shortcut list l` + +Deletes the Personalised Alias `l` for `list` command. +* `delete_shortcut add a` + +Deletes the Personalised Alias `a` for `add` command. +// end::deleteshortcut[] + +=== Editing a contact : `edit` + +Edits an existing contact. + -Edits an existing person in the address book. + Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... +* Contact at the specified `INDEX` is edited. +* The index refers to the index number shown in the last contact listing. +* The index *must be a positive integer* `1, 2, 3, ...`. * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags, the existing tags of the contact will be removed i.e adding of tags is not cumulative. +* You can remove all the contact's tags by typing `t/` without specifying any tags after it. **** Examples: * `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. +Edits the phone number and email address of the 1st contact to be `91234567` and `johndoe@example.com` respectively. * `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +Edits the name of the 2nd contact to be `Betsy Crower` and clears all existing tags. -=== Locating persons by name: `find` +=== Locating contacts by name: `find` -Finds persons whose names contain any of the given keywords. + +Finds contacts whose names contain any of the given keywords. + Format: `find KEYWORD [MORE_KEYWORDS]` **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` +* The search is case insensitive. e.g `hans` will match `Hans`. +* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`. * Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Only full words will be matched e.g. `Han` will not match `Hans`. +* Contacts matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`. **** Examples: @@ -116,47 +383,55 @@ Examples: * `find John` + Returns `john` and `John Doe` * `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +Returns any contact having names `Betsy`, `Tim`, or `John` + +=== Deleting an entry in the list : `delete` -=== Deleting a person : `delete` -Deletes the specified person from the address book. + +Deletes the specified entry in the list. + Format: `delete INDEX` **** -* Deletes the person at the specified `INDEX`. +* Entry at the specified `INDEX` is deleted. * The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* 1, 2, 3, ... +* The index *must be a positive integer* `1, 2, 3, ...`. **** Examples: -* `list` + +* `list contacts` + `delete 2` + -Deletes the 2nd person in the address book. +Deletes the 2nd contact in the list + * `find Betsy` + `delete 1` + -Deletes the 1st person in the results of the `find` command. +Deletes the 1st contact in the results of the `find` command. + +* `list tasks` + +`delete 4` + +Deletes the 4th task in the list -=== Selecting a person : `select` +=== Selecting a contact : `select` -Selects the person identified by the index number used in the last person listing. + +Selects the contact identified by the index number used in the last contact listing. + Format: `select INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. +* Contact specified by `INDEX` is selected. * The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* `1, 2, 3, ...` +* The index *must be a positive integer* `1, 2, 3, ...`. **** Examples: * `list` + `select 2` + -Selects the 2nd person in the address book. + +Selects the 2nd contact in the list. + * `find Betsy` + `select 1` + -Selects the 1st person in the results of the `find` command. +Selects the 1st contact in the results of the `find` command. === Listing entered commands : `history` @@ -171,12 +446,12 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores TeachConnect to the state before the previous _undoable_ command was executed. + Format: `undo` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify TeachConnect's content (`add`, `delete`, `edit` and `clear`). ==== Examples: @@ -220,7 +495,7 @@ The `redo` command fails as there are no `undo` commands executed previously. === Clearing all entries : `clear` -Clears all entries from the address book. + +Clears all entries. + Format: `clear` === Exiting the program : `exit` @@ -230,35 +505,145 @@ Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +Saves data in the hard disk automatically [even while sharing TeachConnect] after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +=== Importing the contacts : `import` `[since v1.4]` + +Imports contacts from a different TeachConnect file by specifying the location of the file. + + +Format: `import [TYPE] pathname` + +Examples: + +* Imports Contacts : `import ./data/importsample.xml` + +=== Exporting the contacts : `export` `[since v1.4]` + +Exports contacts from your TeachConnect by specifying the name of the file you want to save it in and the path where you want to save it. It can export the contacts based on a given range of indexes or a given tag or a given tag in a range of indexes. By specifying the type of the export you want it saves either only the xml file or both the xml and Csv file. + +Format: `export n/NAME r/RANGE t/TAG p/PATH te/normal` + +Format: `export n/NAME r/RANGE p/PATH te/excel` + +[TIP] +You can export all the people at once, all the people with a certain tag at once, all the people with a certain tag in a range at once or all the people in a range with any tags in a single command. + +You can also choose to export it in Csv format which you can later open in Excel. + +[WARNING] +You can only export all or people based on one or zero tags. + +Be careful about the parameter value for the format type. It has to exactly be either `normal` or `excel`. + +Examples: + +* Exports contacts : `export n/StudentsFile1 r/all t/students p/./data te/normal` +* Exports contacts : `export n/StudentsFile2 r/1,10 t/students p/./data te/excel` + +=== Adding your own shortcut : `shortcut` `[since v1.4]` + +Sets your own personal shortcut for any of the commands above. + +Format: `shortcut [command word] [shortcut word]` + +[TIP] +You can choose multiple shortcuts for the same command. + +You can later use these shortcuts in place of the original command even after closing and reopening the app. + +You can also set shortcut for the shortcut command. + +[WARNING] +You cannot set the shortcut word to a already preregistered command. + +Your shortcut word cannot be more than a single word. + +Examples: + +* `shortcut list l` + +Sets `l` as the Personalised Alias for `list` command. +* `shortcut add a` + +Sets `a` as the Personalised Alias for `add` command. + +=== Deleting your personalised Alias : `delete_shortcut` `[since v1.4]` + +Deletes your personalised Alias if you don't want them or if you created them by mistake. + +Format: `delete_shortcut [command word] [shortcut word]` + +[TIP] +You can choose to just undo the delete_shortcut if you delete a shortcut by mistake. + +Listing all the shortcuts using the `list shortcuts` command as mentioned above might help in seeing all the shortcuts at once. + + +[WARNING] +You can only delete shortcuts that you have already added. + +Examples: + +* `delete_shortcut list l` + +Deletes the Personalised Alias `l` for `list` command. +* `delete_shortcut add a` + +Deletes the Personalised Alias `a` for `add` command. + + +=== Upcoming features `[coming soon]` + +* A login feature [coming in v2.0] +* Dynamic search [coming in v2.0] +* NLP for event and appointment scheduling [coming in v2.0] +* Encrypting data files [coming in v2.0] == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. Alternatively you can also choose the import and export commands! == Command Summary +The table below summarizes TeachConnect's command list. + +[width="59%",cols="22%,<30%,<30%",options="header",] +|======================================================================= +|Command |Format |Example + +|*Add*|`add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...`|`add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` + +|*Clear*|`clear`|`clear` + +|*Delete*|`delete INDEX`|`delete 3` + +|*Edit*|`edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...`|`edit 2 n/James Lee e/jameslee@example.com` + +|*Find*|`find KEYWORD [MORE_KEYWORDS]` | `find James Jake` + +|*List*|`list TYPE` | `list student` + +|*Help*|`help`|`help` + +|*Select Contact*|`select INDEX` |`select 2` + +|*Set Appointment*|`set_appointment t/TITLE s/START_DATE START_TIME e/END_DATE END_TIME i/INDEX`|`set_appointment t/Meet parent s/05/04/2018 10:00 e/05/04/2018 11:00 i/3` + +|*Set Task*|`set_task t/TITLE e/END_DATE END_TIME` |`set_task t/Mark papers d/05/04/2018 10:00` + +|*Remove*|`remove EVENT_TYPE INDEX` | `remove task 3` + +|*Change GUI theme*|`theme THEME_NAME` | `theme dark` + +|*Change Calendar View mode*|`calendar VIEW_MODE` | `calendar d` + +|*Change GUI theme*|`theme THEME_NAME` | `theme dark` + +|*Import TeachConnect File*|`import` | `import ./data/samplefile.xml` + +|*Export*|`export n/NAME r/RANGE t/TAG p/PATH te/TYPE` | `export n/samplefile.xml r/all t/friends p/.data te/excel` + +|*Set Shortcut*|`shortcut [command word] [shortcut word]` | `shortcut list l` + +|*Delete Shortcut*|`delete_shortcut [command word] [shortcut word]` | `delete_shortcut list l` + +|*History*|`history`|`history` + +|*Undo*|`undo`|`undo` + +|*Redo*|`redo`|`redo`| + +|======================================================================= -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` + Table 1: TeachConnect's command list diff --git a/docs/diagrams/Delete_ShortcutSequenceDiagram.pptx b/docs/diagrams/Delete_ShortcutSequenceDiagram.pptx new file mode 100644 index 000000000000..e872a3c3aa71 Binary files /dev/null and b/docs/diagrams/Delete_ShortcutSequenceDiagram.pptx differ diff --git a/docs/diagrams/~$HighLevelSequenceDiagrams.pptx b/docs/diagrams/~$HighLevelSequenceDiagrams.pptx new file mode 100644 index 000000000000..bb82a5866dca Binary files /dev/null and b/docs/diagrams/~$HighLevelSequenceDiagrams.pptx differ diff --git a/docs/images/DeleteShortcutSequenceDiagram.png b/docs/images/DeleteShortcutSequenceDiagram.png new file mode 100644 index 000000000000..151e79f439c0 Binary files /dev/null and b/docs/images/DeleteShortcutSequenceDiagram.png differ diff --git a/docs/images/EditedModelClassDiagram.png b/docs/images/EditedModelClassDiagram.png new file mode 100644 index 000000000000..99163ddff879 Binary files /dev/null and b/docs/images/EditedModelClassDiagram.png differ diff --git a/docs/images/EventModelClassDiagram.png b/docs/images/EventModelClassDiagram.png new file mode 100644 index 000000000000..592ff04eef1a Binary files /dev/null and b/docs/images/EventModelClassDiagram.png differ diff --git a/docs/images/ExportCommandDiagram.png b/docs/images/ExportCommandDiagram.png new file mode 100644 index 000000000000..c62439ccb185 Binary files /dev/null and b/docs/images/ExportCommandDiagram.png differ diff --git a/docs/images/ImportCommandFlowChart.png b/docs/images/ImportCommandFlowChart.png new file mode 100644 index 000000000000..4e9e1948a5db Binary files /dev/null and b/docs/images/ImportCommandFlowChart.png differ diff --git a/docs/images/LimShiMinJonathan.jpg b/docs/images/LimShiMinJonathan.jpg new file mode 100644 index 000000000000..befb33d61b7a Binary files /dev/null and b/docs/images/LimShiMinJonathan.jpg differ diff --git a/docs/images/ListShortcutsHighLevelSequenceDiagrams.png b/docs/images/ListShortcutsHighLevelSequenceDiagrams.png new file mode 100644 index 000000000000..c852c484ef41 Binary files /dev/null and b/docs/images/ListShortcutsHighLevelSequenceDiagrams.png differ diff --git a/docs/images/StartupUI.jpg b/docs/images/StartupUI.jpg new file mode 100644 index 000000000000..90daa1cb3c49 Binary files /dev/null and b/docs/images/StartupUI.jpg differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png deleted file mode 100644 index 5ec9c527b49c..000000000000 Binary files a/docs/images/Ui.png and /dev/null differ diff --git a/docs/images/appointmentAdded.jpg b/docs/images/appointmentAdded.jpg new file mode 100644 index 000000000000..5462532a2051 Binary files /dev/null and b/docs/images/appointmentAdded.jpg differ diff --git a/docs/images/appointmentRemoved.jpg b/docs/images/appointmentRemoved.jpg new file mode 100644 index 000000000000..16ffc7afaf82 Binary files /dev/null and b/docs/images/appointmentRemoved.jpg differ diff --git a/docs/images/calendarDay.jpg b/docs/images/calendarDay.jpg new file mode 100644 index 000000000000..77fa6af8c503 Binary files /dev/null and b/docs/images/calendarDay.jpg differ diff --git a/docs/images/calendarMonth.jpg b/docs/images/calendarMonth.jpg new file mode 100644 index 000000000000..088e05d08ca4 Binary files /dev/null and b/docs/images/calendarMonth.jpg differ diff --git a/docs/images/calendarWeek.jpg b/docs/images/calendarWeek.jpg new file mode 100644 index 000000000000..1140bb50c0ea Binary files /dev/null and b/docs/images/calendarWeek.jpg differ diff --git a/docs/images/commandbox.jpg b/docs/images/commandbox.jpg new file mode 100644 index 000000000000..83b1e7639426 Binary files /dev/null and b/docs/images/commandbox.jpg differ diff --git a/docs/images/randypx.jpg b/docs/images/randypx.jpg new file mode 100644 index 000000000000..7d0b17c8a64b Binary files /dev/null and b/docs/images/randypx.jpg differ diff --git a/docs/images/shanmu9898.jpg b/docs/images/shanmu9898.jpg new file mode 100644 index 000000000000..67e96f0c91a5 Binary files /dev/null and b/docs/images/shanmu9898.jpg differ diff --git a/docs/images/sisyphus.jpg b/docs/images/sisyphus.jpg new file mode 100644 index 000000000000..520700422c1b Binary files /dev/null and b/docs/images/sisyphus.jpg differ diff --git a/docs/images/themeDark.jpg b/docs/images/themeDark.jpg new file mode 100644 index 000000000000..7c3876762e78 Binary files /dev/null and b/docs/images/themeDark.jpg differ diff --git a/docs/images/themeGalaxy.jpg b/docs/images/themeGalaxy.jpg new file mode 100644 index 000000000000..f961e74fb3fa Binary files /dev/null and b/docs/images/themeGalaxy.jpg differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/MukeshGadupudi.adoc b/docs/team/MukeshGadupudi.adoc new file mode 100644 index 000000000000..b29b5110b57c --- /dev/null +++ b/docs/team/MukeshGadupudi.adoc @@ -0,0 +1,184 @@ += Mukesh Gadupudi - Project Portfolio +ifdef::env-github,env-browser[:outfilesuffix: .adoc] +:imagesDir: ../images +:stylesDir: ../stylesheets + +== Project: TeachConnect + +TC is created to help teachers and other educational professionals better manage their contacts and remember their past students. It is tailored for teachers/educational professionals who would prefer to use a Desktop App for managing contacts especially for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, TC will manage your contacts in the wink of an eye! + +*Code contributed*: [https://github.com/CS2103JAN2018-W14-B1/main/blob/master/collated/functional/shanmu9898.md[Functional code]] [https://github.com/CS2103JAN2018-W14-B1/main/blob/master/collated/test/shanmu9898.md[Test code]] + +=== Enhancement Added: List Display for Shortcut Commands + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=list] + +#End of Extract# + +--- + +==== Justification + +Teachers or even educational professionals in general have a lot on their hand. When it comes to saving their personalised shortcuts for commands they want, there is a high probability that they forget the shortcut they assigned. + +Showing a list of the shortcut words and the commands to which they have assigned this word will be highly useful for them. + + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=shortcut] + +#End of Extract# + +--- + + +=== Enhancement Added: Adding Personalised Shortcut for Commands + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=shortcut] + +#End of Extract# + +--- + +==== Justification + +TeachConnect comes with a lot of features and each feature has its own command word. The most gruelling part is remembering the exact commmand word as stated in the user guide. To overcome this the shortcut command helps keep aliases to the commands there by helping teachers better remember the command words they are comfortable with. + +This also eliminates the need for a common shortcut word which many teachers might not be comfortable with. Hence this command is a twist of personalisation packed with the essence of comfort. + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=shortcut] + +#End of Extract# + +--- + + +=== Enhancement Added: Deleting Shortcut Commands + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=deleteshortcut] + +#End of Extract# + +--- + +==== Justification + +Mistakes are bound to happen and as long as teachers are humans there is a chance that they might make a mistake. In cases when a teacher assigns a wrong shortcut word to a wrong command word or wants to erase the shortcut entirely, this delete shortcut command comes in handy. + +There is a also a big chance that the assigned number of alias or shortcuts to a certain key word has become huge and the user wants to clear up a few of them. In cases like this too the delete shortcut command is of utmost use. + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=deleteshortcut] + +#End of Extract# + +--- + + +=== Enhancement Added: Importing Contacts + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=import] + +#End of Extract# + +--- + +==== Justification + +Teachers are highly socializable people and there is a high probability that they will want to import contacts from other teachers regarding students' contact details. In contexts like these importing contacts is of utmost importance. + +There might also be cases where teachers might take over a class from another teacher and hence it should be made easy to transfer all the student details from one teacher's TeachConnect to the other teacher's TeachConnect. + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=import] + +#End of Extract# + +--- + + + +=== Enhancement Added: Export Command + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=export] + +#End of Extract# + +--- + +==== Justification + +For teachers to share contacts when a new teacher takes up a class or just when a new teacher asks for contact details it is necessary to have the export comand. + +Since exporting all of the contacts might be redundant and also neglects some confidential contacts, the export command has the feature of exporting a single person or a range of contacts based on tags or just indexes. + +Another very important feature is to export to a CSV file (which can be opened in excel) for them to later create or print Contact Books like the older days address book. + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=export] + +#End of Extract# + +--- + +=== Other contributions + +* Created and set up the team repo including the Travis checks,Coveralls,Auto Publishing and Badges. +* Managed all Issues and Milestone by managing the project and assigning work. +* Wrote additional tests to increase coverage. +* Took the role of Team Leader to make important decisions regarding development of product. +* Managed GitHub effectively to keep track of issues, merging Pull requests by resolving conflicts and keeping track of deadlines so that the weekly releases are made on time with a working product. +* Fixed various(5+) bugs in the product including some bugs which existed previously in the product. link:https://github.com/CS2103JAN2018-W14-B1/main/pull/144[`Pull Request Bug Fix Example`] +* Added several Use Cases for features in Developed Guide. +* Improved User Interface design by changing the orientation and location of various parts of UI. +* Helped teammate create his first PR along with helping him setup git and checkstyle. link:https://github.com/CS2103JAN2018-W14-B1/main/pull/82[`Pull Request AutoSort`]. +* Contributed in User Guide and Developer Guide to enhance its language link:https://github.com/CS2103JAN2018-W14-B1/main/pull/54[`Pull Request Developer Guide Update`]. +* Helped people on the forum with their technical difficulties. +* Managed all final submissions for CS2103T and CS2101. +* Helped in Collating of codes of everyone link:https://github.com/CS2103JAN2018-W14-B1/main/pull/124[`Pull Request Collate`]. diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/rachelngo.adoc b/docs/team/rachelngo.adoc new file mode 100644 index 000000000000..33fc17f088c7 --- /dev/null +++ b/docs/team/rachelngo.adoc @@ -0,0 +1,112 @@ += Rachel Ngo Phuong Thao - Project Portfolio +ifdef::env-github,env-browser[:outfilesuffix: .adoc] +:imagesDir: ../images +:stylesDir: ../stylesheets +:collatedDir: ../../collated + +== Project: TeachConnect +TeachConnect is a contact and event management application designed for teachers and educational professionals. + + +TeachConnect is designed to best aid users who prefer to enter input using keyboard instead of Graphical User Interface (GUI). + +*Code contributed*: + +link:{collatedDir}/main/Sisyphus25.md[Functional code] + +link:{collatedDir}/test/Sisyphus25.md[Test code] + + +== Portfolio Purpose +This portfolio was made to document all contribution I have made to the project. + +The content includes are: + +. The external behavior of my contributions +. The justification for their implementation +. Details of the implementation + + +<<< + +=== Enhancement Added: Task and Appointment commands + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=appointment] + +include::../UserGuide.adoc[tag=task] + +#End of Extract# + +--- + +==== Justification + +--- +These features make it easier for teachers and educational staff to review appointments they have set with students and parents, or to review the list of tasks they have to do before a certain deadline. + +--- + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=eventmanagement] + +#End of Extract# + +--- + +<<< + + +=== Enhancement Added: Theme Command + +==== External behavior + +--- +#Start of Extract [from: User Guide]# + +include::../UserGuide.adoc[tag=theme] + +#End of Extract# + +--- + +==== Justification + +--- +This feature allows the user to customize the GUI of TeachConnect to their own liking and set the GUI colour to one that is most pleasant to their eyes. + +--- + +==== Implementation + +--- +#Start of Extract [from: Developer Guide]# + +include::../DeveloperGuide.adoc[tag=theme] + +#End of Extract# + +--- + +<<< + +=== Enhancement Proposed (for Version 2.0) +* More classifications for existing contacts: i.e: `student`, `staff`, `friend` instead of just `student`. +* A `get guardian` command which allows the user to quickly retrieve contact detail of the guardian of a student in the contact list. + +=== Other contributions + +** Tag Colour for Person tags +** Toggle Calendar View command +** Modification `list` command to toggle list displayed on GUI. +** 2 new themes for GUI: `light` and `galaxy` +** Project issues assignment and management +** Project PR review +** Bugs fixes for various issues raised by other teammates and testers +** User Guide + +*** Commands usage + +*** Demo images + +** Test Coverage diff --git a/libs/README-EXT.txt b/libs/README-EXT.txt new file mode 100644 index 000000000000..6077947eddc1 --- /dev/null +++ b/libs/README-EXT.txt @@ -0,0 +1,18 @@ +This directory contains third-party jar files that are required +by CalendarFX. The framework can not work without these. + +- controlsfx-xxx.jar + + Custom controls developed as part of the open source project ControlsFX. + +- fontawesomefx-commons-xxx.jar + + Common support code for web fonts in JavaFX. + +- fontawesomefx-fontawesome-xxx.jar + + The fontawesome font for JavaFX. + +- license4j-1.4.0.jar + + Support for licensing keys. diff --git a/libs/calendarfx-recurrence-8.4.1.jar b/libs/calendarfx-recurrence-8.4.1.jar new file mode 100644 index 000000000000..fe3c5ec86a5d Binary files /dev/null and b/libs/calendarfx-recurrence-8.4.1.jar differ diff --git a/libs/calendarfx-view-8.4.1.jar b/libs/calendarfx-view-8.4.1.jar new file mode 100644 index 000000000000..0e6cea63650b Binary files /dev/null and b/libs/calendarfx-view-8.4.1.jar differ diff --git a/libs/commons-csv-1.5-sources.jar b/libs/commons-csv-1.5-sources.jar new file mode 100644 index 000000000000..952fc2d2519b Binary files /dev/null and b/libs/commons-csv-1.5-sources.jar differ diff --git a/libs/commons-csv-1.5.jar b/libs/commons-csv-1.5.jar new file mode 100644 index 000000000000..eb4775e30a69 Binary files /dev/null and b/libs/commons-csv-1.5.jar differ diff --git a/libs/controlsfx-8.40.11.jar b/libs/controlsfx-8.40.11.jar new file mode 100644 index 000000000000..3e409877f818 Binary files /dev/null and b/libs/controlsfx-8.40.11.jar differ diff --git a/libs/fontawesomefx-commons-8.13.jar b/libs/fontawesomefx-commons-8.13.jar new file mode 100644 index 000000000000..5acff3236bf2 Binary files /dev/null and b/libs/fontawesomefx-commons-8.13.jar differ diff --git a/libs/fontawesomefx-fontawesome-4.7.0-1.jar b/libs/fontawesomefx-fontawesome-4.7.0-1.jar new file mode 100644 index 000000000000..8360e5db9fe5 Binary files /dev/null and b/libs/fontawesomefx-fontawesome-4.7.0-1.jar differ diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 8f4d737d0e24..524c48994428 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "TeachConnect"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..b11827aa7910 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -8,6 +8,6 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java index 7db9b5c48ed6..65d12ff02c2f 100644 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java @@ -14,6 +14,8 @@ public AddressBookChangedEvent(ReadOnlyAddressBook data) { @Override public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + return "number of persons " + data.getPersonList().size() + ", number of tags " + + data.getTagList().size() + ", number of events " + data.getAppointmentList().size() + + data.getCommandsList().size() + ", number of shortcuts"; } } diff --git a/src/main/java/seedu/address/commons/events/model/AppointmentListChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AppointmentListChangedEvent.java new file mode 100644 index 000000000000..6f9bda63e6f3 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/AppointmentListChangedEvent.java @@ -0,0 +1,22 @@ +package seedu.address.commons.events.model; + +import javafx.collections.ObservableList; +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25 +/** + * Indicates the appointment list has changed + */ +public class AppointmentListChangedEvent extends BaseEvent { + public final ObservableList<Appointment> appointmentList; + + public AppointmentListChangedEvent(ObservableList<Appointment> appointmentList) { + this.appointmentList = appointmentList; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ThemeChangeEvent.java b/src/main/java/seedu/address/commons/events/ui/ThemeChangeEvent.java new file mode 100644 index 000000000000..8ceea22be32f --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ThemeChangeEvent.java @@ -0,0 +1,20 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +//@@author Sisyphus25 +/** + * Indicates a request to change them + */ +public class ThemeChangeEvent extends BaseEvent { + public final String theme; + + public ThemeChangeEvent(String theme) { + this.theme = theme; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ToggleCalendarViewEvent.java b/src/main/java/seedu/address/commons/events/ui/ToggleCalendarViewEvent.java new file mode 100644 index 000000000000..7b6697dde1ed --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ToggleCalendarViewEvent.java @@ -0,0 +1,20 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +//@@author Sisyphus25 +/** + * Indicates a request to toggle Calendar view mode + */ +public class ToggleCalendarViewEvent extends BaseEvent { + public final Character viewMode; + + public ToggleCalendarViewEvent(Character viewMode) { + this.viewMode = viewMode; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ToggleListEvent.java b/src/main/java/seedu/address/commons/events/ui/ToggleListEvent.java new file mode 100644 index 000000000000..5f8e4b4b8f6c --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ToggleListEvent.java @@ -0,0 +1,21 @@ +package seedu.address.commons.events.ui; + +//@@author Sisyphus25 + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to toggle List + */ +public class ToggleListEvent extends BaseEvent { + public final String list; + + public ToggleListEvent(String list) { + this.list = list; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..438976537e72 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,10 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; import seedu.address.model.person.Person; +import seedu.address.model.shortcuts.ShortcutDoubles; /** * API of the Logic component @@ -22,6 +25,19 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList<Person> getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of appointments */ + ObservableList<Appointment> getFilteredAppointmentList(); + + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList<Task> getFilteredTaskList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); + + /** Returns the item type of the current active list that is shown in the GUI by the address book*/ + String getCurrentActiveListType(); + + //@@author shanmu9898 + /** Returns an unmodifiable view of the filtered list of Shortcuts */ + ObservableList<ShortcutDoubles> getFilteredShortcutList(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..e307a76cffab 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,7 +11,10 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; import seedu.address.model.person.Person; +import seedu.address.model.shortcuts.ShortcutDoubles; /** * The main LogicManager of the app. @@ -27,7 +30,7 @@ public class LogicManager extends ComponentManager implements Logic { public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + addressBookParser = new AddressBookParser(model.getFilteredCommandsList()); undoRedoStack = new UndoRedoStack(); } @@ -50,8 +53,28 @@ public ObservableList<Person> getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList<Appointment> getFilteredAppointmentList() { + return model.getFilteredAppointmentList(); + } + + @Override + public ObservableList<Task> getFilteredTaskList() { + return model.getFilteredTaskList(); + } + @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + @Override + public String getCurrentActiveListType() { + return model.getCurrentActiveListType(); + } + + @Override + public ObservableList<ShortcutDoubles> getFilteredShortcutList() { + return model.getFilteredCommandsList(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..d835c203d328 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -9,6 +9,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; /** @@ -20,20 +21,22 @@ public class AddCommand extends UndoableCommand { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + "Parameters: " + + " [TYPE] " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " + + "Example: " + COMMAND_WORD + + " student " + + PREFIX_NAME + " Mary Jane " + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_EMAIL + "MJ@example.com " + + PREFIX_ADDRESS + "478, Pasir Ris, #03-12 " + + PREFIX_TAG + "AStar"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; + public static final String MESSAGE_ADD_PERSON_SUCCESS = "New person added: %1$s"; + public static final String MESSAGE_ADD_STUDENT_SUCCESS = "New student added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; private final Person toAdd; @@ -50,8 +53,14 @@ public AddCommand(Person person) { public CommandResult executeUndoableCommand() throws CommandException { requireNonNull(model); try { - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + + if (toAdd instanceof Student) { + model.addStudent((Student) toAdd); + return new CommandResult(String.format(MESSAGE_ADD_STUDENT_SUCCESS, toAdd)); + } else { + model.addPerson(toAdd); + return new CommandResult(String.format(MESSAGE_ADD_PERSON_SUCCESS, toAdd)); + } } catch (DuplicatePersonException e) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } diff --git a/src/main/java/seedu/address/logic/commands/ChangeThemeCommand.java b/src/main/java/seedu/address/logic/commands/ChangeThemeCommand.java new file mode 100644 index 000000000000..633b21d919b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ChangeThemeCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ThemeChangeEvent; +import seedu.address.logic.commands.exceptions.CommandException; + +//@@author Sisyphus25 +/** + * Change theme of the GUI. + */ +public class ChangeThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change the theme of TeachConnect.\n" + + "Parameters: THEME\n" + + "Example: " + COMMAND_WORD + " dark"; + + public static final String MESSAGE_CHANGE_THEME_SUCCESS = "Theme changed"; + + public static final String MESSAGE_INVALID_THEME = "Not a valid theme"; + + private final String theme; + + public ChangeThemeCommand(String theme) { + requireNonNull(theme); + this.theme = theme; + } + + @Override + public CommandResult execute() throws CommandException { + EventsCenter.getInstance().post(new ThemeChangeEvent(theme)); + return new CommandResult(MESSAGE_CHANGE_THEME_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeThemeCommand // instanceof handles nulls + && this.theme.equals(((ChangeThemeCommand) other).theme)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..4a6452f50e88 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -1,6 +1,8 @@ package seedu.address.logic.commands; +import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.Messages; +import seedu.address.commons.events.ui.ToggleListEvent; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; @@ -14,6 +16,7 @@ public abstract class Command { protected CommandHistory history; protected UndoRedoStack undoRedoStack; + /** * Constructs a feedback message to summarise an operation that displayed a listing of persons. * @@ -40,4 +43,13 @@ public static String getMessageForPersonListShownSummary(int displaySize) { public void setData(Model model, CommandHistory history, UndoRedoStack undoRedoStack) { this.model = model; } + + /** + * Set the person list to active and switch to person list view for the GUI. + * Method used to support command that needed the person list to be shown: Find, Select + */ + public void setPersonListActive() { + model.changeCurrentActiveListType(model.LIST_TYPE_CONTACT); + EventsCenter.getInstance().post(new ToggleListEvent(model.LIST_TYPE_CONTACT)); + } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..eebf55eabadd 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,7 +1,5 @@ package seedu.address.logic.commands; -import static java.util.Objects.requireNonNull; - import java.util.List; import java.util.Objects; @@ -9,6 +7,7 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.PersonNotFoundException; /** @@ -24,10 +23,12 @@ public class DeleteCommand extends UndoableCommand { + "Example: " + COMMAND_WORD + " 1"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = "Deleted Student: %1$s"; private final Index targetIndex; private Person personToDelete; + private Student studentToDelete; public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; @@ -36,14 +37,23 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult executeUndoableCommand() { - requireNonNull(personToDelete); - try { - model.deletePerson(personToDelete); - } catch (PersonNotFoundException pnfe) { - throw new AssertionError("The target person cannot be missing"); + if (personToDelete != null && studentToDelete == null) { + try { + model.deletePerson(personToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + } else if (personToDelete == null && studentToDelete != null) { + try { + model.deleteStudent(studentToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, studentToDelete)); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target student cannot be missing"); + } + } else { + throw new NullPointerException(); } - - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); } @Override @@ -54,7 +64,13 @@ protected void preprocessUndoableCommand() throws CommandException { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - personToDelete = lastShownList.get(targetIndex.getZeroBased()); + if (lastShownList.get(targetIndex.getZeroBased()) instanceof Student) { + studentToDelete = (Student) lastShownList.get(targetIndex.getZeroBased()); + personToDelete = null; + } else { + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + studentToDelete = null; + } } @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteShortcutCommand.java b/src/main/java/seedu/address/logic/commands/DeleteShortcutCommand.java new file mode 100644 index 000000000000..fc5568664cdc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteShortcutCommand.java @@ -0,0 +1,55 @@ +////@@author shanmu9898 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +/** + * Deletes a specific shortcut from the addressbook. + */ +public class DeleteShortcutCommand extends UndoableCommand { + public static final String COMMAND_WORD = "delete_shortcut"; + public static final String MESSAGE_USAGE = COMMAND_WORD + " CommandWord " + " ShortcutWord " + + " :Deletes a shortcut for any command word"; + public static final String MESSAGE_DELETE_SHORTCUT_SUCCESS = "The shortcut has been deleted!"; + private final String shortcutWord; + + private final String commandWord; + + private ShortcutDoubles commandShortcut; + + + public DeleteShortcutCommand(String commandWord, String shortcutWord) { + requireNonNull(commandWord); + requireNonNull(shortcutWord); + this.commandWord = commandWord; + this.shortcutWord = shortcutWord; + commandShortcut = new ShortcutDoubles(shortcutWord, commandWord); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(commandShortcut); + try { + model.deleteCommandShortcut(commandShortcut); + } catch (UniqueShortcutDoublesList.CommandShortcutNotFoundException csnf) { + throw new CommandException("Please enter a valid Shortcut Command you have saved"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_SHORTCUT_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteShortcutCommand // instanceof handles nulls + && this.shortcutWord.equals(((DeleteShortcutCommand) other).shortcutWord) // state check + && this.commandWord.equals(((DeleteShortcutCommand) other).commandWord) // state check + && Objects.equals(this.commandShortcut, ((DeleteShortcutCommand) other).commandShortcut)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..f81c221c8f58 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -24,6 +24,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; @@ -49,6 +50,7 @@ public class EditCommand extends UndoableCommand { + PREFIX_EMAIL + "johndoe@example.com"; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_STUDENT_SUCCESS = "Edited Student: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; @@ -57,6 +59,8 @@ public class EditCommand extends UndoableCommand { private Person personToEdit; private Person editedPerson; + private Student studentToEdit; + private Student editedStudent; /** * @param index of the person in the filtered person list to edit @@ -73,14 +77,20 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { @Override public CommandResult executeUndoableCommand() throws CommandException { try { - model.updatePerson(personToEdit, editedPerson); + if (personToEdit != null) { + model.updatePerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + } else { + model.updateStudent(studentToEdit, editedStudent); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EDIT_STUDENT_SUCCESS, editedStudent)); + } } catch (DuplicatePersonException dpe) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } catch (PersonNotFoundException pnfe) { - throw new AssertionError("The target person cannot be missing"); + throw new AssertionError("The target contact cannot be missing"); } - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } @Override @@ -91,8 +101,15 @@ protected void preprocessUndoableCommand() throws CommandException { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - personToEdit = lastShownList.get(index.getZeroBased()); - editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + if (lastShownList.get(index.getZeroBased()) instanceof Student) { + studentToEdit = (Student) lastShownList.get(index.getZeroBased()); + editedStudent = (Student) createEditedPerson(studentToEdit, editPersonDescriptor); + personToEdit = editedPerson = null; + } else { + personToEdit = lastShownList.get(index.getZeroBased()); + editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + studentToEdit = editedStudent = null; + } } /** @@ -108,7 +125,12 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + if (personToEdit instanceof Student) { + return new Student(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + } else { + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + + } } @Override diff --git a/src/main/java/seedu/address/logic/commands/ExportCommand.java b/src/main/java/seedu/address/logic/commands/ExportCommand.java new file mode 100644 index 000000000000..5a8badd3c737 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ExportCommand.java @@ -0,0 +1,316 @@ +//@@author shanmu9898 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG_EXPORT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import javafx.collections.ObservableList; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tag.Tag; +import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.XmlAddressBookStorage; + + +/** + * + * Exports people to an XML file of choice based on tag, index or range + */ +public class ExportCommand extends Command { + + public static final String MESSAGE_FAIL = "TeachConnect faced some error while exporting! Please try again!"; + public static final String MESSAGE_OUT_OF_BOUNDS = "Please check the index bounds!"; + public static final String MESSAGE_SUCCESS = "Contacts have been successfully exported!"; + public static final String MESSAGE_RANGE_ERROR = "Please input valid range"; + public static final String MESSAGE_TAG_CONTACT_MISMATCH = "The tag and contact don't match"; + + public static final String COMMAND_WORD = "export"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": exports contacts to the TeachConnect Book based " + + "on index, range or tag \n" + + "Parameters: " + + PREFIX_NAME + " NAME " + + PREFIX_RANGE + " RANGE " + + PREFIX_TAG_EXPORT + " TAG " + + PREFIX_PATH + " PATH " + + PREFIX_TYPE + "FORMAT \n" + + "Example 1: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "all " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "excel/xml \n" + + "Example 2: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "1 " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "excel/xml \n" + + "Example 3: " + COMMAND_WORD + " " + PREFIX_NAME + "{Name of file} " + PREFIX_RANGE + "1,2 " + + PREFIX_TAG_EXPORT + "friends " + PREFIX_PATH + "{Path to store} " + PREFIX_TYPE + "excel/xml \n"; + + + private Tag tag; + private final String range; + private final String path; + private AddressBook teachConnectBook; + private AddressBookStorage teachConnectStorage; + private final String nameOfExportFile; + private final String type; + private ArrayList<Person> exportAddition = new ArrayList<Person>(); + + /** + * Creates an ExportCommand to export the specified {@code Persons} + */ + public ExportCommand(String range, Tag tag, String path, String nameOfExportFile, String type) { + requireNonNull(range); + requireNonNull(tag); + requireNonNull(path); + requireNonNull(nameOfExportFile); + requireNonNull(type); + + this.range = range; + this.path = path; + this.tag = tag; + this.nameOfExportFile = nameOfExportFile; + this.type = type; + + + teachConnectBook = new AddressBook(); + } + + /** + * Handles exceptions of various messages and takes care of the actual execution of the command. + */ + @Override + public CommandResult execute() throws CommandException { + String[] rangeGiven; + CommandResult handledRangeSituation; + + try { + rangeGiven = handleRange(); + } catch (IOException e) { + return new CommandResult(MESSAGE_RANGE_ERROR); + } + + + try { + handledRangeSituation = handleRangeArray(rangeGiven); + } catch (DuplicatePersonException e) { + return new CommandResult(MESSAGE_FAIL); + } catch (IndexOutOfBoundsException e) { + return new CommandResult(MESSAGE_OUT_OF_BOUNDS); + } + + if (handledRangeSituation != null) { + return handledRangeSituation; + } + + if (!tryStorage(type)) { + return new CommandResult(MESSAGE_FAIL); + } + + return new CommandResult(MESSAGE_SUCCESS); + + } + + /** + * This method saves the file either as an XML file or an CSV file depending on the user preferences. + * @return a boolean values if the storage has been possible or not + */ + private boolean tryStorage(String type) throws CommandException { + if (type.equalsIgnoreCase("xml")) { + teachConnectStorage = new XmlAddressBookStorage(path + "/" + nameOfExportFile + ".xml"); + try { + teachConnectStorage.saveAddressBook(teachConnectBook); + } catch (IOException e) { + return false; + } + + } else if (type.equalsIgnoreCase("excel")) { + return saveAsCsv(); + } + return true; + } + + /** + * Will save as a CSV file using a CSVPrinter including the list of tags + * @return boolean + */ + private boolean saveAsCsv() throws CommandException { + CSVPrinter csvPrinter; + try { + csvPrinter = csvFileToBeWritten(); + } catch (IOException e) { + throw new CommandException(String.format(MESSAGE_FAIL)); + } + + try { + for (Person p : exportAddition) { + csvPrinter.printRecord(p.getName(), p.getEmail(), p.getPhone(), p.getAddress(), p.getTags()); + } + + csvPrinter.flush(); + + } catch (IOException e) { + throw new CommandException(String.format(MESSAGE_FAIL)); + } + return true; + } + + + /** + * Returns CSVPrinter which is the file to which the contents are going to be added. + */ + public CSVPrinter csvFileToBeWritten() throws IOException { + CSVPrinter csvPrinter; + + BufferedWriter writer = Files.newBufferedWriter(Paths.get(path + "/" + nameOfExportFile + ".csv")); + csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader("Name", "Email", "Phone", "Address", "Tags")); + + return csvPrinter; + } + + + /** + * Handles the range array returned by the handleRange() function + * @param rangeGiven + * @return + */ + private CommandResult handleRangeArray(String[] rangeGiven) throws DuplicatePersonException, + IndexOutOfBoundsException, + CommandException { + if (rangeGiven[0].equals("all")) { + exportAllRange(tag); + } else { + if (rangeGiven.length != 1) { + for (int i = 0; i < rangeGiven.length; i++) { + int low = Integer.parseInt(rangeGiven[0]); + int high = Integer.parseInt(rangeGiven[1]); + if (low >= high) { + return new CommandResult(MESSAGE_RANGE_ERROR); + } else { + exportRange(low, high, tag); + } + } + } else { + int low = Integer.parseInt(rangeGiven[0]); + exportSpecific(low, tag); + } + + + } + return null; + } + + /** + * Adds a specific person to the teachConnectBook + * + * parameters are an integer and a tag + * @throws DuplicatePersonException + * @throws IndexOutOfBoundsException + */ + private void exportSpecific(int low, Tag tag) throws DuplicatePersonException, + IndexOutOfBoundsException, + CommandException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + if (exportPeople.get(low - 1).getTags().contains(tag) || tag.equals(new Tag("shouldnotbethistag"))) { + exportAddition.add(exportPeople.get(low - 1)); + teachConnectBook.addPerson(exportPeople.get(low - 1)); + } else { + throw new CommandException(String.format(MESSAGE_TAG_CONTACT_MISMATCH)); + } + + } + + /** + * Exports a range of people based on the tag and the index range given + * + * @param low + * @param high + * @param tag + * @throws DuplicatePersonException + * @throws IndexOutOfBoundsException + */ + private void exportRange(int low, int high, Tag tag) throws DuplicatePersonException, IndexOutOfBoundsException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + exportAddition = new ArrayList<Person>(); + if (tag.equals(new Tag("shouldnotbethistag"))) { + for (int i = low; i < high; i++) { + exportAddition.add(exportPeople.get(i - 1)); + } + teachConnectBook.setPersons(exportAddition); + } else { + for (int i = low; i < high; i++) { + if (exportPeople.get(i - 1).getTags().contains(tag)) { + exportAddition.add(exportPeople.get(i - 1)); + } + + } + } + + teachConnectBook.setPersons(exportAddition); + } + + /** + * Exports all the contacts in the TeachConnect book if contain certain tag + * + * @param tag + * @throws DuplicatePersonException + */ + private void exportAllRange(Tag tag) throws DuplicatePersonException { + ObservableList<Person> exportPeople = model.getFilteredPersonList(); + if (tag.equals(new Tag("shouldnotbethistag"))) { + teachConnectBook.setPersons(exportPeople); + } else { + exportAddition = new ArrayList<Person>(); + for (int i = 0; i < exportPeople.size(); i++) { + if (exportPeople.get(i).getTags().contains(tag)) { + exportAddition.add(exportPeople.get(i)); + } + } + teachConnectBook.setPersons(exportAddition); + } + } + + /** + * Helper method to identify the lower and higher end of the range given + * + * @return rangeStringArray + */ + public String[] handleRange() throws IOException { + String[] rangeStringArray = this.range.split(","); + if (rangeStringArray.length > 2) { + throw new IOException(); + } + return rangeStringArray; + + } + + /** + * + * @param other [in this case ExportCommand] + * @return a boolean value + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof ExportCommand)) { + return false; + } + + ExportCommand e = (ExportCommand) other; + return range.equals(e.range) && path.equals(e.path); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index b1e671f633d2..28fc776e2053 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -23,6 +23,9 @@ public FindCommand(NameContainsKeywordsPredicate predicate) { @Override public CommandResult execute() { + if (!model.getCurrentActiveListType().equals(model.LIST_TYPE_CONTACT)) { + setPersonListActive(); + } model.updateFilteredPersonList(predicate); return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); } diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java new file mode 100644 index 000000000000..78ec01cde81d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; + +import javafx.collections.ObservableList; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.Storage; +import seedu.address.storage.XmlAddressBookStorage; + +//@@author shanmu9898 +/** + * Imports contacts from a different TeachConnect XML file + */ +public class ImportCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "import"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": imports contacts to the address book." + + "Parameters: file location...\n" + + "Example: " + COMMAND_WORD + " main/src/test/data/sandbox/somerandomfile.xml"; + public static final String MESSAGE_SUCCESS = "%1$s contacts have been successfully imported " + + "and %2$s have been left out!"; + protected static final String MESSAGE_INVALID_FILE = "Please input a valid file location"; + protected Storage storage; + private AddressBook addressBookImported; + private AddressBookStorage addressBookStorage; + private String filePath; + private int numberAdded = 0; + private int numberNotAdded = 0; + + /** + * Creates an ImportCommand to import the specified TeachConnect XML file + */ + public ImportCommand(String importPath) { + requireNonNull(importPath); + this.filePath = importPath; + addressBookStorage = new XmlAddressBookStorage(filePath); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + if (addressBookStorage.readAddressBook(filePath).isPresent()) { + this.addressBookImported = new AddressBook(addressBookStorage.readAddressBook().get()); + ObservableList<Person> people = addressBookImported.getPersonList(); + for (int i = 0; i < people.size(); i++) { + try { + model.addPerson(people.get(i)); + numberAdded++; + } catch (DuplicatePersonException e) { + numberNotAdded++; + } + } + } else { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } + } catch (DataConversionException e) { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } catch (IOException e) { + throw new CommandException(String.format(MESSAGE_INVALID_FILE)); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, numberAdded, numberNotAdded)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ImportCommand // instanceof handles nulls + && filePath.equals(((ImportCommand) other).filePath)); + } + + +} + + + + diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..58a1fc3616ff 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,6 +1,11 @@ package seedu.address.logic.commands; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ONLY_STUDENTS; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ToggleListEvent; +import seedu.address.logic.commands.exceptions.CommandException; /** * Lists all persons in the address book to the user. @@ -9,12 +14,69 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all "; + + public static final String TYPE_CONTACT = "contacts"; + public static final String TYPE_STUDENT = "students"; + public static final String TYPE_APPOINTMENT = "appointments"; + public static final String TYPE_TASK = "tasks"; + public static final String TYPE_SHORTCUT = "shortcuts"; + private static final String MESSAGE_INVALID_TYPE = "TYPE is missing or invalid"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists selected type. \n" + + "Parameter: TYPE\n" + + "Example: " + COMMAND_WORD + " appointments \n" + + "Example: " + COMMAND_WORD + " tasks \n" + + "Example: " + COMMAND_WORD + " shortcuts \n"; + + private final String type; + + public ListCommand(String type) { + this.type = type; + } + + //@@author Sisyphus25 + @Override + public CommandResult execute() throws CommandException { + switch (type) { + case TYPE_CONTACT: + evokeToggleListEvent(TYPE_CONTACT); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS + TYPE_CONTACT); + case TYPE_STUDENT: + evokeToggleListEvent(TYPE_CONTACT); + model.updateFilteredPersonList(PREDICATE_SHOW_ONLY_STUDENTS); + return new CommandResult(MESSAGE_SUCCESS + TYPE_STUDENT); + + case TYPE_APPOINTMENT: + evokeToggleListEvent(TYPE_APPOINTMENT); + return new CommandResult(MESSAGE_SUCCESS + TYPE_APPOINTMENT); + + case TYPE_TASK: + evokeToggleListEvent(TYPE_TASK); + return new CommandResult(MESSAGE_SUCCESS + TYPE_TASK); + + case TYPE_SHORTCUT: + model.changeCurrentActiveListType(TYPE_SHORTCUT); + EventsCenter.getInstance().post(new ToggleListEvent(TYPE_SHORTCUT)); + return new CommandResult(MESSAGE_SUCCESS + TYPE_SHORTCUT); + + default: + throw new CommandException(MESSAGE_INVALID_TYPE); + } + } + + private void evokeToggleListEvent(String type) { + model.changeCurrentActiveListType(type); + EventsCenter.getInstance().post(new ToggleListEvent(type)); + } + //@@author @Override - public CommandResult execute() { - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCommand // instanceof handles nulls + && this.type.equals(((ListCommand) other).type)); // state check } } diff --git a/src/main/java/seedu/address/logic/commands/RemoveCommand.java b/src/main/java/seedu/address/logic/commands/RemoveCommand.java new file mode 100644 index 000000000000..f426799a6ab8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveCommand.java @@ -0,0 +1,86 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.LIST_TYPE_APPOINTMENT; +import static seedu.address.model.Model.LIST_TYPE_TASK; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.EventNotFoundException; + +//@@author Sisyphus25 +/** + * Remove an appointment or task identified using its last displayed index from the address book. + */ +public class RemoveCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "remove"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes the event identified by the index number used in the last event listing.\n" + + "Parameters: " + + " EVENT_TYPE (could be appointment or task)" + + "INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " appointment " + " 1"; + + public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Removed %1$s: %2$s"; + + private final Index targetIndex; + + private String eventTypeOfDeletedTarget; + + private Object eventToBeDeleted; + + public RemoveCommand(Index targetIndex, String eventTypeOfDeletedTarget) { + this.eventTypeOfDeletedTarget = eventTypeOfDeletedTarget; + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(eventToBeDeleted); + try { + if (eventTypeOfDeletedTarget.equals(LIST_TYPE_APPOINTMENT)) { + model.deleteAppointment((Appointment) eventToBeDeleted); + } else if (eventTypeOfDeletedTarget.equals(LIST_TYPE_TASK)) { + model.deleteTask((Task) eventToBeDeleted); + } + } catch (EventNotFoundException ive) { + throw new AssertionError(String.format("The target %s cannot be missing", eventTypeOfDeletedTarget)); + } + return new CommandResult( + String.format(MESSAGE_DELETE_EVENT_SUCCESS, eventTypeOfDeletedTarget, eventToBeDeleted)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (eventTypeOfDeletedTarget.equals(LIST_TYPE_APPOINTMENT)) { + List<Appointment> lastShownList = model.getFilteredAppointmentList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + eventToBeDeleted = lastShownList.get(targetIndex.getZeroBased()); + } else if (eventTypeOfDeletedTarget.equals(LIST_TYPE_TASK)) { + List<Task> lastShownList = model.getFilteredTaskList(); + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + eventToBeDeleted = lastShownList.get(targetIndex.getZeroBased()); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveCommand // instanceof handles nulls + && this.targetIndex.equals(((RemoveCommand) other).targetIndex) // state check + && Objects.equals(this.eventToBeDeleted, ((RemoveCommand) other).eventToBeDeleted)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index 9e3840a9dde6..fe656048d170 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -31,7 +31,9 @@ public SelectCommand(Index targetIndex) { @Override public CommandResult execute() throws CommandException { - + if (!model.getCurrentActiveListType().equals(model.LIST_TYPE_CONTACT)) { + setPersonListActive(); + } List<Person> lastShownList = model.getFilteredPersonList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { diff --git a/src/main/java/seedu/address/logic/commands/SetAppointmentCommand.java b/src/main/java/seedu/address/logic/commands/SetAppointmentCommand.java new file mode 100644 index 000000000000..cc9854a11dc7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SetAppointmentCommand.java @@ -0,0 +1,103 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON_TO_MEET_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.PersonToMeet; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.person.Person; + +//@@author Sisyphus25 +/** + * Adds an appointment with the person at {@code index} in the person list to the address book. + */ +public class SetAppointmentCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "set_appointment"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds an appoinment to the address book.\n" + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_START_TIME + "START-DATE START-TIME " + + PREFIX_END_TIME + "END-DATE END-TIME " + + PREFIX_PERSON_TO_MEET_INDEX + "PERSON TO MEET\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Meet James " + + PREFIX_START_TIME + "20/05/2018 10:00 " + + PREFIX_END_TIME + "20/05/2018 12:00 " + + PREFIX_PERSON_TO_MEET_INDEX + "3 "; + + public static final String MESSAGE_SUCCESS = "New appointment added: %1$s"; + public static final String MESSAGE_DUPLICATE_APPOINTMENT = "This appointment already exists in the address book"; + + private final Appointment baseAppointmentWithoutPerson; + private final Index index; + + private PersonToMeet personToMeet; + + /** + * Creates a SetAppointmentCommand without any PersonToMeet + */ + public SetAppointmentCommand(Appointment baseAppointmentWithoutPerson) { + this(baseAppointmentWithoutPerson, null); + } + + /** + * Creates a SetAppointmentCommand to add the specified {@code Appointment} + */ + public SetAppointmentCommand(Appointment baseAppointmentWithoutPerson, Index index) { + requireNonNull(baseAppointmentWithoutPerson); + this.baseAppointmentWithoutPerson = baseAppointmentWithoutPerson; + this.index = index; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + Appointment toAdd; + if (personToMeet != null) { + toAdd = new Appointment(baseAppointmentWithoutPerson.getTitle(), baseAppointmentWithoutPerson.getTime(), + baseAppointmentWithoutPerson.getEndTime(), personToMeet); + } else { + toAdd = baseAppointmentWithoutPerson; + } + model.addAppointment(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateEventException e) { + throw new CommandException(MESSAGE_DUPLICATE_APPOINTMENT); + } + + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + if (index != null) { + List<Person> lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person person = lastShownList.get(index.getZeroBased()); + personToMeet = new PersonToMeet(person.getName().fullName, person.getEmail().value); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SetAppointmentCommand // instanceof handles nulls + && baseAppointmentWithoutPerson.equals(((SetAppointmentCommand) other).baseAppointmentWithoutPerson)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SetTaskCommand.java b/src/main/java/seedu/address/logic/commands/SetTaskCommand.java new file mode 100644 index 000000000000..1f9d612c9bb9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SetTaskCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; + +//@@author Sisyphus25 +/** + * Adds a task to the address book. + */ +public class SetTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "set_task"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to the address book.\n" + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_END_TIME + "DATE TIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Mark papers " + + PREFIX_END_TIME + "20/05/2018 12:00 "; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book"; + + private final Task toAdd; + + /** + * Creates a SetTaskCommand to add the specified {@code Task} + */ + public SetTaskCommand(Task task) { + requireNonNull(task); + toAdd = task; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateEventException e) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SetTaskCommand // instanceof handles nulls + && toAdd.equals(((SetTaskCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ShortcutCommand.java b/src/main/java/seedu/address/logic/commands/ShortcutCommand.java new file mode 100644 index 000000000000..58377342b8b4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShortcutCommand.java @@ -0,0 +1,100 @@ +//@@author shanmu9898 +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +/** + * + */ +public class ShortcutCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "shortcut"; + public static final String MESSAGE_USAGE = COMMAND_WORD + " CommandWord " + " ShortcutWord " + + " :Creates a shortcut for any command word \n" + + "Example: " + COMMAND_WORD + " list l"; + public static final String MESSAGE_SHORTCUT_AVAILABLE = "This shortcut already exists!"; + public static final String MESSAGE_SUCCESS = "Successfully added the shortcut"; + public static final String MESSAGE_NO_COMMAND_TO_MAP = "The command statement is invalid and hence cant be mapped!"; + + private final String shortcutWord; + + private final String commandWord; + + private List<ShortcutDoubles> commandsList; + + private final String[] commandsPresent = {"add", "clear", "theme", "delete", "edit", "exit", "export", "find", + "help", "history", "import", "list", "redo", "undo", "select", + "set_appointment", "set_task", "shortcut", "undo", "calendar", + "delete_shortcut", "remove"}; + + public ShortcutCommand(String commandWord, String shortcutWord) { + requireNonNull(commandWord); + requireNonNull(shortcutWord); + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + commandsList = model.getFilteredCommandsList(); + if (commandsList != null) { + if (checkIfCommandPresent()) { + return new CommandResult(String.format(MESSAGE_SHORTCUT_AVAILABLE)); + } + + } + + ShortcutDoubles toAdd = new ShortcutDoubles(shortcutWord, commandWord); + try { + model.addCommandShortcut(toAdd); + } catch (UniqueShortcutDoublesList.DuplicateShortcutDoublesException e) { + return new CommandResult(String.format(MESSAGE_SHORTCUT_AVAILABLE)); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + /** + * Checks if the shortcut command is valid or not + * @return whether true or false + */ + private boolean checkIfCommandPresent() throws CommandException { + if (!containsKeyWord(commandWord) || containsKeyWord(shortcutWord)) { + throw new CommandException(MESSAGE_NO_COMMAND_TO_MAP); + } + for (ShortcutDoubles s : commandsList) { + if (s.shortcutWord.equals(shortcutWord)) { + return true; + } + } + return false; + } + + /** + * Checks if the command word is in the Array of commands present + * @param commandWord + * @return whether true if the command is present in the command word list or false otherwise + */ + private boolean containsKeyWord(String commandWord) { + for (String s : commandsPresent) { + if (s.equals(commandWord)) { + return true; + } + } + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShortcutCommand // instanceof handles nulls + && this.shortcutWord.equals(((ShortcutCommand) other).shortcutWord) // state check + && this.commandWord.equals(((ShortcutCommand) other).commandWord)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 000000000000..1eb7f76dd222 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,19 @@ +package seedu.address.logic.commands; + +/** + * Sorts the current list in lexographic order + + */ + +public class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts the current list in lexographic order " + "Example: " + COMMAND_WORD; + public static final String MESSAGE_SUCCESS = "List Sorted"; + + @Override + public CommandResult execute() { + model.sortFilteredPersonList(); + + return new CommandResult(MESSAGE_SUCCESS); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/address/logic/commands/ToggleCalendarViewCommand.java b/src/main/java/seedu/address/logic/commands/ToggleCalendarViewCommand.java new file mode 100644 index 000000000000..f3e74936f536 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ToggleCalendarViewCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ToggleCalendarViewEvent; +import seedu.address.logic.commands.exceptions.CommandException; + +//@@author Sisyphus25 +/** + * Command to change calendar view + */ +public class ToggleCalendarViewCommand extends Command { + + public static final String COMMAND_WORD = "calendar"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Toggles calendar view. \n" + + "Parameter: VIEW_MODE\n" + + "View mode: Day view: d, Week view: w, Month view: m\n" + + "Example: " + COMMAND_WORD + " d"; + + public static final String MESSAGE_VIEW_TOGGLE_SUCCESS = "View changed."; + + private Character viewMode; + + public ToggleCalendarViewCommand(Character viewMode) { + requireNonNull(viewMode); + this.viewMode = viewMode; + } + @Override + public CommandResult execute() throws CommandException { + EventsCenter.getInstance().post(new ToggleCalendarViewEvent(viewMode)); + return new CommandResult(MESSAGE_VIEW_TOGGLE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ToggleCalendarViewCommand // instanceof handles nulls + && this.viewMode == ((ToggleCalendarViewCommand) other).viewMode); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3c729b388554..6fb263a1fc7c 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -18,6 +18,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Student; import seedu.address.model.tag.Tag; /** @@ -25,6 +26,8 @@ */ public class AddCommandParser implements Parser<AddCommand> { + public static final String MESSAGE_INVALID_TYPE = "Type must be student or defualt(empty)."; + /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. @@ -34,8 +37,9 @@ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + String[] preambleArgs = argMultimap.getPreamble().split(" "); if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + || preambleArgs.length > 1) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -46,9 +50,17 @@ public AddCommand parse(String args) throws ParseException { Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + switch (preambleArgs[0]) { + case "": + return new AddCommand(new Person(name, phone, email, address, tagList)); + + case "student": + return new AddCommand(new Student(name, phone, email, address, tagList)); + + default: + throw new IllegalValueException(MESSAGE_INVALID_TYPE); + } - return new AddCommand(person); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..8e9902c62c9d 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -3,23 +3,36 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ChangeThemeCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteShortcutCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.ExportCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ImportCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SetAppointmentCommand; +import seedu.address.logic.commands.SetTaskCommand; +import seedu.address.logic.commands.ShortcutCommand; +import seedu.address.logic.commands.ToggleCalendarViewCommand; import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.SortCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.shortcuts.ShortcutDoubles; + /** * Parses user input. @@ -30,6 +43,12 @@ public class AddressBookParser { * Used for initial separation of command word and args. */ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); + private List<ShortcutDoubles> shortcutDoubles; + + public AddressBookParser(List<ShortcutDoubles> shortcutDoubles) { + this.shortcutDoubles = shortcutDoubles; + } + public AddressBookParser(){} /** * Parses user input into command for execution. @@ -38,14 +57,25 @@ public class AddressBookParser { * @return the command based on the user input * @throws ParseException if the user input does not conform the expected format */ - public Command parseCommand(String userInput) throws ParseException { + public Command parseCommand(String userInput) throws ParseException { final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + + if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } - final String commandWord = matcher.group("commandWord"); + + String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); + if (shortcutDoubles != null) { + for (ShortcutDoubles s : shortcutDoubles) { + if (s.shortcutWord.equals(commandWord)) { + commandWord = s.commandWord; + } + } + } + switch (commandWord) { case AddCommand.COMMAND_WORD: @@ -67,7 +97,7 @@ public Command parseCommand(String userInput) throws ParseException { return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); @@ -84,9 +114,41 @@ public Command parseCommand(String userInput) throws ParseException { case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case SetAppointmentCommand.COMMAND_WORD: + return new SetAppointmentCommandParser().parse(arguments); + + case SetTaskCommand.COMMAND_WORD: + return new SetTaskCommandParser().parse(arguments); + + case ImportCommand.COMMAND_WORD: + return new ImportCommandParser().parse(arguments); + + case ExportCommand.COMMAND_WORD: + return new ExportCommandParser().parse(arguments); + + case ShortcutCommand.COMMAND_WORD: + return new ShortcutCommandParser().parse(arguments); + + case ToggleCalendarViewCommand.COMMAND_WORD: + return new ToggleCalendarViewParser().parse(arguments); + + case ChangeThemeCommand.COMMAND_WORD: + return new ChangeThemeCommandParser().parse(arguments); + + case RemoveCommand.COMMAND_WORD: + return new RemoveCommandParser().parse(arguments); + + case DeleteShortcutCommand.COMMAND_WORD: + return new DeleteShortcutCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommand(); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } + + } diff --git a/src/main/java/seedu/address/logic/parser/ChangeThemeCommandParser.java b/src/main/java/seedu/address/logic/parser/ChangeThemeCommandParser.java new file mode 100644 index 000000000000..89e2bbe2971e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ChangeThemeCommandParser.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ParserUtil.THEME_LIST; + +import java.util.Arrays; + +import seedu.address.logic.commands.ChangeThemeCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author Sisyphus25 +/** + * Parses input arguments and creates a new ChangeThemeCommand object + */ +public class ChangeThemeCommandParser implements Parser<ChangeThemeCommand> { + /** + * Parses the given {@code viewMode} of arguments in the context of the ChangeThemeCommandParser + * and returns an ChangeThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ChangeThemeCommand parse(String args) throws ParseException { + String theme = args.trim(); + if (!isValidTheme(theme)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ChangeThemeCommand.MESSAGE_INVALID_THEME)); + } + return new ChangeThemeCommand(theme); + } + + /** + * + * @param theme + * @return whether if {@code theme} is a valid theme name + */ + private boolean isValidTheme(String theme) { + return !theme.isEmpty() && Arrays.asList(THEME_LIST).contains(theme); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..293e081f1024 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,12 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_TITLE = new Prefix("t/"); + public static final Prefix PREFIX_START_TIME = new Prefix("s/"); + public static final Prefix PREFIX_END_TIME = new Prefix("e/"); + public static final Prefix PREFIX_PERSON_TO_MEET_INDEX = new Prefix("i/"); + public static final Prefix PREFIX_PATH = new Prefix("p/"); + public static final Prefix PREFIX_TAG_EXPORT = new Prefix("t/"); + public static final Prefix PREFIX_RANGE = new Prefix("r/"); + public static final Prefix PREFIX_TYPE = new Prefix("te/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteShortcutCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteShortcutCommandParser.java new file mode 100644 index 000000000000..03ac5a50ca1d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteShortcutCommandParser.java @@ -0,0 +1,31 @@ +//@@author shanmu9898 +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DeleteShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteShortcutCommand object + */ +public class DeleteShortcutCommandParser implements Parser<DeleteShortcutCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteShortcutCommand + * and returns a DeleteShortcutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteShortcutCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] splitWords = trimmedArgs.split(" "); + if (splitWords.length > 2 || splitWords.length < 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteShortcutCommand.MESSAGE_USAGE)); + } else { + return new DeleteShortcutCommand(splitWords[0], splitWords[1]); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ExportCommandParser.java b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java new file mode 100644 index 000000000000..bda4dea579e2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java @@ -0,0 +1,67 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG_EXPORT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.ExportCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +import seedu.address.model.tag.Tag; + +//@@author shanmu9898 +/** + * Parses input arguments and creates a new ExportCommand object + */ +public class ExportCommandParser implements Parser<ExportCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ExportCommand + * and returns an ExportCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ExportCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultiMap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_RANGE, + PREFIX_TAG_EXPORT, PREFIX_PATH, PREFIX_TYPE); + + String[] preambleArgs = argMultiMap.getPreamble().split(" "); + if (!arePrefixesPresent(argMultiMap, PREFIX_NAME, PREFIX_RANGE, PREFIX_PATH, PREFIX_TYPE) + || preambleArgs.length > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } + + String name = argMultiMap.getValue(PREFIX_NAME).orElse(""); + String range = argMultiMap.getValue(PREFIX_RANGE).orElse("all"); + String tag = argMultiMap.getValue(PREFIX_TAG_EXPORT).orElse("shouldnotbethistag"); + String path = argMultiMap.getValue(PREFIX_PATH).orElse(""); + String type = argMultiMap.getValue(PREFIX_TYPE).orElse("normal"); + + if (!(type.equalsIgnoreCase("excel") || type.equalsIgnoreCase("xml"))) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } + + Tag tagExport = new Tag(tag); + return new ExportCommand(range, tagExport, path, name, type); + + + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + + +} diff --git a/src/main/java/seedu/address/logic/parser/ImportCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java new file mode 100644 index 000000000000..0a33a9b145c4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java @@ -0,0 +1,41 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ImportCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author shanmu9898 +/** + * Parses input arguments and creates a new ImportCommand object + */ +public class ImportCommandParser implements Parser<ImportCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ImportCommand + * and returns an ImportCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ImportCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } + + String[] parameterGetterArray = trimmedArgs.split(" "); + + if (parameterGetterArray.length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } else { + return new ImportCommand(parameterGetterArray[0]); + } + + } + + + +} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 000000000000..d5d419db8600 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author Sisyphus25 +/** + * Parser for ListCommand + */ +public class ListCommandParser implements Parser<ListCommand> { + /** + * Parses the given {@code args} of arguments in the context of the ListCommandParser + * and returns an ListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListCommand parse(String args) throws ParseException { + String item = args.trim(); + if (!isValidItem(item)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ListCommand.MESSAGE_USAGE)); + } + return new ListCommand(item); + } + + /** + * @param str + * @return whether if the string is a valid view mode or not + */ + private boolean isValidItem(String str) { + switch (str) { + case(ListCommand.TYPE_CONTACT): + case(ListCommand.TYPE_STUDENT): + case(ListCommand.TYPE_APPOINTMENT): + case(ListCommand.TYPE_TASK): + case(ListCommand.TYPE_SHORTCUT): + return true; + default: + return false; + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..e4ac8263c885 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -10,6 +10,8 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -29,6 +31,7 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; + public static final String[] THEME_LIST = {"dark", "light", "doge", "galaxy"}; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -43,6 +46,16 @@ public static Index parseIndex(String oneBasedIndex) throws IllegalValueExceptio return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code Optional<String> onebasedIndex} into an {@code Optional<Index>} + * if {@code onebasedIndex} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Index> parseIndex(Optional<String> oneBasedIndex) throws IllegalValueException { + requireNonNull(oneBasedIndex); + return oneBasedIndex.isPresent() ? Optional.of(parseIndex(oneBasedIndex.get())) : Optional.empty(); + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -139,6 +152,50 @@ public static Optional<Email> parseEmail(Optional<String> email) throws IllegalV return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); } + //@@author Sisyphus25 + /** + * Parses a {@code Optional<String> title} into an {@code Optional<Title>} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Title> parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional<String> eventTime} into an {@code Optional<EventTime>} if {@code eventTime} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<EventTime> parseEventTime(Optional<String> eventTime) throws IllegalArgumentException { + requireNonNull(eventTime); + return eventTime.isPresent() ? Optional.of(parseEventTime(eventTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String eventTime} into a {@code EventTime}. + * Leading and trailing whitespaces will be trimmed. + */ + public static EventTime parseEventTime(String eventTime) throws IllegalArgumentException { + requireNonNull(eventTime); + String trimmedEventTime = eventTime.trim(); + return new EventTime(trimmedEventTime); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -149,7 +206,7 @@ public static Tag parseTag(String tag) throws IllegalValueException { requireNonNull(tag); String trimmedTag = tag.trim(); if (!Tag.isValidTagName(trimmedTag)) { - throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); + throw new IllegalValueException(Tag.MESSAGE_TAG_NAME_CONSTRAINTS); } return new Tag(trimmedTag); } diff --git a/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java new file mode 100644 index 000000000000..a61785ed5b82 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemoveCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author Sisyphus25 +/** + * Parses input arguments and creates a new RemoveCommand object + */ +public class RemoveCommandParser implements Parser<RemoveCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommand + * and returns an RemoveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] parameterGetterArray = trimmedArgs.split(" "); + if (trimmedArgs.isEmpty() || parameterGetterArray.length != 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + try { + if (!isValidEventType(parameterGetterArray[0])) { + throw new IllegalValueException("Invalid event type"); + } + Index index = ParserUtil.parseIndex(parameterGetterArray[1]); + return new RemoveCommand(index, parameterGetterArray[0]); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } + } + + private boolean isValidEventType(String type) { + return type.equals("appointment") || type.equals("task"); + } +} + + + diff --git a/src/main/java/seedu/address/logic/parser/SetAppointmentCommandParser.java b/src/main/java/seedu/address/logic/parser/SetAppointmentCommandParser.java new file mode 100644 index 000000000000..db20e97dc94d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SetAppointmentCommandParser.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PERSON_TO_MEET_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.SetAppointmentCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +/** + * Parses input arguments and creates a new SetAppointmentCommand object + */ +public class SetAppointmentCommandParser implements Parser<SetAppointmentCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the SetAppointmentCommand + * and returns a SetAppointmentCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetAppointmentCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_START_TIME, + PREFIX_END_TIME, PREFIX_PERSON_TO_MEET_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_START_TIME, PREFIX_END_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SetAppointmentCommand.MESSAGE_USAGE)); + } + + try { + Index index = null; + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + EventTime startTime = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_START_TIME)).get(); + EventTime endTime = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_END_TIME)).get(); + Optional<Index> optionalIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PERSON_TO_MEET_INDEX)); + if (optionalIndex.isPresent()) { + index = optionalIndex.get(); + } + Appointment appointment = new Appointment(title, startTime, endTime); + + return new SetAppointmentCommand(appointment, index); + } catch (IllegalValueException | IllegalArgumentException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/SetTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/SetTaskCommandParser.java new file mode 100644 index 000000000000..323e5f41cc00 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SetTaskCommandParser.java @@ -0,0 +1,57 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.SetTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +/** + * Parses input arguments and creates a new SetTaskCommand object + */ +public class SetTaskCommandParser implements Parser<SetTaskCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the SetTaskCommand + * and returns a SetTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SetTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_END_TIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_END_TIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + SetTaskCommand.MESSAGE_USAGE)); + } + + try { + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + EventTime time = ParserUtil.parseEventTime(argMultimap.getValue(PREFIX_END_TIME)).get(); + + Task task = new Task(title, time); + + return new SetTaskCommand(task); + } catch (IllegalValueException | IllegalArgumentException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ShortcutCommandParser.java b/src/main/java/seedu/address/logic/parser/ShortcutCommandParser.java new file mode 100644 index 000000000000..7075d73da40a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ShortcutCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author shanmu9898 +/** + * Parser + */ +public class ShortcutCommandParser implements Parser<ShortcutCommand> { + /** + * Parses the given {@code String} of arguments in the context of the ShortcutCommand + * and returns an ShortcutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShortcutCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + String[] splitWords = trimmedArgs.split(" "); + if (splitWords.length > 2 || splitWords.length < 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShortcutCommand.MESSAGE_USAGE)); + } else { + return new ShortcutCommand(splitWords[0], splitWords[1]); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ToggleCalendarViewParser.java b/src/main/java/seedu/address/logic/parser/ToggleCalendarViewParser.java new file mode 100644 index 000000000000..170aab936022 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ToggleCalendarViewParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ToggleCalendarViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author Sisyphus25 +/** + * Parser for ToggleCalendarViewCommand + */ +public class ToggleCalendarViewParser implements Parser<ToggleCalendarViewCommand> { + /** + * Parses the given {@code viewMode} of arguments in the context of the ToggleCalendarViewParser + * and returns an ToggleCalendarViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ToggleCalendarViewCommand parse(String args) throws ParseException { + String viewMode = args.trim(); + if (viewMode.isEmpty() || !isValidViewMode(viewMode)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ToggleCalendarViewCommand.MESSAGE_USAGE)); + } + return new ToggleCalendarViewCommand(viewMode.charAt(0)); + } + + /** + * + * @param str + * @return whether if the string is a valid view mode or not + */ + private boolean isValidViewMode(String str) { + if (str.length() != 1) { + return false; + } + switch (str.charAt(0)) { + case('w'): + case('d'): + case('m'): + return true; + default: + return false; + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index f8d0260de159..cb726486f9dd 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -11,10 +11,20 @@ import java.util.stream.Collectors; import javafx.collections.ObservableList; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.UniqueEventList; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.person.UniqueContactList; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.person.UniqueStudentList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; @@ -24,8 +34,13 @@ */ public class AddressBook implements ReadOnlyAddressBook { + private final UniqueContactList contacts; private final UniquePersonList persons; + private final UniqueStudentList students; private final UniqueTagList tags; + private final UniqueEventList<Appointment> appointments; + private final UniqueEventList<Task> tasks; + private final UniqueShortcutDoublesList shorcutCommands; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -36,7 +51,12 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + students = new UniqueStudentList(); + contacts = new UniqueContactList(persons, students); tags = new UniqueTagList(); + appointments = new UniqueEventList<>(); + tasks = new UniqueEventList<>(); + shorcutCommands = new UniqueShortcutDoublesList(); } public AddressBook() {} @@ -49,30 +69,65 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { resetData(toBeCopied); } - //// list overwrite operations + //// list operations public void setPersons(List<Person> persons) throws DuplicatePersonException { this.persons.setPersons(persons); } + public void setStudents(List<Student> students) throws DuplicatePersonException { + this.students.setStudents(students); + } + public void setTags(Set<Tag> tags) { this.tags.setTags(tags); } + public void setAppointments(List<Appointment> appointments) + throws DuplicateEventException { + this.appointments.setEvents(appointments); + } + + //@@author shanmu9898 + public void setShorcutCommands(List<ShortcutDoubles> shorcutCommands) { + this.shorcutCommands.setCommandsList(shorcutCommands); + } + //@@author + + public void setTasks(List<Task> tasks) + throws DuplicateEventException { + this.tasks.setEvents(tasks); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setTags(new HashSet<>(newData.getTagList())); - List<Person> syncedPersonList = newData.getPersonList().stream() - .map(this::syncWithMasterTagList) - .collect(Collectors.toList()); + List<ShortcutDoubles> commandsList = newData.getCommandsList(); + List<Appointment> appointmentList = newData.getAppointmentList(); + List<Task> taskList = newData.getTaskList(); + List<Person> syncedContactList = newData.getContactList().stream() + .map(this::syncWithMasterTagList).collect(Collectors.toList()); try { - setPersons(syncedPersonList); + setShorcutCommands(commandsList); + setAppointments(appointmentList); + setTasks(taskList); + persons.setPersons(new UniquePersonList()); + students.setStudents(new UniqueStudentList()); + for (Person contact : syncedContactList) { + if (contact instanceof Student) { + addStudent((Student) contact); + } else { + addPerson(contact); + } + } } catch (DuplicatePersonException e) { - throw new AssertionError("AddressBooks should not have duplicate persons"); + throw new AssertionError("TeachConnect should not have duplicate persons"); + } catch (DuplicateEventException e) { + throw new AssertionError("TeachConnect should not have duplicate events"); } } @@ -87,10 +142,38 @@ public void resetData(ReadOnlyAddressBook newData) { */ public void addPerson(Person p) throws DuplicatePersonException { Person person = syncWithMasterTagList(p); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.add(person); + if (!students.contains(new Student(person.getName(), person.getPhone(), person.getEmail(), + person.getAddress(), person.getTags()))) { + try { + persons.add(person); + } catch (DuplicatePersonException e) { + removeUnusedTags(); + throw e; + } + } else { + throw new DuplicatePersonException(); + } + } + + /** + * Adds a student to the address book. + * Also checks the new student's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the student to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent student already exists. + */ + public void addStudent(Student s) throws DuplicatePersonException { + Student student = (Student) syncWithMasterTagList(s); + if (!persons.contains(student)) { + try { + students.add(student); + } catch (DuplicatePersonException e) { + removeUnusedTags(); + throw e; + } + } else { + throw new DuplicatePersonException(); + } } /** @@ -108,16 +191,58 @@ public void updatePerson(Person target, Person editedPerson) requireNonNull(editedPerson); Person syncedEditedPerson = syncWithMasterTagList(editedPerson); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.setPerson(target, syncedEditedPerson); + if (!students.contains(new Student(syncedEditedPerson.getName(), syncedEditedPerson.getPhone(), + syncedEditedPerson.getEmail(), syncedEditedPerson.getAddress(), syncedEditedPerson.getTags()))) { + try { + persons.setPerson(target, syncedEditedPerson); + } finally { + removeUnusedTags(); + } + } else { + throw new DuplicatePersonException(); + } + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code AddressBook}'s tag list will be updated with the tags of {@code editedStudent}. + * + * @throws DuplicatePersonException if updating the student's details causes the student to be equivalent to + * another existing person in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + * + * @see #syncWithMasterTagList(Person) + */ + public void updateStudent(Student target, Student editedStudent) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedStudent); + + Student syncedEditedStudent = (Student) syncWithMasterTagList(editedStudent); + if (!persons.contains(syncedEditedStudent)) { + try { + students.setStudent(target, syncedEditedStudent); + } finally { + removeUnusedTags(); + } + } else { + throw new DuplicatePersonException(); + } + } + + /** + * Removes all {@code Tag}s that are not used by any {@code Person} or {@code Student} in this {@code AddressBook}. + */ + private void removeUnusedTags() { + Set<Tag> tagsInContacts = contacts.asObservableList().stream().map(Person::getTags).flatMap(Set::stream) + .collect(Collectors.toSet()); + + tags.setTags(tagsInContacts); } /** - * Updates the master tag list to include tags in {@code person} that are not in the list. - * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master - * list. + * Updates the master tag list to include tags in {@code person} or {@code student} that are not in the list. + * @return a copy of this {@code person} or {@code student} such that every tag in this person points to a Tag + * object in the master list. */ private Person syncWithMasterTagList(Person person) { final UniqueTagList personTags = new UniqueTagList(person.getTags()); @@ -131,8 +256,14 @@ private Person syncWithMasterTagList(Person person) { // Rebuild the list of person tags to point to the relevant tags in the master tag list. final Set<Tag> correctTagReferences = new HashSet<>(); personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); - return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + + if (person instanceof Student) { + return new Student( + person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + } else { + return new Person( + person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + } } /** @@ -147,17 +278,55 @@ public boolean removePerson(Person key) throws PersonNotFoundException { } } + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeStudent(Student key) throws PersonNotFoundException { + if (students.remove(key)) { + return true; + } else { + throw new PersonNotFoundException(); + } + } + //@@author shanmu9898 + /** + * + * @param commandShortcut + * @return a boolean variable + * @throws UniqueShortcutDoublesList.CommandShortcutNotFoundException + */ + public boolean removeShortcutDouble(ShortcutDoubles commandShortcut) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + if (shorcutCommands.remove(commandShortcut)) { + return true; + } else { + throw new UniqueShortcutDoublesList.CommandShortcutNotFoundException(); + } + } + //author + //// tag-level operations public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { tags.add(t); } + //@@author shanmu9898 + public void addShortcutDoubles(ShortcutDoubles s) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + shorcutCommands.add(s); + } + //@@author //// util methods @Override public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + return persons.asObservableList().size() + " persons, " + + students.asObservableList().size() + " students, " + + tags.asObservableList().size() + " tags, " + + appointments.asObservableList().size() + " appointments, " + + tasks.asObservableList().size() + " tasks"; // TODO: refine later } @@ -166,22 +335,146 @@ public ObservableList<Person> getPersonList() { return persons.asObservableList(); } + @Override + public ObservableList<Student> getStudentList() { + return students.asObservableList(); + } + + @Override + public ObservableList<Person> getContactList() { + return contacts.asObservableList(); + } + @Override public ObservableList<Tag> getTagList() { return tags.asObservableList(); } + @Override + public ObservableList<ShortcutDoubles> getCommandsList() { + return shorcutCommands.asObservableList(); + } + + @Override + public ObservableList<Appointment> getAppointmentList() { + return appointments.asObservableList(); + } + + @Override + public ObservableList<Task> getTaskList() { + return tasks.asObservableList(); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls && this.persons.equals(((AddressBook) other).persons) - && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); + && this.students.equals(((AddressBook) other).students) + && this.appointments.equals(((AddressBook) other).appointments) + && this.tasks.equals(((AddressBook) other).tasks) + && this.tags.equalsOrderInsensitive(((AddressBook) other).tags) + && this.shorcutCommands.equals(((AddressBook) other).shorcutCommands)); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); + return Objects.hash(persons, appointments, tasks, tags); + } + + //@@author shanmu9898 + /** + * Removes the particular tag for all people in the AddressBook. + */ + public void removeTag(Tag tag) throws DuplicatePersonException, PersonNotFoundException { + for (Person person : persons) { + removeTagFromPerson(tag, person); + } + for (Student student : students) { + removeTagFromStudent(tag, student); + } + + } + + + /** + * Removes the particular tag for that particular person in the AddressBook. + */ + private void removeTagFromPerson(Tag tag, Person person) throws PersonNotFoundException, DuplicatePersonException { + Set<Tag> listOfTags = new HashSet<>(person.getTags()); + + if (listOfTags.contains(tag)) { + listOfTags.remove(tag); + } else { + return; + } + + Person updatedPerson = new Person(person.getName(), person.getPhone(), person.getEmail(), + person.getAddress(), listOfTags); + + updatePerson(person, updatedPerson); + } + //@@author + /** + * Removes the particular tag for that particular student in the AddressBook. + */ + private void removeTagFromStudent(Tag tag, Student student) + throws PersonNotFoundException, DuplicatePersonException { + Set<Tag> listOfTags = new HashSet<>(student.getTags()); + + if (listOfTags.contains(tag)) { + listOfTags.remove(tag); + } else { + return; + } + + Student updatedStudent = new Student(student.getName(), student.getPhone(), student.getEmail(), + student.getAddress(), listOfTags); + + updateStudent(student, updatedStudent); + } + //@@author Sisyphus25 + //event operations + /** + * Adds an appointment to the address book. + * + * @throws DuplicateEventException if an equivalent appointment already exists. + */ + public void addAppointment(Appointment e) throws DuplicateEventException { + appointments.add(e); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws EventNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeAppointment(Appointment key) throws EventNotFoundException { + if (appointments.remove(key)) { + return true; + } else { + throw new EventNotFoundException(); + } + } + + /** + * Adds a task to the address book. + * + * @throws DuplicateEventException if an equivalent appointment already exists. + */ + public void addTask(Task e) throws DuplicateEventException { + tasks.add(e); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws EventNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeTask(Task key) throws EventNotFoundException { + if (tasks.remove(key)) { + return true; + } else { + throw new EventNotFoundException(); + } } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..a2bc3914f7dc 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,17 +3,31 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; +import seedu.address.model.tag.Tag; /** * The API of the Model component. */ public interface Model { + String LIST_TYPE_CONTACT = "contact"; + String LIST_TYPE_APPOINTMENT = "appointment"; + String LIST_TYPE_TASK = "task"; + /** {@code Predicate} that always evaluate to true */ Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate<Person> PREDICATE_SHOW_ONLY_STUDENTS = person -> person instanceof Student; + /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -23,9 +37,20 @@ public interface Model { /** Deletes the given person. */ void deletePerson(Person target) throws PersonNotFoundException; + /** Deletes the given student. */ + void deleteStudent(Student target) throws PersonNotFoundException; + /** Adds the given person */ void addPerson(Person person) throws DuplicatePersonException; + + /** Adds the given student */ + void addStudent(Student student) throws DuplicatePersonException; + + /** Adds the given shortcut */ + void addCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException; + /** * Replaces the given person {@code target} with {@code editedPerson}. * @@ -36,13 +61,59 @@ public interface Model { void updatePerson(Person target, Person editedPerson) throws DuplicatePersonException, PersonNotFoundException; + /** + * Replaces the given student {@code target} with {@code editedStudent}. + * + * @throws DuplicatePersonException if updating the student's details causes the student to be equivalent to + * another existing student in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + */ + void updateStudent(Student target, Student editedStudent) + throws DuplicatePersonException, PersonNotFoundException; + /** Returns an unmodifiable view of the filtered person list */ ObservableList<Person> getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered appointment list */ + ObservableList<Appointment> getFilteredAppointmentList(); + + /** Returns an unmodifiable view of the filtered appointment list */ + ObservableList<Task> getFilteredTaskList(); + + /** Returns an unmodifiable view of the filtered commands list */ + ObservableList<ShortcutDoubles> getFilteredCommandsList(); + + /** Returns the item type of the curent active list being shown in the GUI */ + String getCurrentActiveListType(); + + void sortFilteredPersonList(); + + /** Deletes the given command shortcut */ + void deleteCommandShortcut(ShortcutDoubles commandShortcut) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException; + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate<Person> predicate); + + + void deleteTag(Tag tag) throws PersonNotFoundException, DuplicatePersonException; + + /** Adds the given appointment */ + void addAppointment(Appointment appointment) throws DuplicateEventException; + + /** Deletes the given appointment. */ + void deleteAppointment(Appointment appointment) throws EventNotFoundException; + + /** Adds the given task */ + void addTask(Task task) throws DuplicateEventException; + + /** Deletes the given task */ + void deleteTask(Task task) throws EventNotFoundException; + + /** Change the current active list that is being displayed in the model */ + void changeCurrentActiveListType(String itemType); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..14c4552764f1 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -5,27 +5,44 @@ import java.util.function.Predicate; import java.util.logging.Logger; +import java.util.Comparator; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.AppointmentListChangedEvent; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; +import seedu.address.model.tag.Tag; /** * Represents the in-memory model of the address book data. * All changes to any model should be synchronized. */ public class ModelManager extends ComponentManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; - private final FilteredList<Person> filteredPersons; + private final FilteredList<Person> filteredContacts; + private final FilteredList<Appointment> filteredAppointments; + private final FilteredList<Task> filteredTasks; + private final FilteredList<ShortcutDoubles> filteredShortcutCommands; + private String currentActiveListType; + private SortedList<Person> sortedFilteredPersons; /** * Initializes a ModelManager with the given addressBook and userPrefs. */ @@ -36,7 +53,12 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); this.addressBook = new AddressBook(addressBook); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredContacts = new FilteredList<>(this.addressBook.getContactList()); + sortedFilteredPersons = new SortedList<Person>(filteredContacts); + filteredAppointments = new FilteredList<>(this.addressBook.getAppointmentList()); + filteredShortcutCommands = new FilteredList<>(this.addressBook.getCommandsList()); + filteredTasks = new FilteredList<>(this.addressBook.getTaskList()); + currentActiveListType = LIST_TYPE_CONTACT; } public ModelManager() { @@ -59,12 +81,23 @@ private void indicateAddressBookChanged() { raise(new AddressBookChangedEvent(addressBook)); } + /** Raises an event to indicate the appointment list has changed */ + private void indicateAppointmentListChanged() { + raise(new AppointmentListChangedEvent(addressBook.getAppointmentList())); + } + @Override public synchronized void deletePerson(Person target) throws PersonNotFoundException { addressBook.removePerson(target); indicateAddressBookChanged(); } + @Override + public synchronized void deleteStudent(Student target) throws PersonNotFoundException { + addressBook.removeStudent(target); + indicateAddressBookChanged(); + } + @Override public synchronized void addPerson(Person person) throws DuplicatePersonException { addressBook.addPerson(person); @@ -72,6 +105,27 @@ public synchronized void addPerson(Person person) throws DuplicatePersonExceptio indicateAddressBookChanged(); } + @Override + public synchronized void addStudent(Student student) throws DuplicatePersonException { + addressBook.addStudent(student); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + + //@@author shanmu9898 + @Override + public synchronized void addCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + addressBook.addShortcutDoubles(shortcutDoubles); + indicateAddressBookChanged(); + } + + @Override + public synchronized void deleteCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + addressBook.removeShortcutDouble(shortcutDoubles); + } + //@@author @Override public void updatePerson(Person target, Person editedPerson) throws DuplicatePersonException, PersonNotFoundException { @@ -81,6 +135,41 @@ public void updatePerson(Person target, Person editedPerson) indicateAddressBookChanged(); } + @Override + public void updateStudent(Student target, Student editedStudent) + throws DuplicatePersonException, PersonNotFoundException { + requireAllNonNull(target, editedStudent); + + addressBook.updateStudent(target, editedStudent); + indicateAddressBookChanged(); + } + + @Override + public void addAppointment(Appointment appointment) throws DuplicateEventException { + addressBook.addAppointment(appointment); + indicateAddressBookChanged(); + indicateAppointmentListChanged(); + } + + @Override + public void deleteAppointment(Appointment target) throws EventNotFoundException { + addressBook.removeAppointment(target); + indicateAddressBookChanged(); + indicateAppointmentListChanged(); + } + + @Override + public void addTask(Task task) throws DuplicateEventException { + addressBook.addTask(task); + indicateAddressBookChanged(); + } + + @Override + public void deleteTask(Task target) throws EventNotFoundException { + addressBook.removeTask(target); + indicateAddressBookChanged(); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -89,13 +178,52 @@ public void updatePerson(Person target, Person editedPerson) */ @Override public ObservableList<Person> getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + return FXCollections.unmodifiableObservableList(sortedFilteredPersons); + } + + @Override + public void sortFilteredPersonList(){ + + Comparator<Person> sortByName = new Comparator<Person>() { + @Override + public int compare(Person o1, Person o2) { + return o1.getName().fullName.compareTo(o2.getName().fullName); + } + }; + + sortedFilteredPersons.setComparator(sortByName); + indicateAddressBookChanged(); + } + @Override + public ObservableList<Appointment> getFilteredAppointmentList() { + return FXCollections.unmodifiableObservableList(filteredAppointments); + } + + @Override + public ObservableList<Task> getFilteredTaskList() { + return FXCollections.unmodifiableObservableList(filteredTasks); + } + + //@@author shanmu9898 + @Override + public ObservableList<ShortcutDoubles> getFilteredCommandsList() { + return FXCollections.unmodifiableObservableList(filteredShortcutCommands); + } + //@@author + @Override + public String getCurrentActiveListType() { + return currentActiveListType; + } + + @Override + public void changeCurrentActiveListType(String itemType) { + currentActiveListType = itemType; } @Override public void updateFilteredPersonList(Predicate<Person> predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredContacts.setPredicate(predicate); } @Override @@ -113,7 +241,12 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); + && filteredContacts.equals(other.filteredContacts); + } + + @Override + public void deleteTag(Tag tag) throws PersonNotFoundException, DuplicatePersonException { + addressBook.removeTag(tag); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1f4e49a37d67..c816bb3982a6 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,11 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.shortcuts.ShortcutDoubles; import seedu.address.model.tag.Tag; /** @@ -15,10 +19,41 @@ public interface ReadOnlyAddressBook { */ ObservableList<Person> getPersonList(); + /** + * Returns an unmodifiable view of the students list. + * This list will not contain any duplicate students. + */ + ObservableList<Student> getStudentList(); + + /** + * Returns an unmodifiable view of a list of all contacts. + * This list will not contain any duplicate persons or students. + */ + ObservableList<Person> getContactList(); + /** * Returns an unmodifiable view of the tags list. * This list will not contain any duplicate tags. */ ObservableList<Tag> getTagList(); + /** + * Returns an unmodifiable view of the appointments list. + * This list will not contain any duplicate appointment. + */ + ObservableList<Appointment> getAppointmentList(); + + /** + * Returns an unmodifiable view of the tasks list. + * This list will not contain any duplicate tasks. + */ + ObservableList<Task> getTaskList(); + + /** + * Returns an unmodifiable view of the commands list. + * This list will not contain any duplicate commands. + */ + ObservableList<ShortcutDoubles> getCommandsList(); + + } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 8c8a071876eb..34cb3dc95b76 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -37,10 +37,6 @@ public void setAddressBookFilePath(String addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } - public String getAddressBookName() { - return addressBookName; - } - public void setAddressBookName(String addressBookName) { this.addressBookName = addressBookName; } diff --git a/src/main/java/seedu/address/model/event/Appointment.java b/src/main/java/seedu/address/model/event/Appointment.java new file mode 100644 index 000000000000..fabd0ed67aee --- /dev/null +++ b/src/main/java/seedu/address/model/event/Appointment.java @@ -0,0 +1,86 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +//@@author Sisyphus25 +/** + * Represent an appointment in the schedule, contains time of the appointment as well as details and personMeet. + */ +public class Appointment { + public static final String MESSAGE_TIME_PERIOD_CONSTRAINTS = "The end time should be after the start time"; + + private final Title title; + private final EventTime time; + private final EventTime endTime; + private final PersonToMeet personToMeet; + + //Every field must be present and not null + public Appointment(Title title, EventTime startTime, EventTime endTime) { + this(title, startTime, endTime, null); + } + + //Every field except personToMeet must be present and not null + public Appointment(Title title, EventTime startTime, EventTime endTime, PersonToMeet personToMeet) { + requireAllNonNull(title, startTime, endTime); + checkArgument(isValidTime(startTime, endTime), MESSAGE_TIME_PERIOD_CONSTRAINTS); + this.title = title; + this.time = startTime; + this.endTime = endTime; + this.personToMeet = personToMeet; + } + + public Title getTitle() { + return title; + } + + public EventTime getTime() { + return time; + } + + public EventTime getEndTime() { + return endTime; + } + + public PersonToMeet getPersonToMeet() { + return personToMeet; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Appointment)) { + return false; + } + + Appointment otherAppointment = (Appointment) other; + return otherAppointment.getTitle().equals(this.getTitle()) + && otherAppointment.getTime().equals(this.getTime()) + && otherAppointment.getEndTime().equals(this.getEndTime()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(", Start Time: ") + .append(getTime().toString()) + .append(", End Time: ") + .append(getEndTime().toString()); + if (personToMeet != null) { + builder.append(", With: ") + .append(personToMeet.getName()); + } + return builder.toString(); + } + + /** + * Returns true if the given time is valid + */ + public static boolean isValidTime(EventTime startTime, EventTime endTime) { + return endTime.value.after(startTime.value); + } +} diff --git a/src/main/java/seedu/address/model/event/EventTime.java b/src/main/java/seedu/address/model/event/EventTime.java new file mode 100644 index 000000000000..ae1682011fad --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventTime.java @@ -0,0 +1,72 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents an event's time stamp in the address book. + * Guarantees: immutable + */ +public class EventTime { + public static final String MESSAGE_TIME_CONSTRAINTS = "Date and time must be in the format: dd/MM/yyyy HH:mm"; + public static final String TIME_VALIDATION_REGEX = "((^(((0[1-9]|1[0-9]|2[0-8])[\\/](0[1-9]|1[012]))|" + + "((29|30|31)[\\/](0[13578]|1[02]))|((29|30)[\\/](0[4,6,9]|11)))[\\/](19|" + + "[2-9][0-9])\\d\\d)|(^29[\\/]02[\\/](19|[2-9][0-9])" + + "(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)))" + + "[ ]([0-1]?[0-9]|2[0-3]):[0-5][0-9]"; + + private static final String DATE_FORMAT = "dd/MM/yyyy HH:mm"; + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + public final Calendar value; + + /** + * Constructs a {@code EventTime}. + * + * @param timeStamp valid timeStamp. + */ + public EventTime(String timeStamp) { + requireNonNull(timeStamp); + checkArgument(isValidTimeStamp(timeStamp), MESSAGE_TIME_CONSTRAINTS); + value = Calendar.getInstance(); + try { + this.value.setTime(DATE_FORMATTER.parse(timeStamp)); + } catch (ParseException e) { + throw new IllegalArgumentException(MESSAGE_TIME_CONSTRAINTS); + } + } + + /** + * Returns true if the given time has already passed the current time + */ + public boolean isExpired() { + Calendar currentTime = Calendar.getInstance(); + currentTime.setTime(new Date()); + return value.before(currentTime); + } + + /** + * Returns if a given string is a valid time stamp. + */ + public boolean isValidTimeStamp(String time) { + return time.matches(TIME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return DATE_FORMATTER.format(value.getTime()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventTime // instanceof handles nulls + && this.value.equals(((EventTime) other).value)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/PersonToMeet.java b/src/main/java/seedu/address/model/event/PersonToMeet.java new file mode 100644 index 000000000000..79f275346535 --- /dev/null +++ b/src/main/java/seedu/address/model/event/PersonToMeet.java @@ -0,0 +1,50 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.model.person.Email.MESSAGE_EMAIL_CONSTRAINTS; +import static seedu.address.model.person.Name.MESSAGE_NAME_CONSTRAINTS; + +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; + +/** + * Represents an Appointment's personToMeet in the address book. + * Guarantees: immutable; + */ +public class PersonToMeet { + + public static final String EMAIL_SPLITTER = " Email: "; + + private final String name; + private final String email; + + public PersonToMeet(String name, String email) { + requireNonNull(name, email); + checkArgument(Name.isValidName(name), MESSAGE_NAME_CONSTRAINTS); + checkArgument(Email.isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); + this.name = name; + this.email = email; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + @Override + public String toString() { + return name + EMAIL_SPLITTER + email; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PersonToMeet // instanceof handles nulls + && this.name.equals(((PersonToMeet) other).name)) + && this.name.equals(((PersonToMeet) other).email); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/Task.java b/src/main/java/seedu/address/model/event/Task.java new file mode 100644 index 000000000000..56e30c76f274 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Task.java @@ -0,0 +1,47 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +//@@author Sisyphus25 +/** + * Represent a Task in the schedule, contains deadline as well as the title + */ +public class Task { + private Title title; + private EventTime time; + + //Every field must be present and not null + public Task(Title title, EventTime deadline) { + requireAllNonNull(title, deadline); + this.title = title; + this.time = deadline; + } + + public Title getTitle() { + return title; + } + + public EventTime getTime() { + return time; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getTitle().equals(this.getTitle()) + && otherTask.getTime().equals(this.getTime()); + } + + @Override + public String toString() { + return title + ", Deadline: " + time; + } +} diff --git a/src/main/java/seedu/address/model/event/Title.java b/src/main/java/seedu/address/model/event/Title.java new file mode 100644 index 000000000000..befdd8df7d0f --- /dev/null +++ b/src/main/java/seedu/address/model/event/Title.java @@ -0,0 +1,50 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an event's title in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidTitle(String)} + */ +public class Title { + + public static final String MESSAGE_TITLE_CONSTRAINTS = "Title must be non empty"; + + /* + * The title can not be empty string or spaces only + */ + private static final String TITLE_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs a {@code Title}. + * + * @param title A valid title. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_TITLE_CONSTRAINTS); + this.value = title; + } + + /** + * Returns true if a given string is a valid title. + */ + public static boolean isValidTitle(String test) { + return test.matches(TITLE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Title // instanceof handles nulls + && this.value.equals(((Title) other).value)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/UniqueEventList.java b/src/main/java/seedu/address/model/event/UniqueEventList.java new file mode 100644 index 000000000000..cd1547d4b6a5 --- /dev/null +++ b/src/main/java/seedu/address/model/event/UniqueEventList.java @@ -0,0 +1,95 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; + +/** + * A list of events that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + */ +public class UniqueEventList<A> implements Iterable<A> { + + private final ObservableList<A> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent Event as the given argument. + */ + public boolean contains(A toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds an Event to the list. + * + * @throws DuplicateEventException if the event to add + * is a duplicate of an existing Event in the list. + */ + public void add(A toAdd) throws DuplicateEventException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateEventException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent Event from the list. + * + * @throws EventNotFoundException if no such event could be found in the list. + */ + public boolean remove(A toRemove) throws EventNotFoundException { + requireNonNull(toRemove); + final boolean eventFoundAndDeleted = internalList.remove(toRemove); + if (!eventFoundAndDeleted) { + throw new EventNotFoundException(); + } + return eventFoundAndDeleted; + } + + public void setEvents(UniqueEventList<A> replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setEvents(List<A> events) throws DuplicateEventException { + requireAllNonNull(events); + final UniqueEventList<A> replacement = new UniqueEventList<A>(); + for (final A event : events) { + replacement.add(event); + } + setEvents(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<A> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<A> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueEventList // instanceof handles nulls + && this.internalList.equals(((UniqueEventList<A>) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java new file mode 100644 index 000000000000..31642baa63dc --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java @@ -0,0 +1,12 @@ +package seedu.address.model.event.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +/** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ +public class DuplicateEventException extends DuplicateDataException { + public DuplicateEventException() { + super("Operation would result in duplicate events"); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java new file mode 100644 index 000000000000..0c1d1ff1ea3a --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.event.exceptions; + +/** + * Signals that an operation is looking for an appointment doesn't exist. + */ +public class EventNotFoundException extends Exception { + public EventNotFoundException() { + super("Event not found"); + } +} diff --git a/src/main/java/seedu/address/model/person/Student.java b/src/main/java/seedu/address/model/person/Student.java new file mode 100644 index 000000000000..36170f478f37 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Student.java @@ -0,0 +1,19 @@ +package seedu.address.model.person; + +import java.util.Set; + +import seedu.address.model.tag.Tag; + +/** + * Represents a Student in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student extends Person { + + /** + * Every field must be present and not null. + */ + public Student(Name name, Phone phone, Email email, Address address, Set<Tag> tags) { + super(name, phone, email, address, tags); + } +} diff --git a/src/main/java/seedu/address/model/person/UniqueContactList.java b/src/main/java/seedu/address/model/person/UniqueContactList.java new file mode 100644 index 000000000000..8a87eddb8294 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueContactList.java @@ -0,0 +1,56 @@ +package seedu.address.model.person; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +//@@author randypx +/** + * A list that is the aggregation of {@code UniquePersonList} and {@code UniqueStudentList} + * and is the list displayed in the GUI. + * This list remains up-to-date by listening to the changes of both lists and is not changed by anything else. + */ +public class UniqueContactList { + private final UniquePersonList persons; + private final UniqueStudentList students; + private final ObservableList<Person> combinedList = FXCollections.observableArrayList(); + + public UniqueContactList(UniquePersonList p, UniqueStudentList s) { + persons = p; + students = s; + persons.addListener(this); + students.addListener(this); + } + + /** + * This method is called when there is a change in eithor {@code UniquePersonList} or {@code UniqueStudentList}. + * @param c this contains the change(s) that has occured. + */ + public void updateList(ListChangeListener.Change<? extends Person> c) { + while (c.next()) { + if (c.wasReplaced()) { + for (int i = 0; i < c.getRemovedSize(); i++) { + int index = combinedList.indexOf(c.getRemoved().get(i)); + combinedList.set(index, c.getAddedSubList().get(i)); + } + if (c.getTo() > c.getRemovedSize()) { + for (int i = c.getRemovedSize(); i < c.getTo(); i++) { + combinedList.add(c.getAddedSubList().get(i)); + } + } + } else if (c.wasRemoved()) { + combinedList.removeAll(c.getRemoved()); + } else if (c.wasAdded()) { + combinedList.addAll(c.getAddedSubList()); + } + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Person> asObservableList() { + return FXCollections.unmodifiableObservableList(combinedList); + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index f2c4c4c585e4..83eb7da85f94 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -3,18 +3,22 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; +import java.util.Collection; import java.util.Iterator; import java.util.List; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.util.CollectionUtil; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * + * <p> * Supports a minimal set of list operations. * * @see Person#equals(Object) @@ -49,7 +53,7 @@ public void add(Person toAdd) throws DuplicatePersonException { * Replaces the person {@code target} in the list with {@code editedPerson}. * * @throws DuplicatePersonException if the replacement is equivalent to another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. */ public void setPerson(Person target, Person editedPerson) throws DuplicatePersonException, PersonNotFoundException { @@ -98,9 +102,25 @@ public void setPersons(List<Person> persons) throws DuplicatePersonException { * Returns the backing list as an unmodifiable {@code ObservableList}. */ public ObservableList<Person> asObservableList() { + sort(); return FXCollections.unmodifiableObservableList(internalList); } + //@@author randypx + /** + * Add a listener to the list for any changes. + * Update {@code contacts} for any changes made. + */ + public void addListener(UniqueContactList contacts) { + internalList.addListener(new ListChangeListener<Person>() { + @Override + public void onChanged(Change<? extends Person> c) { + contacts.updateList(c); + } + }); + } + //@@author + @Override public Iterator<Person> iterator() { return internalList.iterator(); @@ -110,11 +130,24 @@ public Iterator<Person> iterator() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof UniquePersonList // instanceof handles nulls - && this.internalList.equals(((UniquePersonList) other).internalList)); + && this.internalList.equals(((UniquePersonList) other).internalList)); } @Override public int hashCode() { return internalList.hashCode(); } + + public void sort() { + internalList.sort(new Comparator<Person>() { + @Override + public int compare(Person otherMember1, Person otherMember2) { + return otherMember1.getName().toString().compareTo(otherMember2.getName().toString()); + } + + + }); + + + } } diff --git a/src/main/java/seedu/address/model/person/UniqueStudentList.java b/src/main/java/seedu/address/model/person/UniqueStudentList.java new file mode 100644 index 000000000000..89fd03a13ab1 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueStudentList.java @@ -0,0 +1,136 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +/** + * A list of students that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Person#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueStudentList implements Iterable<Student> { + + private final ObservableList<Student> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a student to the list. + * + * @throws DuplicatePersonException if the person to add is a duplicate of an existing student in the list. + */ + public void add(Student toAdd) throws DuplicatePersonException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the student {@code target} in the list with {@code editedStudent}. + * + * @throws DuplicatePersonException if the replacement is equivalent to another existing person in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + */ + public void setStudent(Student target, Student editedStudent) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.equals(editedStudent) && internalList.contains(editedStudent)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedStudent); + } + + /** + * Removes the equivalent student from the list. + * + * @throws PersonNotFoundException if no such student could be found in the list. + */ + public boolean remove(Student toRemove) throws PersonNotFoundException { + requireNonNull(toRemove); + final boolean studentFoundAndDeleted = internalList.remove(toRemove); + if (!studentFoundAndDeleted) { + throw new PersonNotFoundException(); + } + return studentFoundAndDeleted; + } + + public void setStudents(UniqueStudentList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setStudents(List<Student> students) throws DuplicatePersonException { + requireAllNonNull(students); + final UniqueStudentList replacement = new UniqueStudentList(); + for (final Student student : students) { + replacement.add(student); + } + setStudents(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Student> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + //@@author randypx + /** + * Add a listener to the list for any changes. + * Update {@code contacts} for any changes made. + */ + public void addListener(UniqueContactList contacts) { + internalList.addListener(new ListChangeListener<Student>() { + @Override + public void onChanged(Change<? extends Student> c) { + contacts.updateList(c); + } + }); + } + + @Override + public Iterator<Student> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && this.internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/shortcuts/ShortcutDoubles.java b/src/main/java/seedu/address/model/shortcuts/ShortcutDoubles.java new file mode 100644 index 000000000000..2fe7c5ab03ae --- /dev/null +++ b/src/main/java/seedu/address/model/shortcuts/ShortcutDoubles.java @@ -0,0 +1,35 @@ +//@@author shanmu9898 +package seedu.address.model.shortcuts; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Command Double + */ +public class ShortcutDoubles { + + public final String shortcutWord; + public final String commandWord; + + public ShortcutDoubles(String shortcutWord, String commandWord) { + requireNonNull(shortcutWord); + requireNonNull(commandWord); + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ShortcutDoubles)) { + return false; + } + + ShortcutDoubles otherShortcut = (ShortcutDoubles) other; + return otherShortcut.commandWord.equals(this.commandWord) + && otherShortcut.shortcutWord.equals(this.shortcutWord); + } +} diff --git a/src/main/java/seedu/address/model/shortcuts/UniqueShortcutDoublesList.java b/src/main/java/seedu/address/model/shortcuts/UniqueShortcutDoublesList.java new file mode 100644 index 000000000000..a5148ce7dd29 --- /dev/null +++ b/src/main/java/seedu/address/model/shortcuts/UniqueShortcutDoublesList.java @@ -0,0 +1,101 @@ +//@@author shanmu9898 +package seedu.address.model.shortcuts; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.exceptions.DuplicateDataException; +import seedu.address.commons.util.CollectionUtil; + +/** + * + */ +public class UniqueShortcutDoublesList { + + private final ObservableList<ShortcutDoubles> internalList = FXCollections.observableArrayList(); + + public UniqueShortcutDoublesList(){ + + } + + /** + * Adds Shortcut Doubles to the internal list + * @param toAdd + * @throws DuplicateShortcutDoublesException + */ + public void add(ShortcutDoubles toAdd) throws DuplicateShortcutDoublesException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateShortcutDoublesException(); + } + internalList.add(toAdd); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns an ObservableList of the internallist + * @return + */ + public ObservableList<ShortcutDoubles> asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Gives a duplicate Except + */ + public static class DuplicateShortcutDoublesException extends DuplicateDataException { + protected DuplicateShortcutDoublesException() { + super("Operation would result in duplicate Doubles"); + } + } + + /** + * Helps in checking if there are duplicates + * @param toCheck + * @return + */ + public boolean contains(ShortcutDoubles toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof UniqueShortcutDoublesList // instanceof handles nulls + && this.internalList.equals(((UniqueShortcutDoublesList) other).internalList)); + } + + public void setCommandsList(List<ShortcutDoubles> commandsList) { + requireNonNull(commandsList); + internalList.setAll(commandsList); + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Removes the equvivalent command shortcut from the list. + * @param shortcutDoubles + * + * @throws UniqueShortcutDoublesList.CommandShortcutNotFoundException + */ + public boolean remove(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + requireNonNull(shortcutDoubles); + final boolean shortcutToBeDeleted = internalList.remove(shortcutDoubles); + if (!shortcutToBeDeleted) { + throw new UniqueShortcutDoublesList.CommandShortcutNotFoundException(); + } + return shortcutToBeDeleted; + } + + /** + * Exception when the command shortcut is not present in the list of stored commands + */ + public static class CommandShortcutNotFoundException extends Exception {} +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 65bdd769995d..7ddc2948d291 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -3,15 +3,23 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; +import java.util.Arrays; + /** * Represents a Tag in the address book. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; - + public static final String MESSAGE_TAG_NAME_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_TAG_COLOR_STYLE_CONSTRAINTS = "Tag color style is invalid or not supported"; + private static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; + //@@author Sisyphus25-reused + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + private static final String[] TAG_COLOR_STYLES = {"teal", "red", "yellow", "blue", "orange", "brown", + "green", "pink", "black", "grey"}; + //@@author + public final String tagColorStyle; public final String tagName; /** @@ -20,9 +28,27 @@ public class Tag { * @param tagName A valid tag name. */ public Tag(String tagName) { + this(tagName, "default"); + } + + /** + * Constructs a {@code Tag}. + * + * @param tagName A valid tag name. + * @param tagColorStyle A valid tag color style + */ + public Tag(String tagName, String tagColorStyle) { requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_TAG_CONSTRAINTS); + requireNonNull(tagColorStyle); + + if (tagColorStyle.equals("default")) { + tagColorStyle = getTagColorStyle(tagName); + } + + checkArgument(isValidTagName(tagName), MESSAGE_TAG_NAME_CONSTRAINTS); + checkArgument(isValidTagColorStyle(tagColorStyle), MESSAGE_TAG_COLOR_STYLE_CONSTRAINTS); this.tagName = tagName; + this.tagColorStyle = tagColorStyle; } /** @@ -32,6 +58,13 @@ public static boolean isValidTagName(String test) { return test.matches(TAG_VALIDATION_REGEX); } + /** + * Returns true if a given string is a valid tag color style. + */ + public static boolean isValidTagColorStyle(String tagColorStyle) { + return Arrays.asList(TAG_COLOR_STYLES).contains(tagColorStyle); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object @@ -51,4 +84,16 @@ public String toString() { return '[' + tagName + ']'; } + //@@author Sisyphus25-reused + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * Returns a color style for {@code tagName} + */ + private String getTagColorStyle(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between different runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + } diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java index e9a74947fc3f..95ecc3901538 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java @@ -111,7 +111,7 @@ public boolean equals(Object other) { assert CollectionUtil.elementsAreUnique(internalList); return other == this // short circuit if same object || (other instanceof UniqueTagList // instanceof handles nulls - && this.internalList.equals(((UniqueTagList) other).internalList)); + && this.internalList.equals(((UniqueTagList) other).internalList)); } /** diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..1f2867917e5e 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -5,12 +5,20 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.PersonToMeet; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; +import seedu.address.model.event.exceptions.DuplicateEventException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; import seedu.address.model.tag.Tag; /** @@ -18,40 +26,91 @@ */ public class SampleDataUtil { public static Person[] getSamplePersons() { - return new Person[] { + return new Person[]{ new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + new Address("Blk 30 Geylang Street 29, #06-40"), + getTagSet("friends")), new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + getTagSet("colleagues", "friends")), new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + getTagSet("neighbours")), new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + getTagSet("family")), new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), + new Address("Blk 47 Tampines Street 20, #17-35"), + getTagSet("classmates")), new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Address("Blk 45 Aljunied Street 85, #11-31"), + getTagSet("colleagues")) + }; + } + + public static ShortcutDoubles[] getSampleShortcutDoubles() { + return new ShortcutDoubles[]{ + new ShortcutDoubles("a", "add"), + new ShortcutDoubles("s", "shortcut") + }; + } + + public static Appointment[] getSampleAppointment() { + return new Appointment[]{ + new Appointment(new Title("Consultation"), + new EventTime("04/04/2018 15:00"), + new EventTime("04/04/2018 18:00"), + new PersonToMeet("Bernice Yu", "berniceyu@example.com")), + new Appointment(new Title("Tutoring Session"), + new EventTime("08/04/2018 10:00"), + new EventTime("08/04/2018 12:00"), + new PersonToMeet("Roy Balakrishnan", "royb@example.com")), + new Appointment(new Title("Meet up with parents"), + new EventTime("07/04/2018 13:00"), + new EventTime("07/04/2018 15:00")) + }; + } + + public static Task[] getSampleTask() { + return new Task[] { + new Task(new Title("Mark papers"), new EventTime("30/03/2018 18:00")), + new Task(new Title("Collect documents"), new EventTime("28/03/2018 10:00")), + new Task(new Title("Arrange tutor session"), new EventTime("05/04/2018 23:00")), + new Task(new Title("Prepare documents for meeting"), new EventTime("08/04/2018 10:00")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { try { + AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + + for (ShortcutDoubles s : getSampleShortcutDoubles()) { + sampleAb.addShortcutDoubles(s); + } + + for (Appointment a : getSampleAppointment()) { + sampleAb.addAppointment(a); + } + + for (Task t : getSampleTask()) { + sampleAb.addTask(t); + } + return sampleAb; } catch (DuplicatePersonException e) { throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (UniqueShortcutDoublesList.DuplicateShortcutDoublesException e) { + throw new AssertionError("sample data cannot contain duplicate command shortcuts", e); + } catch (DuplicateEventException e) { + throw new AssertionError("sample data cannot contain duplicate events", e); } } + /** * Returns a tag set containing the list of strings given. */ @@ -64,4 +123,13 @@ public static Set<Tag> getTagSet(String... strings) { return tags; } + public static Set<ShortcutDoubles> getSampleShortcutDoublesTagSet(String... strings) { + HashSet<ShortcutDoubles> shortcutDoubles = new HashSet<>(); + for (String s : strings) { + shortcutDoubles.add(new ShortcutDoubles(s, s)); + } + + return shortcutDoubles; + } + } diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index c0881a5a6483..d48469b613db 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -26,6 +26,10 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; + @Override + Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, IOException; + + @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; diff --git a/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java b/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java new file mode 100644 index 000000000000..3eefc306f538 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedAppointment.java @@ -0,0 +1,121 @@ +package seedu.address.storage; + +import static seedu.address.model.event.PersonToMeet.EMAIL_SPLITTER; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.PersonToMeet; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedAppointment { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Appointment's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String startTime; + @XmlElement(required = true) + private String endTime; + @XmlElement(required = true) + private String personToMeet; + + /** + * Constructs an XmlAdaptedAppointment. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAppointment() {} + + public XmlAdaptedAppointment(String title, String startTime, String endTime) { + this(title, startTime, endTime, null); + } + + /** + * Constructs an {@code XmlAdaptedAppointment} with the given appointment details. + */ + public XmlAdaptedAppointment(String title, String startTime, String endTime, String personToMeet) { + this.title = title; + this.startTime = startTime; + this.endTime = endTime; + if (personToMeet != null) { + this.personToMeet = personToMeet; + } + } + + /** + * Converts a given Appointment into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedAppointment + */ + public XmlAdaptedAppointment(Appointment source) { + title = source.getTitle().toString(); + startTime = source.getTime().toString(); + endTime = source.getEndTime().toString(); + if (source.getPersonToMeet() != null) { + personToMeet = source.getPersonToMeet().toString(); + } + } + + /** + * Converts this jaxb-friendly adapted person object into the model's Appointment object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted appointment + */ + public Appointment toModelType() throws IllegalValueException { + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(this.title)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + final Title title = new Title(this.title); + + if (this.startTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Start Time")); + } + if (this.endTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "End Time")); + } + + final EventTime startTime = new EventTime(this.startTime); + final EventTime endTime = new EventTime(this.endTime); + + if (!Appointment.isValidTime(startTime, endTime)) { + throw new IllegalValueException(Appointment.MESSAGE_TIME_PERIOD_CONSTRAINTS); + } + + if (this.personToMeet != null) { + String[] components = this.personToMeet.split(EMAIL_SPLITTER); + PersonToMeet personToMeet = new PersonToMeet(components[0], components[1]); + return new Appointment(title, startTime, endTime, personToMeet); + } + + return new Appointment(title, startTime, endTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAppointment)) { + return false; + } + + XmlAdaptedAppointment otherAppointment = (XmlAdaptedAppointment) other; + return Objects.equals(title, otherAppointment.title) + && Objects.equals(startTime, otherAppointment.startTime) + && Objects.equals(endTime, otherAppointment.endTime) + && Objects.equals(personToMeet, otherAppointment.personToMeet); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedShortcutDouble.java b/src/main/java/seedu/address/storage/XmlAdaptedShortcutDouble.java new file mode 100644 index 000000000000..3b189d78465a --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedShortcutDouble.java @@ -0,0 +1,48 @@ +//@@author shanmu9898 +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * + */ +public class XmlAdaptedShortcutDouble { + @XmlElement + private String shortcutWord; + @XmlElement + private String commandWord; + + public XmlAdaptedShortcutDouble() {} + + public XmlAdaptedShortcutDouble(String shortcutWord, String commandWord) { + this.shortcutWord = shortcutWord; + this.commandWord = commandWord; + } + + public XmlAdaptedShortcutDouble(ShortcutDoubles source) { + shortcutWord = source.shortcutWord; + commandWord = source.commandWord; + } + + public ShortcutDoubles toModelType() throws IllegalValueException { + return new ShortcutDoubles(shortcutWord, commandWord); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedShortcutDouble)) { + return false; + } + + return commandWord.equals(((XmlAdaptedShortcutDouble) other).commandWord) + && shortcutWord.equals(((XmlAdaptedShortcutDouble) other).shortcutWord); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedStudent.java b/src/main/java/seedu/address/storage/XmlAdaptedStudent.java new file mode 100644 index 000000000000..168617e3fe9c --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedStudent.java @@ -0,0 +1,137 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Student; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Student. + */ +public class XmlAdaptedStudent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String phone; + @XmlElement(required = true) + private String email; + @XmlElement(required = true) + private String address; + + @XmlElement + private List<XmlAdaptedTag> tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedStudent. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedStudent() {} + + /** + * Constructs an {@code XmlAdaptedStudent} with the given student details. + */ + public XmlAdaptedStudent(String name, String phone, String email, String address, List<XmlAdaptedTag> tagged) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + } + + /** + * Converts a given Student into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedPerson + */ + public XmlAdaptedStudent(Student source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted student object into the model's Student object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Student toModelType() throws IllegalValueException { + final List<Tag> studentTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + studentTags.add(tag.toModelType()); + } + + if (this.name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(this.name)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + final Name name = new Name(this.name); + + if (this.phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(this.phone)) { + throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + } + final Phone phone = new Phone(this.phone); + + if (this.email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(this.email)) { + throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + } + final Email email = new Email(this.email); + + if (this.address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(this.address)) { + throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + } + final Address address = new Address(this.address); + + final Set<Tag> tags = new HashSet<>(studentTags); + return new Student(name, phone, email, address, tags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedPerson)) { + return false; + } + + XmlAdaptedStudent otherStudent = (XmlAdaptedStudent) other; + return Objects.equals(name, otherStudent.name) + && Objects.equals(phone, otherStudent.phone) + && Objects.equals(email, otherStudent.email) + && Objects.equals(address, otherStudent.address) + && tagged.equals(otherStudent.tagged); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..176d18c3c8de 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -1,6 +1,6 @@ package seedu.address.storage; -import javax.xml.bind.annotation.XmlValue; +import javax.xml.bind.annotation.XmlElement; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.tag.Tag; @@ -10,8 +10,10 @@ */ public class XmlAdaptedTag { - @XmlValue + @XmlElement(required = true) private String tagName; + @XmlElement(required = true) + private String tagColorStyle; /** * Constructs an XmlAdaptedTag. @@ -20,10 +22,11 @@ public class XmlAdaptedTag { public XmlAdaptedTag() {} /** - * Constructs a {@code XmlAdaptedTag} with the given {@code tagName}. + * Constructs a {@code XmlAdaptedTag} with the given {@code tagName} and {@code tagColorStyle}. */ - public XmlAdaptedTag(String tagName) { + public XmlAdaptedTag(String tagName, String tagColorStyle) { this.tagName = tagName; + this.tagColorStyle = tagColorStyle; } /** @@ -33,6 +36,7 @@ public XmlAdaptedTag(String tagName) { */ public XmlAdaptedTag(Tag source) { tagName = source.tagName; + tagColorStyle = source.tagColorStyle; } /** @@ -42,9 +46,12 @@ public XmlAdaptedTag(Tag source) { */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); + throw new IllegalValueException(Tag.MESSAGE_TAG_NAME_CONSTRAINTS); } - return new Tag(tagName); + if (!Tag.isValidTagColorStyle(tagColorStyle)) { + throw new IllegalValueException(Tag.MESSAGE_TAG_COLOR_STYLE_CONSTRAINTS); + } + return new Tag(tagName, tagColorStyle); } @Override @@ -57,6 +64,7 @@ public boolean equals(Object other) { return false; } - return tagName.equals(((XmlAdaptedTag) other).tagName); + return tagName.equals(((XmlAdaptedTag) other).tagName) + && tagColorStyle.equals(((XmlAdaptedTag) other).tagColorStyle); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTask.java b/src/main/java/seedu/address/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..ea54f785d6e6 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedTask.java @@ -0,0 +1,85 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +/** + * JAXB-friendly version of the Person. + */ +public class XmlAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String time; + + /** + * Constructs an XmlAdaptedTask. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedTask() {} + + /** + * Constructs an {@code XmlAdaptedTask} with the given task details. + */ + public XmlAdaptedTask(String title, String time) { + this.title = title; + this.time = time; + } + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedTask(Task source) { + title = source.getTitle().toString(); + time = source.getTime().toString(); + } + + /** + * Converts this jaxb-friendly adapted person object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(this.title)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + final Title title = new Title(this.title); + + if (this.time == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Time")); + } + final EventTime time = new EventTime(this.time); + + return new Task(title, time); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTask)) { + return false; + } + + XmlAdaptedTask otherTask = (XmlAdaptedTask) other; + return Objects.equals(title, otherTask.title) + && Objects.equals(time, otherTask.time); + } +} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index dc820896c312..66956ebd8ad5 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -20,7 +20,15 @@ public class XmlSerializableAddressBook { @XmlElement private List<XmlAdaptedPerson> persons; @XmlElement + private List<XmlAdaptedStudent> students; + @XmlElement private List<XmlAdaptedTag> tags; + @XmlElement + private List<XmlAdaptedAppointment> appointments; + @XmlElement + private List<XmlAdaptedTask> tasks; + @XmlElement + private List<XmlAdaptedShortcutDouble> commandsList; /** * Creates an empty XmlSerializableAddressBook. @@ -28,7 +36,11 @@ public class XmlSerializableAddressBook { */ public XmlSerializableAddressBook() { persons = new ArrayList<>(); + students = new ArrayList<>(); tags = new ArrayList<>(); + appointments = new ArrayList<>(); + tasks = new ArrayList<>(); + commandsList = new ArrayList<>(); } /** @@ -37,14 +49,21 @@ public XmlSerializableAddressBook() { public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + students.addAll(src.getStudentList().stream().map(XmlAdaptedStudent::new).collect(Collectors.toList())); tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + appointments.addAll(src.getAppointmentList().stream().map( + XmlAdaptedAppointment::new).collect(Collectors.toList())); + tasks.addAll(src.getTaskList().stream().map( + XmlAdaptedTask::new).collect(Collectors.toList())); + commandsList.addAll(src.getCommandsList().stream().map(XmlAdaptedShortcutDouble::new) + .collect(Collectors.toList())); } /** * Converts this addressbook into the model's {@code AddressBook} object. * * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. + * {@code XmlAdaptedPerson},{@code XmlAdaptedTag}, {@code XmlAdaptedAppointment}, {@code XmlAdaptedTask}. */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); @@ -54,6 +73,18 @@ public AddressBook toModelType() throws IllegalValueException { for (XmlAdaptedPerson p : persons) { addressBook.addPerson(p.toModelType()); } + for (XmlAdaptedStudent s : students) { + addressBook.addStudent(s.toModelType()); + } + for (XmlAdaptedAppointment a: appointments) { + addressBook.addAppointment(a.toModelType()); + } + for (XmlAdaptedTask t: tasks) { + addressBook.addTask(t.toModelType()); + } + for (XmlAdaptedShortcutDouble s : commandsList) { + addressBook.addShortcutDoubles(s.toModelType()); + } return addressBook; } @@ -66,8 +97,13 @@ public boolean equals(Object other) { if (!(other instanceof XmlSerializableAddressBook)) { return false; } - XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); + return persons.equals(otherAb.persons) + && students.equals(otherAb.students) + && tags.equals(otherAb.tags) + && appointments.equals(otherAb.appointments) + && tasks.equals(otherAb.tasks) + && commandsList.equals(otherAb.commandsList); } } + diff --git a/src/main/java/seedu/address/ui/AppointmentCard.java b/src/main/java/seedu/address/ui/AppointmentCard.java new file mode 100644 index 000000000000..8866578b2e83 --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentCard.java @@ -0,0 +1,69 @@ + +package seedu.address.ui; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25 +/** + * An UI component that displays information of a {@code Appointment}. + */ +public class AppointmentCard extends UiPart<Region> { + + private static final String FXML = "AppointmentListCard.fxml"; + private static final String DATE_FORMAT = "EEE, MMMMM dd, HH:mm a"; + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + + public final Appointment appointment; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label time; + @FXML + private Label endTime; + @FXML + private Label personToMeet; + + public AppointmentCard(Appointment appointment, int displayedIndex) { + super(FXML); + this.appointment = appointment; + id.setText(displayedIndex + ". "); + title.setText(appointment.getTitle().value); + time.setText("From: " + DATE_FORMATTER.format(appointment.getTime().value.getTime())); + endTime.setText("To: " + DATE_FORMATTER.format(appointment.getEndTime().value.getTime())); + if (appointment.getPersonToMeet() != null) { + personToMeet.setText("With " + appointment.getPersonToMeet().getName()); + } else { + personToMeet.setText(""); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AppointmentCard)) { + return false; + } + + // state check + AppointmentCard card = (AppointmentCard) other; + return id.getText().equals(card.id.getText()) + && appointment.equals(card.appointment); + } +} diff --git a/src/main/java/seedu/address/ui/AppointmentListPanel.java b/src/main/java/seedu/address/ui/AppointmentListPanel.java new file mode 100644 index 000000000000..d90ae5d20699 --- /dev/null +++ b/src/main/java/seedu/address/ui/AppointmentListPanel.java @@ -0,0 +1,59 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.ObservableList; + +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25-reused +//Reuse from PersonListPanel class with modification +/** + * Panel containing the list of appointments. + */ +public class AppointmentListPanel extends UiPart<Region> { + private static final String FXML = "AppointmentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(AppointmentListPanel.class); + + @FXML + private ListView<AppointmentCard> appointmentListView; + + public AppointmentListPanel(ObservableList<Appointment> appointmentList) { + super(FXML); + setConnections(appointmentList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Appointment> appointmentList) { + ObservableList<AppointmentCard> mappedList = EasyBind.map(appointmentList, (appointment) -> + new AppointmentCard(appointment, appointmentList.indexOf(appointment) + 1)); + appointmentListView.setItems(mappedList); + appointmentListView.setCellFactory(listView -> new AppointmentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code AppointmentCard}. + */ + class AppointmentListViewCell extends ListCell<AppointmentCard> { + + @Override + protected void updateItem(AppointmentCard appointment, boolean empty) { + super.updateItem(appointment, empty); + + if (empty || appointment == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(appointment.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index bb0d61380d5a..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart<Region> { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(this.getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); - } -} diff --git a/src/main/java/seedu/address/ui/CalendarPanel.java b/src/main/java/seedu/address/ui/CalendarPanel.java new file mode 100644 index 000000000000..94282870af44 --- /dev/null +++ b/src/main/java/seedu/address/ui/CalendarPanel.java @@ -0,0 +1,153 @@ +package seedu.address.ui; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.ArrayList; + +import com.calendarfx.model.Calendar; +import com.calendarfx.model.CalendarSource; +import com.calendarfx.model.Entry; +import com.calendarfx.model.Interval; +import com.calendarfx.view.CalendarView; +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; + +import javafx.scene.layout.Region; +import seedu.address.commons.events.model.AppointmentListChangedEvent; +import seedu.address.commons.events.ui.ToggleCalendarViewEvent; +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25 +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart<Region> { + private static final String FXML = "CalendarPanel.fxml"; + + @FXML + private CalendarView calendarView; + private Calendar calendar; + + private ObservableList<Appointment> appointmentList; + + public CalendarPanel(ObservableList<Appointment> appointmentObservableList) { + super(FXML); + this.appointmentList = appointmentObservableList; + + calendarView = new CalendarView(); + CalendarSource calendarSource = new CalendarSource("My Calendar"); + calendar = new Calendar("Appointments"); + + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + + calendarView.getCalendarSources().add(calendarSource); + calendarSource.getCalendars().add(calendar); + calendar.setStyle(Calendar.Style.getStyle(0)); + calendar.setLookAheadDuration(Duration.ofDays(365)); + + updateCalendar(); + disableViews(); + registerAsAnEventHandler(this); + } + + /** + * Clear the entry list in the CalendarFX calendar and + * populate it with appointment in the updated appointmentList + */ + private void updateCalendar() { + calendar.clear(); + ArrayList<Entry> entries = getEntries(); + for (Entry entry : entries) { + calendar.addEntry(entry); + } + } + + private ArrayList<Entry> getEntries() { + ArrayList<Entry> entries = new ArrayList<>(); + for (Appointment appointment : appointmentList) { + entries.add(getEntry(appointment)); + } + return entries; + } + + private Entry getEntry(Appointment appointment) { + LocalDateTime ldtstart = LocalDateTime.ofInstant( + appointment.getTime().value.getTime().toInstant(), ZoneId.systemDefault()); + LocalDateTime ldtend = LocalDateTime.ofInstant( + appointment.getEndTime().value.getTime().toInstant(), ZoneId.systemDefault()); + String description = appointment.getTitle().value; + return new Entry(description, new Interval(ldtstart, ldtend)); + } + + @Subscribe + private void handleAppointmentListChangedEvent(AppointmentListChangedEvent event) { + appointmentList = event.appointmentList; + Platform.runLater( + this::updateCalendar + ); + } + + + //@@author Sisyphus25-reused + //Reused from https://github.com/CS2103AUG2017-T17-B2/main with modifications + private void setTime() { + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + } + + @Subscribe + private void handleToggleCalendarViewEvent(ToggleCalendarViewEvent event) { + Character c = event.viewMode; + Platform.runLater(() -> toggleView(c)); + } + + public CalendarView getRoot() { + return this.calendarView; + } + + /** + * Remove clutter from interface + */ + private void disableViews() { + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.setShowPageSwitcher(false); + calendarView.setShowSourceTrayButton(false); + calendarView.setShowPageToolBarControls(false); + calendarView.setShowToolBar(false); + calendarView.setShowSourceTray(false); + + calendarView.showDayPage(); + } + + /** + * Changes calendar view accordingly + */ + private void toggleView(Character c) { + switch(c) { + case ('d'): + calendarView.showDayPage(); + return; + case ('w'): + calendarView.showWeekPage(); + return; + case ('m'): + calendarView.showMonthPage(); + return; + default: + //should not reach here + assert (false); + } + } + //@@author +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..039d22521ed6 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -4,6 +4,7 @@ import com.google.common.eventbus.Subscribe; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -17,6 +18,8 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.events.ui.ShowHelpRequestEvent; +import seedu.address.commons.events.ui.ThemeChangeEvent; +import seedu.address.commons.events.ui.ToggleListEvent; import seedu.address.logic.Logic; import seedu.address.model.UserPrefs; @@ -28,19 +31,29 @@ public class MainWindow extends UiPart<Stage> { private static final String FXML = "MainWindow.fxml"; + private static final String EXTENSIONS_STYLESHEET = "view/Extensions.css"; + + private static final String TAG_COLOUR_STYLESHEET = "view/TagColour.css"; + + private static final ThemeList THEME_LIST = new ThemeList(); + + private static final String DEFAULT_THEME = "light"; + private final Logger logger = LogsCenter.getLogger(this.getClass()); private Stage primaryStage; private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; private PersonListPanel personListPanel; + private AppointmentListPanel appointmentListPanel; + private TaskListPanel taskListPanel; private Config config; private UserPrefs prefs; + private CalendarPanel calendarPanel; + private ShortcutListPanel shortcutListPanel; - @FXML - private StackPane browserPlaceholder; + private String theme; @FXML private StackPane commandBoxPlaceholder; @@ -49,7 +62,7 @@ public class MainWindow extends UiPart<Stage> { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane listPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -57,6 +70,9 @@ public class MainWindow extends UiPart<Stage> { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane calendarPlaceholder; + public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { super(FXML, primaryStage); @@ -67,6 +83,7 @@ public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logi this.prefs = prefs; // Configure the UI + setTheme(); setTitle(config.getAppTitle()); setWindowDefaultSize(prefs); @@ -78,6 +95,31 @@ public Stage getPrimaryStage() { return primaryStage; } + //@@author Sisyphus25 + private void setTheme() { + setTheme(DEFAULT_THEME); + } + + private void setTheme(String theme) { + primaryStage.getScene().getStylesheets().add(EXTENSIONS_STYLESHEET); + primaryStage.getScene().getStylesheets().add(TAG_COLOUR_STYLESHEET); + primaryStage.getScene().getStylesheets().add(THEME_LIST.getThemeStyleSheet(theme)); + } + + @Subscribe + private void handleThemeChangeEvent(ThemeChangeEvent event) { + theme = event.theme; + Platform.runLater( + this::changeTheme + ); + } + + private void changeTheme() { + primaryStage.getScene().getStylesheets().clear(); + setTheme(theme); + } + //@@author + private void setAccelerators() { setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); } @@ -116,11 +158,8 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -130,6 +169,15 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(logic); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + calendarPanel = new CalendarPanel(logic.getFilteredAppointmentList()); + calendarPlaceholder.getChildren().add(calendarPanel.getRoot()); + + appointmentListPanel = new AppointmentListPanel(logic.getFilteredAppointmentList()); + taskListPanel = new TaskListPanel(logic.getFilteredTaskList()); + + shortcutListPanel = new ShortcutListPanel(logic.getFilteredShortcutList()); + } void hide() { @@ -169,6 +217,32 @@ public void handleHelp() { helpWindow.show(); } + /** + * Toggles list + */ + @FXML + public void toggleList(String list) { + listPanelPlaceholder.getChildren().clear(); + switch(list) { + case "appointments": + listPanelPlaceholder.getChildren().add(appointmentListPanel.getRoot()); + break; + case "tasks": + listPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + break; + case "contacts": + listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + break; + case "shortcuts": + listPanelPlaceholder.getChildren().add(shortcutListPanel.getRoot()); + break; + + default: + listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + } + + } + void show() { primaryStage.show(); } @@ -185,13 +259,15 @@ public PersonListPanel getPersonListPanel() { return this.personListPanel; } - void releaseResources() { - browserPanel.freeResources(); - } - @Subscribe private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + @Subscribe + private void handleToggleListEvent(ToggleListEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + toggleList(event.list); + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..e1b278f19c96 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -24,6 +24,7 @@ public class PersonCard extends UiPart<Region> { public final Person person; + //@@author @FXML private HBox cardPane; @FXML @@ -47,9 +48,23 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + initTags(person); } + //@@author Sisyphus25-reused + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * Returns the color style for {@code tagName}'s label. + */ + private void initTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(tag.tagColorStyle); + tags.getChildren().add(tagLabel); + }); + } + + //@@author @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/ui/ShortcutCard.java b/src/main/java/seedu/address/ui/ShortcutCard.java new file mode 100644 index 000000000000..b8e2923f96ff --- /dev/null +++ b/src/main/java/seedu/address/ui/ShortcutCard.java @@ -0,0 +1,54 @@ +//@@author shanmu9898 +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * An UI component that displays information of a {@code Shortcut Double} + */ +public class ShortcutCard extends UiPart<Region> { + + private static final String FXML = "ShortcutListCard.fxml"; + + public final ShortcutDoubles shortcutDoubles; + + @FXML + private HBox cardPane; + @FXML + private Label shortcut; + @FXML + private Label command; + @FXML + private Label id; + + public ShortcutCard(ShortcutDoubles shortcutDoubles, int displayedIndex) { + super(FXML); + + this.shortcutDoubles = shortcutDoubles; + id.setText(displayedIndex + ". "); + shortcut.setText("===> " + shortcutDoubles.shortcutWord); + command.setText(shortcutDoubles.commandWord); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ShortcutCard)) { + return false; + } + + // state check + ShortcutCard card = (ShortcutCard) other; + return id.getText().equals(card.id.getText()) + && shortcutDoubles.equals(card.shortcutDoubles); + } +} diff --git a/src/main/java/seedu/address/ui/ShortcutListPanel.java b/src/main/java/seedu/address/ui/ShortcutListPanel.java new file mode 100644 index 000000000000..48d9d5138052 --- /dev/null +++ b/src/main/java/seedu/address/ui/ShortcutListPanel.java @@ -0,0 +1,56 @@ +//@@author shanmu9898 +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * Panel containing the list of Shortcuts. + */ +public class ShortcutListPanel extends UiPart<Region> { + private static final String FXML = "ShortcutListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + + @FXML + private ListView<ShortcutCard> shortcutListView; + + public ShortcutListPanel(ObservableList<ShortcutDoubles> shortcutList) { + super(FXML); + setConnections(shortcutList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<ShortcutDoubles> shortcutList) { + ObservableList<ShortcutCard> mappedList = EasyBind.map(shortcutList, (shortcutDoubles) -> + new ShortcutCard(shortcutDoubles, shortcutList.indexOf(shortcutDoubles) + 1)); + shortcutListView.setItems(mappedList); + shortcutListView.setCellFactory(listView -> new ShortcutListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code AppointmentCard}. + */ + class ShortcutListViewCell extends ListCell<ShortcutCard> { + + @Override + protected void updateItem(ShortcutCard shortcutCard, boolean empty) { + super.updateItem(shortcutCard, empty); + + if (empty || shortcutCard == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(shortcutCard.getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 000000000000..9dd03db3c256 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,76 @@ + +package seedu.address.ui; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.event.Task; + +//@@author Sisyphus25 +/** + * An UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart<Region> { + + private static final String FXML = "TaskListCard.fxml"; + private static final String DATE_FORMAT = "EEE, MMMMM dd, HH:mm a"; + private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT); + private static final Calendar CALENDAR = Calendar.getInstance(); + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label time; + @FXML + private FlowPane tags; + + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + title.setText(task.getTitle().value); + time.setText("Finish before: " + DATE_FORMATTER.format(task.getTime().value.getTime())); + if (task.getTime().isExpired()) { + addExpiredTag(); + } + } + + /** + * Add an expired tag to the Task Card + */ + private void addExpiredTag() { + Label expiredTask = new Label("Expired"); + expiredTask.getStyleClass().add("red"); + tags.getChildren().add(expiredTask); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 000000000000..790b04184a81 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,59 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.ObservableList; + +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.event.Task; + +//@@author Sisyphus25-reused +//Reuse from PersonListPanel class with modification +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart<Region> { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView<TaskCard> taskListView; + + public TaskListPanel(ObservableList<Task> taskList) { + super(FXML); + setConnections(taskList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Task> taskList) { + ObservableList<TaskCard> mappedList = EasyBind.map(taskList, (task) -> + new TaskCard(task, taskList.indexOf(task) + 1)); + taskListView.setItems(mappedList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code TaskCard}. + */ + class TaskListViewCell extends ListCell<TaskCard> { + + @Override + protected void updateItem(TaskCard task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(task.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/ThemeList.java b/src/main/java/seedu/address/ui/ThemeList.java new file mode 100644 index 000000000000..dc1f119ad32c --- /dev/null +++ b/src/main/java/seedu/address/ui/ThemeList.java @@ -0,0 +1,26 @@ +package seedu.address.ui; + +import java.util.HashMap; + +//@@author Sisyphus25 +/** + * Provide list of themes and respective URL to their CSS stylesheet + */ +public class ThemeList { + private HashMap<String, String> themeList; + + public ThemeList() { + themeList = new HashMap<>(); + themeList.put("dark", "view/DarkTheme.css"); + themeList.put("light", "view/LightTheme.css"); + themeList.put("doge", "view/DogeTheme.css"); + themeList.put("galaxy", "view/GalaxyTheme.css"); + } + + public String getThemeStyleSheet(String theme) { + if (!themeList.containsKey(theme)) { + return themeList.get("light"); + } + return themeList.get(theme); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..641fcd6aba6f 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/Tc_logo.png"; private Logic logic; private Config config; @@ -66,7 +66,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/resources/images/Tc_logo.png b/src/main/resources/images/Tc_logo.png new file mode 100644 index 000000000000..87fd018a7d54 Binary files /dev/null and b/src/main/resources/images/Tc_logo.png differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd93..000000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/doge.jpg b/src/main/resources/images/doge.jpg new file mode 100644 index 000000000000..ba9defb7a165 Binary files /dev/null and b/src/main/resources/images/doge.jpg differ diff --git a/src/main/resources/images/galaxy.jpg b/src/main/resources/images/galaxy.jpg new file mode 100644 index 000000000000..7b47fc657510 Binary files /dev/null and b/src/main/resources/images/galaxy.jpg differ diff --git a/src/main/resources/view/AppointmentListCard.fxml b/src/main/resources/view/AppointmentListCard.fxml new file mode 100644 index 000000000000..040a43d1528b --- /dev/null +++ b/src/main/resources/view/AppointmentListCard.fxml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="80" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15" /> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="title" text="\$first" styleClass="cell_big_label" /> + </HBox> + <Label fx:id="time" styleClass="cell_small_label" text="\$time" /> + <Label fx:id="endTime" styleClass="cell_small_label" text="\$endTime" /> + <Label fx:id="personToMeet" styleClass="cell_small_label" text="\$personToMeet" /> + </VBox> + </GridPane> +</HBox> diff --git a/src/main/resources/view/AppointmentListPanel.fxml b/src/main/resources/view/AppointmentListPanel.fxml new file mode 100644 index 000000000000..812e0a96f47c --- /dev/null +++ b/src/main/resources/view/AppointmentListPanel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="appointmentListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<?import javafx.scene.layout.StackPane?> -<?import javafx.scene.web.WebView?> - -<StackPane xmlns:fx="http://javafx.com/fxml/1"> - <WebView fx:id="browser"/> -</StackPane> diff --git a/src/main/resources/view/CalendarPanel.fxml b/src/main/resources/view/CalendarPanel.fxml new file mode 100644 index 000000000000..c040ccf54ad7 --- /dev/null +++ b/src/main/resources/view/CalendarPanel.fxml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.StackPane?> + +<StackPane xmlns="http://javafx.com/javafx/8.0.101"> + <children> + <BorderPane prefHeight="200.0" prefWidth="200.0" /> + </children> +</StackPane> diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index d06336391cca..d1ab65ea5597 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -78,7 +78,7 @@ .split-pane:horizontal .split-pane-divider { -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-border-color: transparent transparent transparent #ffffff; } .split-pane { @@ -91,16 +91,21 @@ -fx-background-insets: 0; -fx-padding: 0; -fx-background-color: derive(#1d1d1d, 20%); + } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #515658; } .list-cell:filled:odd { @@ -114,6 +119,7 @@ .list-cell:filled:selected #cardPane { -fx-border-color: #3e7b91; -fx-border-width: 1; + } .list-cell .label { @@ -183,6 +189,25 @@ -fx-background-color: derive(#1d1d1d, 30%); } +.calendar-panel .button{ + -fx-text-fill: #000000; +} + +.calendar-panel .list-cell .label { + -fx-text-fill: white !important; +} + +.calendar-panel { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.app-title { + -fx-text-fill: white; + -fx-font-family: "Franklin Gothic Medium"; + -fx-font-weight: bold; + -fx-font-size: 36px +} + .context-menu { -fx-background-color: derive(#1d1d1d, 50%); } @@ -281,34 +306,51 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#000000, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; + -fx-background-color: derive(#ffffff, 50%); + } .scroll-bar .increment-button, .scroll-bar .decrement-button { -fx-background-color: transparent; - -fx-padding: 0 0 0 0; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; } -.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { - -fx-shape: " "; +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; } .scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { - -fx-padding: 1 8 1 8; + -fx-padding: 3 3 3 3; } -.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { - -fx-padding: 8 1 8 1; +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; } #cardPane { -fx-background-color: transparent; - -fx-border-width: 0; + -fx-border-width: 10pt; } #commandTypeLabel { @@ -335,17 +377,3 @@ -fx-background-color: transparent, #383838, transparent, #383838; -fx-background-radius: 0; } - -#tags { - -fx-hgap: 7; - -fx-vgap: 3; -} - -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; - -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; -} diff --git a/src/main/resources/view/DogeTheme.css b/src/main/resources/view/DogeTheme.css new file mode 100644 index 000000000000..5c3db308e63d --- /dev/null +++ b/src/main/resources/view/DogeTheme.css @@ -0,0 +1,393 @@ +/* @@author Sisyphus25-reused +Reused from DarkTheme.css with modifications */ + +.root { + -fx-background-image: url("../images/doge.jpg"); + -fx-background-repeat: repeat; + -fx-background-position: center center; + -fx-effect: dropshadow(three-pass-box, black, 30, 0.5, 0, 0); +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: white; + -fx-font-family: "Franklin Gothic Heavy"; + -fx-font-size: 40px; +} + +.app-title .text { + -fx-stroke: black; + -fx-stroke-width: 1px; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: transparent; + -fx-background-color: transparent; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35px; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: transparent; + -fx-border-color: transparent transparent transparent #635615; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: transparent; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: transparent; + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:odd { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:selected { + -fx-background-color: #efdc7f; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #c1b05b; + -fx-border-width: 1; + +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #efdc7f; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #efdc7f; +} + +.anchor-pane { + -fx-background-color: transparent; +} + +.pane-with-border { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: transparent; + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: transparent !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: transparent; + -fx-border-color: derive(#efdc7f, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: transparent; +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: transparent; + background-color: transparent; +} + +.context-menu { + -fx-background-color: derive(#efdc7f, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: transparent; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: transparent; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#efdc7f, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#efdc7f, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(#635615, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: #efdc7f; + -fx-background-insets: 0; + -fx-border-color: #efdc7f #efdc7f black #efdc7f ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay { + -fx-background-color: transparent; +} + +#resultDisplay .scroll-pane .viewport{ + -fx-background-color: transparent; +} + +#resultDisplay .content { + -fx-background-color: #efdc7f; + -fx-background-radius: 0; +} diff --git a/src/main/resources/view/GalaxyTheme.css b/src/main/resources/view/GalaxyTheme.css new file mode 100644 index 000000000000..001918000f2a --- /dev/null +++ b/src/main/resources/view/GalaxyTheme.css @@ -0,0 +1,387 @@ +/* @@author Sisyphus25-reused +reused from DarkTheme.css with modifications */ + +.root { + -fx-background-image: url("../images/galaxy.jpg"); + -fx-background-size: cover; + -fx-background-position: center center; + -fx-effect: dropshadow(three-pass-box, black, 30, 0.5, 0, 0); +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: white; + -fx-font-family: "Franklin Gothic Heavy"; + -fx-font-size: 40px; +} + +.app-title .text { + -fx-stroke: black; + -fx-stroke-width: 1px; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: transparent; + -fx-background-color: transparent; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35px; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: transparent; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: transparent; + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #edf2f9; +} + +.list-cell:filled:odd { + -fx-background-color: #edf2f9; +} + +.list-cell:filled:selected { + -fx-background-color: #c0c5f9; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #edf2f9; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #edf2f9; +} + +.anchor-pane { + -fx-background-color: transparent; +} + +.pane-with-border { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: transparent; + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: transparent !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI"; + -fx-text-fill: #edf2f9; +} + +.status-bar-with-border { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: transparent; +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: transparent; + background-color: transparent; +} + +.context-menu { + -fx-background-color: derive(#070f60, 50%); +} + +.context-menu .label { + -fx-text-fill: #edf2f9; +} + +.menu-bar { + -fx-background-color: transparent; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI"; + -fx-text-fill: #edf2f9; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: transparent; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#edf2f9, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#edf2f9, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(#070f60, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: #edf2f9; + -fx-background-insets: 0; + -fx-border-color: #edf2f9 #edf2f9 black #edf2f9 ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay { + -fx-background-color: transparent; +} + +#resultDisplay .scroll-pane .viewport{ + -fx-background-color: transparent; +} + +#resultDisplay .content { + -fx-background-color: #edf2f9; + -fx-background-radius: 0; +} diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css new file mode 100644 index 000000000000..1fc763870e5d --- /dev/null +++ b/src/main/resources/view/LightTheme.css @@ -0,0 +1,387 @@ +/* @@author Sisyphus25-reused +Reused from DarkTheme.css with modifications */ + +.background { + -fx-background-color: #ffffff; + background-color: #ffffff; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.app-title { + -fx-text-fill: brown; + -fx-font-family: "Franklin Gothic Medium"; + -fx-font-weight: bold; + -fx-font-size: 36px +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent transparent transparent tomato; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffffff, 20%); + +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-background-radius: 10 10 10 10; + -fx-border-radius: 10 10 10 10; + -fx-padding: 10px; + -fx-background-insets: 3px, 3px; + -fx-background-color: transparent +} + +.list-cell:filled:even { + -fx-background-color: #ffd0d0; +} + +.list-cell:filled:odd { + -fx-background-color: #ffd0d0; +} + +.list-cell:filled:selected { + -fx-background-color: #ffc2c2; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #ffc2c2; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: brown; +} + +.cell_big_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #ffd0d0; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #ffd0d0; +} + +.anchor-pane { + -fx-background-color: derive(#ffffff, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffd0d0, 20%); + -fx-text-fill: white; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: brown; +} + +.result-display .label { + -fx-text-fill: white !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: brown; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffd0d0, 30%); + -fx-border-color: derive(#ffd0d0, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: brown; +} + +.grid-pane { + -fx-background-color: derive(#ffd0d0, 30%); + -fx-border-color: derive(#ffd0d0, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffd0d0, 30%); +} + +.calendar-panel .button { + -fx-text-fill: #000000; +} + +.calendar-panel { + -fx-background-color: #ffffff; + background-color: #ffffff; +} + +.calendar-panel .content { + -fx-border-color: transparent transparent tomato transparent; +} + +.calendar-panel .header { + -fx-border-color: tomato transparent transparent transparent; +} + +.context-menu { + -fx-background-color: derive(#ffd0d0, 50%); +} + +.context-menu .label { + -fx-text-fill: brown; +} + +.menu-bar { + -fx-background-color: derive(#ffd0d0, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: brown; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: brown; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffd0d0, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: brown; + -fx-text-fill: brown; +} + +.scroll-bar { + -fx-background-color: derive(#ffd0d0, 20%); + -fx-border-radius: 20px; + -fx-background-radius: 20px; + +} + +.scroll-bar .thumb { + -fx-background-color: derive(tomato, 20%); + +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-rotate: 0; + +} + +.scroll-bar .increment-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; +} + +.scroll-bar .decrement-arrow { + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-background-color: #ffffff; + -fx-rotate: -180; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .increment-arrow{ + -fx-rotate: -90; + -fx-padding: 3 3 3 3; +} + +.scroll-bar:horizontal .decrement-arrow { + -fx-rotate: 90; + -fx-padding: 3 3 3 3; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 10pt; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #ffd0d0 transparent #ffd0d0; + -fx-background-insets: 0; + -fx-border-color: #ffd0d0 #ffd0d0 brown #ffd0d0 ; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI"; + -fx-font-size: 13pt; + -fx-text-fill: brown; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, brown, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffffff, transparent, #ffffff; + -fx-background-radius: 0; + -fx-border-color: #ffd0d0; +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..ce48328c3961 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<?import java.net.URL?> <?import javafx.geometry.Insets?> <?import javafx.scene.Scene?> <?import javafx.scene.control.Menu?> @@ -11,18 +10,15 @@ <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.control.Label?> <fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" minWidth="450" minHeight="600"> <icons> - <Image url="@/images/address_book_32.png" /> + <Image url="@/images/Tc_logo.png" /> </icons> <scene> <Scene> - <stylesheets> - <URL value="@DarkTheme.css" /> - <URL value="@Extensions.css" /> - </stylesheets> - <VBox> <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> <Menu mnemonicParsing="false" text="File"> @@ -32,33 +28,43 @@ <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help" /> </Menu> </MenuBar> - - <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> - - <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" - minHeight="100" prefHeight="100" maxHeight="100"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> - - <SplitPane id="splitPane" fx:id="splitPane" dividerPositions="0.4" VBox.vgrow="ALWAYS"> + <SplitPane id="splitPane" fx:id="splitPane" dividerPositions="0.4" VBox.vgrow="ALWAYS" > <VBox fx:id="personList" minWidth="340" prefWidth="340" SplitPane.resizableWithParent="false"> <padding> <Insets top="10" right="10" bottom="10" left="10" /> </padding> - <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + <StackPane fx:id="listPanelPlaceholder" VBox.vgrow="ALWAYS"/> + </VBox> + + <VBox> + <StackPane fx:id="appTitle" maxHeight="60"> + <HBox spacing="5" alignment="CENTER_LEFT"> + <padding> + <Insets top="10" right="15" bottom="0" left="10" /> + </padding> + <Label fx:id="id" text="TeachConnect" styleClass="app-title"> + </Label> + </HBox> + </StackPane> + <StackPane fx:id="calendarPlaceholder" prefWidth="200" styleClass="calendar-panel" > + <padding> + <Insets top="10" right="10" bottom="10" left="10" /> + </padding> + </StackPane> + <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" + minHeight="100" prefHeight="100" maxHeight="100" alignment="BOTTOM_RIGHT" > + <padding> + <Insets top="5" right="10" bottom="5" left="10" /> + </padding> + </StackPane> + + <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> + <padding> + <Insets top="5" right="10" bottom="5" left="10" /> + </padding> + </StackPane> </VBox> - <StackPane fx:id="browserPlaceholder" prefWidth="340" > - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - </StackPane> </SplitPane> <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> @@ -66,3 +72,4 @@ </Scene> </scene> </fx:root> + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..0e9b528324f8 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -27,7 +27,11 @@ </Label> <Label fx:id="name" text="\$first" styleClass="cell_big_label" /> </HBox> - <FlowPane fx:id="tags" /> + <FlowPane fx:id="tags"> + <padding> + <Insets top="5" right="10" bottom="5" left="0" /> + </padding> + </FlowPane> <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" /> <Label fx:id="address" styleClass="cell_small_label" text="\$address" /> <Label fx:id="email" styleClass="cell_small_label" text="\$email" /> diff --git a/src/main/resources/view/ShortcutListCard.fxml b/src/main/resources/view/ShortcutListCard.fxml new file mode 100644 index 000000000000..a143084b343f --- /dev/null +++ b/src/main/resources/view/ShortcutListCard.fxml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- @@author shanmu9898 --> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="80" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15" /> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="command" text="\$command" styleClass="cell_big_label" /> + <Label fx:id="shortcut" styleClass="cell_big_label_label" text="\$shortcut" /> + </HBox> + + </VBox> + </GridPane> +</HBox> diff --git a/src/main/resources/view/ShortcutListPanel.fxml b/src/main/resources/view/ShortcutListPanel.fxml new file mode 100644 index 000000000000..462323ce8036 --- /dev/null +++ b/src/main/resources/view/ShortcutListPanel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- @@author shanmu9898 --> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="shortcutListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/TagColour.css b/src/main/resources/view/TagColour.css new file mode 100644 index 000000000000..8bdd5ec65b00 --- /dev/null +++ b/src/main/resources/view/TagColour.css @@ -0,0 +1,66 @@ +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} +/* +@@author Sisyphus25-reused +Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + */ +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: red; +} + +#tags .yellow { + -fx-background-color: yellow; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: white; + -fx-background-color: blue; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: orange; +} + +#tags .brown { + -fx-text-fill: white; + -fx-background-color: brown; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: green; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: pink; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .grey { + -fx-text-fill: black; + -fx-background-color: grey; +} diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 000000000000..83cb2f2e8d27 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<?import javafx.scene.layout.FlowPane?> +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> +<GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="80" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15" /> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="title" text="\$first" styleClass="cell_big_label" /> + </HBox> + <FlowPane fx:id="tags"> + <padding> + <Insets top="5" right="10" bottom="5" left="0" /> + </padding> + </FlowPane> + <Label fx:id="time" styleClass="cell_small_label" text="\$time" /> + </VBox> +</GridPane> +</HBox> + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 000000000000..a58887c89d18 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="taskListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/test/data/XmlAddressBookStorageTest/importsamplefile.xml b/src/test/data/XmlAddressBookStorageTest/importsamplefile.xml new file mode 100644 index 000000000000..d4e4bf93f2de --- /dev/null +++ b/src/test/data/XmlAddressBookStorageTest/importsamplefile.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- @@author shanmu9898 --> +<addressbook> + <persons> + <name>Funny Yeoh</name> + <phone>874388071</phone> + <email>funnyeoh@example.com</email> + <address>Blk 30 Geylang Street 29, #06-40</address> + <tagged> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Yu</name> + <phone>992727581</phone> + <email>funnyyu@example.com</email> + <address>Blk 30 Lorong 3 Serangoon Gardens, #07-18</address> + <tagged> + <tagName>colleagues</tagName> + <tagColorStyle>yellow</tagColorStyle> + </tagged> + <tagged> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Oliveiro</name> + <phone>93210283</phone> + <email>funny@example.com</email> + <address>Blk 11 Ang Mo Kio Street 74, #11-04</address> + <tagged> + <tagName>neighbours</tagName> + <tagColorStyle>yellow</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Li</name> + <phone>91031282</phone> + <email>lidavid@example.com</email> + <address>Blk 436 Serangoon Gardens Street 26, #16-43</address> + <tagged> + <tagName>family</tagName> + <tagColorStyle>orange</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Ibrahim</name> + <phone>92492021</phone> + <email>funnyirfan@example.com</email> + <address>Blk 47 Tampines Street 20, #17-35</address> + <tagged> + <tagName>classmates</tagName> + <tagColorStyle>teal</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Balakrishnan</name> + <phone>92624417</phone> + <email>funnyb@example.com</email> + <address>Blk 45 Aljunied Street 85, #11-31</address> + <tagged> + <tagName>colleagues</tagName> + <tagColorStyle>yellow</tagColorStyle> + </tagged> + </persons> + <persons> + <name>Funny Doe</name> + <phone>98765432</phone> + <email>funnyd@example.com</email> + <address>311, Clementi Ave 2, #02-25</address> + <tagged> + <tagName>owesMoney</tagName> + <tagColorStyle>teal</tagColorStyle> + </tagged> + <tagged> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tagged> + </persons> + <tags> + <tagName>colleagues</tagName> + <tagColorStyle>yellow</tagColorStyle> + </tags> + <tags> + <tagName>classmates</tagName> + <tagColorStyle>teal</tagColorStyle> + </tags> + <tags> + <tagName>neighbours</tagName> + <tagColorStyle>yellow</tagColorStyle> + </tags> + <tags> + <tagName>family</tagName> + <tagColorStyle>orange</tagColorStyle> + </tags> + <tags> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tags> + <tags> + <tagName>owesMoney</tagName> + <tagColorStyle>teal</tagColorStyle> + </tags> +</addressbook> diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml index 5fa697c22c4c..9a1ae610cc5c 100644 --- a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml +++ b/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml @@ -1,5 +1,7 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <addressbook> - <!-- Invalid tag --> - <tags>frie!nds</tags> + <tags> + <tagName>frie!nds</tagName> + <tagColorStyle>brown</tagColorStyle> + </tags> </addressbook> diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml index c778cccc4c89..e17e3ffbe419 100644 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml @@ -7,15 +7,24 @@ <phone>85355255</phone> <email>alice@example.com</email> <address>123, Jurong West Ave 6, #08-111</address> - <tagged>friends</tagged> + <tagged> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tagged> </persons> <persons> <name>Benson Meier</name> <phone>98765432</phone> <email>johnd@example.com</email> <address>311, Clementi Ave 2, #02-25</address> - <tagged>owesMoney</tagged> - <tagged>friends</tagged> + <tagged> + <tagName>owesMoney</tagName> + <tagColorStyle>teal</tagColorStyle> + </tagged> + <tagged> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tagged> </persons> <persons> <name>Carl Kurz</name> @@ -47,6 +56,32 @@ <email>anna@example.com</email> <address>4th street</address> </persons> - <tags>friends</tags> - <tags>owesMoney</tags> + <tags> + <tagName>owesMoney</tagName> + <tagColorStyle>teal</tagColorStyle> + </tags> + <tags> + <tagName>friends</tagName> + <tagColorStyle>brown</tagColorStyle> + </tags> + <appointments> + <title>Meeting with parents + 09/10/2018 10:00 + 09/10/2018 11:00 + Alice Pauline Email: alice@example.com + + + Consultation session + 04/07/2018 10:00 + 04/07/2018 11:00 + Bob Choo Email: bob@example.com + + + To do + + + + Mark papers + + diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml index ba49c971e884..039175d5bb31 100644 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ b/src/test/data/XmlUtilTest/invalidPersonField.xml @@ -5,5 +5,8 @@ 9482asf424 hans@example
4th street
- friends + + classmates + teal + diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..2739e7077b65 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -4,5 +4,8 @@ 9482424 hans@example
4th street
- friends + + classmates + teal + diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..e10997e8f5f5 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -4,5 +4,8 @@ 9482424 hans@example
4th street
- friends + + classmates + teal + diff --git a/src/test/java/guitests/guihandles/BrowserPanelHandle.java b/src/test/java/guitests/guihandles/BrowserPanelHandle.java deleted file mode 100644 index bd3633af78f3..000000000000 --- a/src/test/java/guitests/guihandles/BrowserPanelHandle.java +++ /dev/null @@ -1,64 +0,0 @@ -package guitests.guihandles; - -import java.net.URL; - -import guitests.GuiRobot; -import javafx.concurrent.Worker; -import javafx.scene.Node; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; - -/** - * A handler for the {@code BrowserPanel} of the UI. - */ -public class BrowserPanelHandle extends NodeHandle { - - public static final String BROWSER_ID = "#browser"; - - private boolean isWebViewLoaded = true; - - private URL lastRememberedUrl; - - public BrowserPanelHandle(Node browserPanelNode) { - super(browserPanelNode); - - WebView webView = getChildNode(BROWSER_ID); - WebEngine engine = webView.getEngine(); - new GuiRobot().interact(() -> engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { - if (newState == Worker.State.RUNNING) { - isWebViewLoaded = false; - } else if (newState == Worker.State.SUCCEEDED) { - isWebViewLoaded = true; - } - })); - } - - /** - * Returns the {@code URL} of the currently loaded page. - */ - public URL getLoadedUrl() { - return WebViewUtil.getLoadedUrl(getChildNode(BROWSER_ID)); - } - - /** - * Remembers the {@code URL} of the currently loaded page. - */ - public void rememberUrl() { - lastRememberedUrl = getLoadedUrl(); - } - - /** - * Returns true if the current {@code URL} is different from the value remembered by the most recent - * {@code rememberUrl()} call. - */ - public boolean isUrlChanged() { - return !lastRememberedUrl.equals(getLoadedUrl()); - } - - /** - * Returns true if the browser is done loading a page, or if this browser has yet to load any page. - */ - public boolean isLoaded() { - return isWebViewLoaded; - } -} diff --git a/src/test/java/guitests/guihandles/CalendarPanelHandle.java b/src/test/java/guitests/guihandles/CalendarPanelHandle.java new file mode 100644 index 000000000000..2daaf8476448 --- /dev/null +++ b/src/test/java/guitests/guihandles/CalendarPanelHandle.java @@ -0,0 +1,15 @@ +package guitests.guihandles; + +import javafx.scene.Node; + +/** + * Provides a handle for CalendarPanel + */ +public class CalendarPanelHandle extends NodeHandle { + + public static final String CALENDAR_VIEW_ID = "#calendarPlaceholder"; + + protected CalendarPanelHandle(Node rootNode) { + super(rootNode); + } +} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..df196a92f971 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -12,7 +12,7 @@ public class MainWindowHandle extends StageHandle { private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; + private final CalendarPanelHandle calendarPanel; public MainWindowHandle(Stage stage) { super(stage); @@ -22,7 +22,7 @@ public MainWindowHandle(Stage stage) { commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + calendarPanel = new CalendarPanelHandle(getChildNode(CalendarPanelHandle.CALENDAR_VIEW_ID)); } public PersonListPanelHandle getPersonListPanel() { @@ -45,7 +45,7 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; + public CalendarPanelHandle getCalendarPanel() { + return calendarPanel; } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index d337d3a4cee9..67867a7306bb 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -62,10 +62,26 @@ public String getEmail() { return emailLabel.getText(); } - public List getTags() { + public List getTagsNames() { return tagLabels .stream() .map(Label::getText) .collect(Collectors.toList()); } + + //@@author Sisyphus25-reused + //Reused from https://github.com/se-edu/addressbook-level4/pull/798/commits/167b3d0b4f7ad34296d2fbf505f9ae71f983f53c + /** + * + * @param tag Text value of the tag label + * @return List of style classes for tag label with text value {@code tag} + */ + public List getTagStyleClasses(String tag) { + return tagLabels + .stream() + .filter(label -> label.getText().equals(tag)) + .map(Label::getStyleClass) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No such tag.")); + } } diff --git a/src/test/java/guitests/guihandles/WebViewUtil.java b/src/test/java/guitests/guihandles/WebViewUtil.java index 99f7db998747..b833b492661f 100644 --- a/src/test/java/guitests/guihandles/WebViewUtil.java +++ b/src/test/java/guitests/guihandles/WebViewUtil.java @@ -3,7 +3,6 @@ import java.net.MalformedURLException; import java.net.URL; -import guitests.GuiRobot; import javafx.scene.web.WebView; /** @@ -22,10 +21,4 @@ public static URL getLoadedUrl(WebView webView) { } } - /** - * If the {@code browserPanelHandle}'s {@code WebView} is loading, sleeps the thread till it is successfully loaded. - */ - public static void waitUntilBrowserLoaded(BrowserPanelHandle browserPanelHandle) { - new GuiRobot().waitForEvent(browserPanelHandle::isLoaded); - } } diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java index cc4bf567cb44..1de858c4abcc 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/address/commons/core/ConfigTest.java @@ -14,7 +14,7 @@ public class ConfigTest { @Test public void toString_defaultObject_stringReturned() { - String defaultConfigAsString = "App title : Address App\n" + String defaultConfigAsString = "App title : TeachConnect\n" + "Current log level : INFO\n" + "Preference file Location : preferences.json"; diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java index 8a6fe5fcb7d6..1329ad513c2e 100644 --- a/src/test/java/seedu/address/commons/util/AppUtilTest.java +++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java @@ -15,7 +15,7 @@ public class AppUtilTest { @Test public void getImage_exitingImage() { - assertNotNull(AppUtil.getImage("/images/address_book_32.png")); + assertNotNull(AppUtil.getImage("/images/Tc_logo.png")); } diff --git a/src/test/java/seedu/address/commons/util/XmlUtilTest.java b/src/test/java/seedu/address/commons/util/XmlUtilTest.java index 56b6ef8f40d3..8a058f551612 100644 --- a/src/test/java/seedu/address/commons/util/XmlUtilTest.java +++ b/src/test/java/seedu/address/commons/util/XmlUtilTest.java @@ -39,7 +39,8 @@ public class XmlUtilTest { private static final String VALID_PHONE = "9482424"; private static final String VALID_EMAIL = "hans@example"; private static final String VALID_ADDRESS = "4th street"; - private static final List VALID_TAGS = Collections.singletonList(new XmlAdaptedTag("friends")); + private static final List VALID_TAGS = Collections.singletonList( + new XmlAdaptedTag("classmates", "teal")); @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index 954c1fb04388..4d12b62396b8 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -41,8 +41,9 @@ public void execute_commandExecutionError_throwsCommandException() { @Test public void execute_validCommand_success() { - String listCommand = ListCommand.COMMAND_WORD; - assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model); + String listCommand = ListCommand.COMMAND_WORD + " " + ListCommand.TYPE_CONTACT; + assertCommandSuccess(listCommand, + ListCommand.MESSAGE_SUCCESS + ListCommand.TYPE_CONTACT, model); assertHistoryCorrect(listCommand); } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java index d3ff5043e126..8de9ff300fe3 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java @@ -35,7 +35,7 @@ public void execute_newPerson_success() throws Exception { expectedModel.addPerson(validPerson); assertCommandSuccess(prepareCommand(validPerson, model), model, - String.format(AddCommand.MESSAGE_SUCCESS, validPerson), expectedModel); + String.format(AddCommand.MESSAGE_ADD_PERSON_SUCCESS, validPerson), expectedModel); } @Test diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 461cf09d1217..868d33d35850 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -4,17 +4,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; -import java.util.function.Predicate; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import javafx.collections.ObservableList; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; @@ -23,8 +20,8 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.modelstub.ModelStub; public class AddCommandTest { @@ -44,7 +41,7 @@ public void execute_personAcceptedByModel_addSuccessful() throws Exception { CommandResult commandResult = getAddCommandForPerson(validPerson, modelStub).execute(); - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.feedbackToUser); + assertEquals(String.format(AddCommand.MESSAGE_ADD_PERSON_SUCCESS, validPerson), commandResult.feedbackToUser); assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); } @@ -92,49 +89,6 @@ private AddCommand getAddCommandForPerson(Person person, Model model) { return command; } - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void addPerson(Person person) throws DuplicatePersonException { - fail("This method should not be called."); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - fail("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - fail("This method should not be called."); - return null; - } - - @Override - public void deletePerson(Person target) throws PersonNotFoundException { - fail("This method should not be called."); - } - - @Override - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException { - fail("This method should not be called."); - } - - @Override - public ObservableList getFilteredPersonList() { - fail("This method should not be called."); - return null; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - fail("This method should not be called."); - } - } - /** * A Model stub that always throw a DuplicatePersonException when trying to add a person. */ @@ -157,7 +111,7 @@ private class ModelStubAcceptingPersonAdded extends ModelStub { final ArrayList personsAdded = new ArrayList<>(); @Override - public void addPerson(Person person) throws DuplicatePersonException { + public void addPerson(Person person) { requireNonNull(person); personsAdded.add(person); } diff --git a/src/test/java/seedu/address/logic/commands/ChangeThemeCommandTest.java b/src/test/java/seedu/address/logic/commands/ChangeThemeCommandTest.java new file mode 100644 index 000000000000..0759841e9847 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ChangeThemeCommandTest.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static seedu.address.logic.commands.ChangeThemeCommand.MESSAGE_CHANGE_THEME_SUCCESS; + +import org.junit.Rule; +import org.junit.Test; + +import seedu.address.commons.events.ui.ThemeChangeEvent; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.ui.testutil.EventsCollectorRule; + +//@@author Sisyphus25 +public class ChangeThemeCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + @Test + public void execute_help_success() throws CommandException { + String theme = "dark"; + CommandResult result = new ChangeThemeCommand(theme).execute(); + assertEquals(MESSAGE_CHANGE_THEME_SUCCESS, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ThemeChangeEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 9a5679cc29b6..76aa119e19a4 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -5,9 +5,12 @@ import static org.junit.Assert.fail; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +42,11 @@ public class CommandTestUtil { public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_TAG_STUDENT = "student"; + public static final String VALID_TAG_NOTUSED = "notused"; + public static final String VALID_TITLE = "Consultation"; + public static final String VALID_START_TIME = "10/10/2018 10:00"; + public static final String VALID_END_TIME = "10/10/2018 12:00"; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -49,6 +57,7 @@ public class CommandTestUtil { public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; + public static final String TAG_DESC_STUDENT = " " + PREFIX_TAG + VALID_TAG_STUDENT; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names @@ -57,8 +66,18 @@ public class CommandTestUtil { public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String TITLE_DESC = " " + PREFIX_TITLE + VALID_TITLE; + public static final String START_TIME_DESC = " " + PREFIX_START_TIME + VALID_START_TIME; + public static final String END_TIME_DESC = " " + PREFIX_END_TIME + VALID_END_TIME; + + public static final String INVALID_TITLE_DESC = " " + PREFIX_TITLE + " "; // spaces only + public static final String INVALID_START_TIME_DESC = " " + PREFIX_START_TIME + "911afddf"; // not a time stamp + public static final String INVALID_END_TIME_DESC = + " " + PREFIX_END_TIME + "May 20, 2018 10am"; // not in correct format + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + public static final String PREAMBLE_STUDENT = " student "; public static final EditCommand.EditPersonDescriptor DESC_AMY; public static final EditCommand.EditPersonDescriptor DESC_BOB; diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 866e6a9be32a..7249cdef6cf6 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -8,8 +8,8 @@ import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.Test; @@ -33,8 +33,8 @@ public class DeleteCommandTest { @Test public void execute_validIndexUnfilteredList_success() throws Exception { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); @@ -54,10 +54,10 @@ public void execute_invalidIndexUnfilteredList_throwsCommandException() throws E @Test public void execute_validIndexFilteredList_success() throws Exception { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); @@ -70,9 +70,9 @@ public void execute_validIndexFilteredList_success() throws Exception { @Test public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); @@ -86,8 +86,8 @@ public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception UndoRedoStack undoRedoStack = new UndoRedoStack(); UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST); Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); // delete -> first person deleted @@ -130,11 +130,11 @@ public void executeUndoRedo_validIndexFilteredList_samePersonDeleted() throws Ex UndoRedoStack undoRedoStack = new UndoRedoStack(); UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); - DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteCommand = prepareCommand(INDEX_FIRST); Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - showPersonAtIndex(model, INDEX_SECOND_PERSON); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + showPersonAtIndex(model, INDEX_SECOND); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); // delete -> deletes second person in unfiltered person list / first person in filtered person list deleteCommand.execute(); undoRedoStack.push(deleteCommand); @@ -143,21 +143,21 @@ public void executeUndoRedo_validIndexFilteredList_samePersonDeleted() throws Ex assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); expectedModel.deletePerson(personToDelete); - assertNotEquals(personToDelete, model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased())); + assertNotEquals(personToDelete, model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased())); // redo -> deletes same second person in unfiltered person list assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); } @Test public void equals() throws Exception { - DeleteCommand deleteFirstCommand = prepareCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = prepareCommand(INDEX_SECOND_PERSON); + DeleteCommand deleteFirstCommand = prepareCommand(INDEX_FIRST); + DeleteCommand deleteSecondCommand = prepareCommand(INDEX_SECOND); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = prepareCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteFirstCommandCopy = prepareCommand(INDEX_FIRST); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // one command preprocessed when previously equal -> returns false diff --git a/src/test/java/seedu/address/logic/commands/DeleteShortcutCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteShortcutCommandTest.java new file mode 100644 index 000000000000..45e1af45cf44 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteShortcutCommandTest.java @@ -0,0 +1,118 @@ +//@@author shanmu9898 +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class DeleteShortcutCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + private Model expectedModel; + private final String validtestingCommandWord = "list"; + private final String validtestingShortcutWord = "l"; + private final String invalidtestingCommandWord = "king"; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new DeleteShortcutCommand(null, validtestingShortcutWord); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new DeleteShortcutCommand(validtestingCommandWord, null); + } + + @Test + public void executeUndoCommand_invalidCommandWord_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + CommandResult commandResult = getDeleteShortcutForCommand(validtestingShortcutWord, + invalidtestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_inputNotPresent_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + CommandResult commandResult = getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_validInput_commandSuccess() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validtestingShortcutWord, validtestingCommandWord); + model.addCommandShortcut(shortcutDoubles); + assertCommandSuccess(getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model), + model, + String.format(DeleteShortcutCommand + .MESSAGE_DELETE_SHORTCUT_SUCCESS), + expectedModel); + } + + @Test + public void equals() throws Exception { + DeleteShortcutCommand deleteFirstCommand = getDeleteShortcutForCommand(validtestingShortcutWord, + validtestingCommandWord, + model); + DeleteShortcutCommand deleteSecondCommand = getDeleteShortcutForCommand("c", + "clear", + model); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteShortcutCommand deleteFirstCommandCopy = getDeleteShortcutForCommand("l", + "list", + model); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Generates a new AddCommand with the details of the given person. + */ + private DeleteShortcutCommand getDeleteShortcutForCommand(String shortcutWord, String commandWord, Model model) { + DeleteShortcutCommand command = new DeleteShortcutCommand(commandWord, shortcutWord); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index a8b104d3a81d..643b34355c69 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -13,8 +13,8 @@ import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.Test; @@ -43,7 +43,7 @@ public class EditCommandTest { public void execute_allFieldsSpecifiedUnfilteredList_success() throws Exception { Person editedPerson = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + EditCommand editCommand = prepareCommand(INDEX_FIRST, descriptor); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -76,8 +76,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() throws Exception @Test public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EditCommand editCommand = prepareCommand(INDEX_FIRST, new EditPersonDescriptor()); + Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -88,11 +88,11 @@ public void execute_noFieldSpecifiedUnfilteredList_success() { @Test public void execute_filteredList_success() throws Exception { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, + EditCommand editCommand = prepareCommand(INDEX_FIRST, new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -105,20 +105,20 @@ public void execute_filteredList_success() throws Exception { @Test public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = prepareCommand(INDEX_SECOND_PERSON, descriptor); + EditCommand editCommand = prepareCommand(INDEX_SECOND, descriptor); assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); } @Test public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, + Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND.getZeroBased()); + EditCommand editCommand = prepareCommand(INDEX_FIRST, new EditPersonDescriptorBuilder(personInList).build()); assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); @@ -139,8 +139,8 @@ public void execute_invalidPersonIndexUnfilteredList_failure() { */ @Test public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + showPersonAtIndex(model, INDEX_FIRST); + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); @@ -156,9 +156,9 @@ public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); Person editedPerson = new PersonBuilder().build(); - Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + EditCommand editCommand = prepareCommand(INDEX_FIRST, descriptor); Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); // edit -> first person edited @@ -204,11 +204,11 @@ public void executeUndoRedo_validIndexFilteredList_samePersonEdited() throws Exc RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); Person editedPerson = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + EditCommand editCommand = prepareCommand(INDEX_FIRST, descriptor); Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - showPersonAtIndex(model, INDEX_SECOND_PERSON); - Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + showPersonAtIndex(model, INDEX_SECOND); + Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); // edit -> edits second person in unfiltered person list / first person in filtered person list editCommand.execute(); undoRedoStack.push(editCommand); @@ -217,18 +217,18 @@ public void executeUndoRedo_validIndexFilteredList_samePersonEdited() throws Exc assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); expectedModel.updatePerson(personToEdit, editedPerson); - assertNotEquals(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()), personToEdit); + assertNotEquals(model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()), personToEdit); // redo -> edits same second person in unfiltered person list assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); } @Test public void equals() throws Exception { - final EditCommand standardCommand = prepareCommand(INDEX_FIRST_PERSON, DESC_AMY); + final EditCommand standardCommand = prepareCommand(INDEX_FIRST, DESC_AMY); // same values -> returns true EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = prepareCommand(INDEX_FIRST_PERSON, copyDescriptor); + EditCommand commandWithSameValues = prepareCommand(INDEX_FIRST, copyDescriptor); assertTrue(standardCommand.equals(commandWithSameValues)); // same object -> returns true @@ -245,10 +245,10 @@ public void equals() throws Exception { assertFalse(standardCommand.equals(new ClearCommand())); // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); + assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND, DESC_AMY))); // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); + assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST, DESC_BOB))); } /** diff --git a/src/test/java/seedu/address/logic/commands/ExportCommandTest.java b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java new file mode 100644 index 000000000000..ae1c15b10edf --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java @@ -0,0 +1,217 @@ +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.tag.Tag; + +//@@author shanmu9898 +public class ExportCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private final Tag testingTag = new Tag("testingTag"); + private final String testingPath = "./test/data/XmlAddressBookStorageTest"; + private final String name = "testingName"; + private final String testingRange = "1,5"; + private final String fileTypeNormal = "xml"; + private final String fileTypeExcel = "excel"; + + + @Test + public void constructor_nullRange_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(null, testingTag, testingPath, name, fileTypeExcel); + } + + @Test + public void constructor_nullPath_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, null, name, fileTypeNormal); + } + + @Test + public void constructor_nullName_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, testingPath, null, fileTypeNormal); + } + + @Test + public void constructor_nullType_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ExportCommand(testingRange, testingTag, testingPath, name, null); + } + + @Test + public void execute_multipleRange_showsMessageError() { + String testingMultiRange = "1,2,3"; + ExportCommand exportCommand = new ExportCommand(testingMultiRange, testingTag, testingPath, + name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + + } + + @Test + public void execute_outOfRange_showsMessageError() { + String testingOutOfRange = "0,10000000"; + ExportCommand exportCommand = new ExportCommand(testingOutOfRange, testingTag, testingPath, + name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_OUT_OF_BOUNDS), model); + + } + + @Test + public void execute_successfulExport_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand(testingRange, testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithAllRange_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("all", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_exportWithSingleRangeAndMismatchTag_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("2", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandFailure(exportCommand, model, String.format(exportCommand.MESSAGE_TAG_CONTACT_MISMATCH)); + } + + @Test + public void execute_successfulExportWithSingleRange_showsNoMessageError() { + Tag friendsTag = new Tag("friends"); + ExportCommand exportCommand = new ExportCommand("2", friendsTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithExcel_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("1,6", testingTag, testingPath, name, fileTypeExcel); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_successfulExportWithAllRangeExcel_showsNoMessageError() { + Tag friendTag = new Tag("friends"); + ExportCommand exportCommand = new ExportCommand("all", friendTag, testingPath, name, fileTypeExcel); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_rangeNotCorrect_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("2,1", testingTag, testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGiven_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand("all", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGivnAndRangeError_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("2,1", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_RANGE_ERROR), model); + } + + @Test + public void execute_whenTagIsSupposedlyNotGivenAndRangeGiven_showsNoMessageError() { + ExportCommand exportCommand = new ExportCommand(testingRange, new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_SUCCESS), model); + } + + + + @Test + public void execute_whenRangeIsSelectiveAndOutOfRange_showsMessageError() { + ExportCommand exportCommand = new ExportCommand("10000000", new Tag("shouldnotbethistag"), + testingPath, name, fileTypeNormal); + exportCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandSuccess(exportCommand, model, String.format(exportCommand.MESSAGE_OUT_OF_BOUNDS), model); + } + + + + @Test + public void equals() { + final ExportCommand comparableCommand = new ExportCommand(testingRange, testingTag, testingPath, + name, fileTypeNormal); + + // same values -> returns true + ExportCommand comparedToCommand = new ExportCommand(testingRange, testingTag, testingPath, + name, fileTypeNormal); + assertTrue(comparableCommand.equals(comparedToCommand)); + + // same object -> returns true + assertTrue(comparableCommand.equals(comparableCommand)); + + // null -> returns false + assertFalse(comparableCommand.equals(null)); + + // different types -> returns false + assertFalse(comparableCommand.equals(new ClearCommand())); + + // different range -> returns false + assertFalse(comparableCommand.equals(new ExportCommand("1,2", testingTag, testingPath, name, + fileTypeNormal))); + } + + + +} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java index dee1f007f751..b2974e33f298 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java @@ -13,8 +13,11 @@ import java.util.Collections; import java.util.List; +import org.junit.Rule; import org.junit.Test; +import junit.framework.TestCase; +import seedu.address.commons.events.ui.ToggleListEvent; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.model.AddressBook; @@ -23,11 +26,15 @@ import seedu.address.model.UserPrefs; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.ui.testutil.EventsCollectorRule; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. */ public class FindCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); @Test @@ -61,14 +68,18 @@ public void equals() { public void execute_zeroKeywords_noPersonFound() { String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); FindCommand command = prepareCommand(" "); - assertCommandSuccess(command, expectedMessage, Collections.emptyList()); + assertCommandSuccessWithNoToggleListEvent(command, expectedMessage, Collections.emptyList()); + model.changeCurrentActiveListType("appointments"); + assertCommandSuccessWithToggleListEvent(command, expectedMessage, Collections.emptyList()); } @Test public void execute_multipleKeywords_multiplePersonsFound() { String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); FindCommand command = prepareCommand("Kurz Elle Kunz"); - assertCommandSuccess(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); + assertCommandSuccessWithNoToggleListEvent(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); + model.changeCurrentActiveListType("appointments"); + assertCommandSuccessWithToggleListEvent(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); } /** @@ -87,12 +98,32 @@ private FindCommand prepareCommand(String userInput) { * - the {@code FilteredList} is equal to {@code expectedList}
* - the {@code AddressBook} in model remains the same after executing the {@code command} */ - private void assertCommandSuccess(FindCommand command, String expectedMessage, List expectedList) { + private void assertCommandSuccessWithNoToggleListEvent( + FindCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + * - a {@code ToggleListEvent} is called + */ + private void assertCommandSuccessWithToggleListEvent( + FindCommand command, String expectedMessage, List expectedList) { AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); CommandResult commandResult = command.execute(); assertEquals(expectedMessage, commandResult.feedbackToUser); assertEquals(expectedList, model.getFilteredPersonList()); assertEquals(expectedAddressBook, model.getAddressBook()); + TestCase.assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleListEvent); + TestCase.assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); } } diff --git a/src/test/java/seedu/address/logic/commands/ImportCommandTest.java b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java new file mode 100644 index 000000000000..332886ee9ddc --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +//@@author shanmu9898 +public class ImportCommandTest { + + + private static final String INVALID_FILE_LOCATION = "./data/samplefile.xml"; + private static final String VALID_FILE_LOCATION = + "src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullString_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ImportCommand(null); + } + + @Test + public void execute_importFailure_throwsException() { + ImportCommand command = prepareCommand(INVALID_FILE_LOCATION); + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + assertCommandFailure(command, model, String.format(command.MESSAGE_INVALID_FILE)); + } + + @Test + public void execute_acceptedSuccess_successfulImport() { + Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + ClearCommand clearCommand = new ClearCommand(); + clearCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + clearCommand.executeUndoableCommand(); + + ImportCommand command = prepareCommand(VALID_FILE_LOCATION); + + assertCommandSuccess(command, model, String.format (command.MESSAGE_SUCCESS, "7", "0"), model); + } + + @Test + public void equals() { + final ImportCommand comparableCommand = new ImportCommand(VALID_FILE_LOCATION); + + // same values -> returns true + ImportCommand comparedToCommand = new ImportCommand(VALID_FILE_LOCATION); + assertTrue(comparableCommand.equals(comparedToCommand)); + + // same object -> returns true + assertTrue(comparableCommand.equals(comparableCommand)); + + // null -> returns false + assertFalse(comparableCommand.equals(null)); + + // different types -> returns false + assertFalse(comparableCommand.equals(new ClearCommand())); + + // different range -> returns false + assertFalse(comparableCommand.equals(new ImportCommand("./data/sampleimportfile.xml"))); + } + + + /** + * A method to prepare the Import command based on the path given + */ + private ImportCommand prepareCommand(String path) { + ImportCommand importCommand = new ImportCommand(path); + importCommand.setData(new ModelManager(getTypicalAddressBook(), new UserPrefs()), new CommandHistory(), + new UndoRedoStack()); + return importCommand; + } + +} diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 4ee519e3668e..f35c5682724c 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -1,23 +1,36 @@ package seedu.address.logic.commands; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.logic.commands.ListCommand.MESSAGE_SUCCESS; +import static seedu.address.logic.commands.ListCommand.TYPE_APPOINTMENT; +import static seedu.address.logic.commands.ListCommand.TYPE_CONTACT; +import static seedu.address.logic.commands.ListCommand.TYPE_SHORTCUT; +import static seedu.address.logic.commands.ListCommand.TYPE_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import seedu.address.commons.events.ui.ToggleListEvent; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; +import seedu.address.ui.testutil.EventsCollectorRule; /** * Contains integration tests (interaction with the Model) and unit tests for ListCommand. */ public class ListCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); private Model model; private Model expectedModel; @@ -27,19 +40,50 @@ public class ListCommandTest { public void setUp() { model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + @Test + public void execute_personListIsNotFiltered_showsSameList() { + listCommand = new ListCommand(TYPE_CONTACT); + listCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + assertCommandSuccess(listCommand, model, MESSAGE_SUCCESS + TYPE_CONTACT, expectedModel); + } - listCommand = new ListCommand(); + @Test + public void execute_personListIsFiltered_showsEverything() { + listCommand = new ListCommand(TYPE_CONTACT); listCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + showPersonAtIndex(model, INDEX_FIRST); + assertCommandSuccess(listCommand, model, MESSAGE_SUCCESS + TYPE_CONTACT, expectedModel); } + //@@author Sisyphus25 @Test - public void execute_listIsNotFiltered_showsSameList() { - assertCommandSuccess(listCommand, model, ListCommand.MESSAGE_SUCCESS, expectedModel); + public void execute_listEvent_success() throws CommandException { + assertListEventSuccess(TYPE_APPOINTMENT); + assertListEventSuccess(TYPE_TASK); } + /** + * assert if execution of listing of event is successful or not + * @throws CommandException + */ + private void assertListEventSuccess(String eventType) throws CommandException { + listCommand = new ListCommand(eventType); + listCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + CommandResult result = listCommand.execute(); + assertEquals(MESSAGE_SUCCESS + eventType, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleListEvent); + } + + //@@author shanmu9898 @Test - public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - assertCommandSuccess(listCommand, model, ListCommand.MESSAGE_SUCCESS, expectedModel); + public void execute_listShortcut_success() throws CommandException { + listCommand = new ListCommand(TYPE_SHORTCUT); + listCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + CommandResult result = listCommand.execute(); + assertEquals(MESSAGE_SUCCESS + TYPE_SHORTCUT, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleListEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); } } diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java index e615f089a4f2..938e163005c6 100644 --- a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java @@ -4,8 +4,8 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.Arrays; @@ -25,8 +25,8 @@ public class RedoCommandTest { private static final UndoRedoStack EMPTY_STACK = new UndoRedoStack(); private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST_PERSON); - private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_SECOND_PERSON); + private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST); + private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_SECOND); @Before public void setUp() throws Exception { diff --git a/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java new file mode 100644 index 000000000000..da41f62a90b1 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/RemoveCommandTest.java @@ -0,0 +1,100 @@ +package seedu.address.logic.commands; + +//@@author Sisyphus25 + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; + +/** + * Contains Test for {@code RemoveCommand} + */ +public class RemoveCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexRemoveAppointment_success() throws Exception { + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Appointment appointmentToDelete = model.getFilteredAppointmentList().get(INDEX_FIRST.getZeroBased()); + RemoveCommand removeCommandRemovingAppointment = prepareCommand(INDEX_FIRST, "appointment"); + String expectedMessage = + String.format(RemoveCommand.MESSAGE_DELETE_EVENT_SUCCESS, "appointment", appointmentToDelete); + expectedModel.deleteAppointment(appointmentToDelete); + assertCommandSuccess(removeCommandRemovingAppointment, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexRemoveTask_success() throws Exception { + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST.getZeroBased()); + RemoveCommand removeCommandRemovingTask = prepareCommand(INDEX_FIRST, "task"); + String expectedMessage = + String.format(RemoveCommand.MESSAGE_DELETE_EVENT_SUCCESS, "task", taskToDelete); + expectedModel.deleteTask(taskToDelete); + assertCommandSuccess(removeCommandRemovingTask, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + RemoveCommand removeCommandRemovingTask = prepareCommand(outOfBoundIndex, "task"); + + Index outOfBoundIndex2 = Index.fromOneBased(model.getFilteredAppointmentList().size() + 1); + RemoveCommand removeCommandRemovingAppointment = prepareCommand(outOfBoundIndex2, "appointment"); + + assertCommandFailure(removeCommandRemovingTask, model, Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + assertCommandFailure(removeCommandRemovingAppointment, + model, Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + @Test + public void equals() throws Exception { + RemoveCommand removeCommandRemovingAppointment = prepareCommand(INDEX_FIRST, "appointment"); + RemoveCommand removeCommandRemovingTask = prepareCommand(INDEX_SECOND, "task"); + + // same object -> returns true + assertTrue(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointment)); + + // same values -> returns true + RemoveCommand removeCommandRemovingAppointmentCopy = prepareCommand(INDEX_FIRST, "appointment"); + assertTrue(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointmentCopy)); + + // one command preprocessed when previously equal -> returns false + removeCommandRemovingAppointmentCopy.preprocessUndoableCommand(); + assertFalse(removeCommandRemovingAppointment.equals(removeCommandRemovingAppointmentCopy)); + + // different types -> returns false + assertFalse(removeCommandRemovingAppointment.equals(1)); + + // null -> returns false + assertFalse(removeCommandRemovingAppointment.equals(null)); + + // different person -> returns false + assertFalse(removeCommandRemovingAppointment.equals(removeCommandRemovingTask)); + } + + /** + * Returns a {@code RemoveCommand} with the parameter {@code index}, {@code eventType}. + */ + private RemoveCommand prepareCommand(Index index, String eventType) { + RemoveCommand removeCommand = new RemoveCommand(index, eventType); + removeCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return removeCommand; + } +} diff --git a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java index 4840900602ac..ee353c8b8750 100644 --- a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java @@ -5,9 +5,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.Before; @@ -43,8 +43,8 @@ public void setUp() { public void execute_validIndexUnfilteredList_success() { Index lastPersonIndex = Index.fromOneBased(model.getFilteredPersonList().size()); - assertExecutionSuccess(INDEX_FIRST_PERSON); - assertExecutionSuccess(INDEX_THIRD_PERSON); + assertExecutionSuccess(INDEX_FIRST); + assertExecutionSuccess(INDEX_THIRD); assertExecutionSuccess(lastPersonIndex); } @@ -57,16 +57,16 @@ public void execute_invalidIndexUnfilteredList_failure() { @Test public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - assertExecutionSuccess(INDEX_FIRST_PERSON); + assertExecutionSuccess(INDEX_FIRST); } @Test public void execute_invalidIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Index outOfBoundsIndex = INDEX_SECOND_PERSON; + Index outOfBoundsIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundsIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); @@ -75,14 +75,14 @@ public void execute_invalidIndexFilteredList_failure() { @Test public void equals() { - SelectCommand selectFirstCommand = new SelectCommand(INDEX_FIRST_PERSON); - SelectCommand selectSecondCommand = new SelectCommand(INDEX_SECOND_PERSON); + SelectCommand selectFirstCommand = new SelectCommand(INDEX_FIRST); + SelectCommand selectSecondCommand = new SelectCommand(INDEX_SECOND); // same object -> returns true assertTrue(selectFirstCommand.equals(selectFirstCommand)); // same values -> returns true - SelectCommand selectFirstCommandCopy = new SelectCommand(INDEX_FIRST_PERSON); + SelectCommand selectFirstCommandCopy = new SelectCommand(INDEX_FIRST); assertTrue(selectFirstCommand.equals(selectFirstCommandCopy)); // different types -> returns false diff --git a/src/test/java/seedu/address/logic/commands/SetAppointmentCommandTest.java b/src/test/java/seedu/address/logic/commands/SetAppointmentCommandTest.java new file mode 100644 index 000000000000..51ce622df92d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/SetAppointmentCommandTest.java @@ -0,0 +1,123 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static seedu.address.testutil.TypicalEvents.APPOINTMENT_WITHOUT_PERSON_1; +import static seedu.address.testutil.TypicalEvents.APPOINTMENT_WITHOUT_PERSON_3; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_2; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_3; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25 +public class SetAppointmentCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + } + + @Test + public void constructor_nullAppointment_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new SetAppointmentCommand(null, null); + } + + @Test + public void execute_invalidPersonToMeetIndex_failure() { + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + SetAppointmentCommand command = getSetAppointmentCommand(TYPICAL_APPOINTMENT_3, outOfBoundsIndex, model); + + try { + command.execute(); + fail("The expected CommandException was not thrown."); + } catch (CommandException ce) { + assertEquals(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, ce.getMessage()); + } + } + + @Test + public void execute_appointmentWithoutPersonToMeetAccepted_addSuccessful() throws Exception { + CommandResult commandResult = + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_3, null, model).execute(); + + assertEquals(String.format( + SetAppointmentCommand.MESSAGE_SUCCESS, APPOINTMENT_WITHOUT_PERSON_3), commandResult.feedbackToUser); + } + + @Test + public void execute_appointmentWithPersonToMeetAccepted_addSuccessful() throws Exception { + CommandResult commandResult = + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_3, INDEX_THIRD, model).execute(); + + assertEquals(String.format( + SetAppointmentCommand.MESSAGE_SUCCESS, TYPICAL_APPOINTMENT_3), commandResult.feedbackToUser); + } + + @Test + public void execute_duplicateAppointmentsameIndex_throwsCommandException() throws Exception { + thrown.expect(CommandException.class); + thrown.expectMessage(SetAppointmentCommand.MESSAGE_DUPLICATE_APPOINTMENT); + + getSetAppointmentCommand(APPOINTMENT_WITHOUT_PERSON_1, INDEX_FIRST, model).execute(); + } + + @Test + public void equals() { + SetAppointmentCommand addAppointment1 = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_1); + SetAppointmentCommand addAppointment2 = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_2); + + // same object -> returns true + assertTrue(addAppointment1.equals(addAppointment1)); + + // same values -> returns true + SetAppointmentCommand addAppointment1Copy = + new SetAppointmentCommand(TYPICAL_APPOINTMENT_1); + assertTrue(addAppointment1.equals(addAppointment1Copy)); + + // different types -> returns false + assertFalse(addAppointment1.equals(1)); + + // null -> returns false + assertFalse(addAppointment1.equals(null)); + + // different appointments -> returns false + assertFalse(addAppointment1.equals(addAppointment2)); + } + + /** + * Generates a new SetAppointmentCommand with the details of the given appointment. + */ + private SetAppointmentCommand getSetAppointmentCommand(Appointment baseAppointment, Index index, Model model) { + SetAppointmentCommand command = new SetAppointmentCommand(baseAppointment, index); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} diff --git a/src/test/java/seedu/address/logic/commands/SetTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/SetTaskCommandTest.java new file mode 100644 index 000000000000..9abcf588ac83 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/SetTaskCommandTest.java @@ -0,0 +1,90 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_2; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Task; +import seedu.address.testutil.modelstub.ModelStub; +import seedu.address.testutil.modelstub.ModelStubAcceptingTaskAdded; +import seedu.address.testutil.modelstub.ModelStubThrowingDuplicateEventException; + +//@@author Sisyphus25 +public class SetTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullAppointment_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new SetTaskCommand(null); + } + + @Test + public void execute_appointmentAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingTaskAdded modelStub = new ModelStubAcceptingTaskAdded(); + + CommandResult commandResult = getSetTaskCommand(TYPICAL_TASK_2, modelStub).execute(); + + assertEquals(String.format(SetTaskCommand.MESSAGE_SUCCESS, TYPICAL_TASK_2), commandResult.feedbackToUser); + assertEquals(Arrays.asList(TYPICAL_TASK_2), modelStub.tasksAdded); + } + + @Test + public void execute_duplicateEvent_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicateEventException(); + + thrown.expect(CommandException.class); + thrown.expectMessage(SetTaskCommand.MESSAGE_DUPLICATE_TASK); + + getSetTaskCommand(TYPICAL_TASK_1, modelStub).execute(); + } + + @Test + public void equals() { + SetTaskCommand addTask1 = + new SetTaskCommand(TYPICAL_TASK_1); + SetTaskCommand addTask2 = + new SetTaskCommand(TYPICAL_TASK_2); + + // same object -> returns true + assertTrue(addTask1.equals(addTask1)); + + // same values -> returns true + SetTaskCommand addAppointment1Copy = + new SetTaskCommand(TYPICAL_TASK_1); + assertTrue(addTask1.equals(addAppointment1Copy)); + + // different types -> returns false + assertFalse(addTask1.equals(1)); + + // null -> returns false + assertFalse(addTask1.equals(null)); + + // different tasks -> returns false + assertFalse(addTask1.equals(addTask2)); + } + + /** + * Generates a new SetTaskCommand with the details of the given task. + */ + private SetTaskCommand getSetTaskCommand(Task task, Model model) { + SetTaskCommand command = new SetTaskCommand(task); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} diff --git a/src/test/java/seedu/address/logic/commands/ShortcutCommandTest.java b/src/test/java/seedu/address/logic/commands/ShortcutCommandTest.java new file mode 100644 index 000000000000..aee25f2e2b56 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ShortcutCommandTest.java @@ -0,0 +1,123 @@ +//@@author shanmu9898 +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class ShortcutCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + private Model expectedModel; + private final String validTestingCommandWord = "list"; + private final String validTestingShortcutWord = "l"; + private final String invalidTestingCommandWord = "king"; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + } + + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutCommand(null, validTestingShortcutWord); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutCommand(validTestingCommandWord, null); + } + + @Test + public void executeUndoCommand_invalidCommandWord_throwsCommandException() throws CommandException { + thrown.expect(CommandException.class); + thrown.expectMessage(ShortcutCommand.MESSAGE_NO_COMMAND_TO_MAP); + + CommandResult commandResult = getAddShortcutForCommand(validTestingShortcutWord, + invalidTestingCommandWord, + model).executeUndoableCommand(); + } + + @Test + public void executeUndoCommand_shortcutWordPresent_throwsCommandException() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException, CommandException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validTestingShortcutWord, validTestingCommandWord); + model.addCommandShortcut(shortcutDoubles); + CommandResult commandResult = getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model).executeUndoableCommand(); + assertEquals(commandResult.feedbackToUser, String.format(ShortcutCommand.MESSAGE_SHORTCUT_AVAILABLE)); + } + + @Test + public void executeUndoCommand_validInput_commandSuccess() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ShortcutDoubles shortcutDoubles = new ShortcutDoubles(validTestingShortcutWord, validTestingCommandWord); + expectedModel.addCommandShortcut(shortcutDoubles); + assertCommandSuccess(getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model), model, + String.format(ShortcutCommand.MESSAGE_SUCCESS), expectedModel); + } + + /** + * Generates a new AddCommand with the details of the given person. + */ + private ShortcutCommand getAddShortcutForCommand(String shortcutWord, String commandWord, Model model) { + ShortcutCommand command = new ShortcutCommand(commandWord, shortcutWord); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + @Test + public void equals() throws Exception { + ShortcutCommand deleteFirstCommand = getAddShortcutForCommand(validTestingShortcutWord, + validTestingCommandWord, + model); + ShortcutCommand deleteSecondCommand = getAddShortcutForCommand("c", + "clear", + model); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + ShortcutCommand deleteFirstCommandCopy = getAddShortcutForCommand("l", + "list", + model); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/ToggleCalendarViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ToggleCalendarViewCommandTest.java new file mode 100644 index 000000000000..ee7e0a76e825 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ToggleCalendarViewCommandTest.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static seedu.address.logic.commands.ToggleCalendarViewCommand.MESSAGE_VIEW_TOGGLE_SUCCESS; + +import org.junit.Rule; +import org.junit.Test; + +import seedu.address.commons.events.ui.ToggleCalendarViewEvent; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.ui.testutil.EventsCollectorRule; + +//@@author Sisyphus25 +public class ToggleCalendarViewCommandTest { + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + @Test + public void execute_help_success() throws CommandException { + Character viewMode = 'd'; + CommandResult result = new ToggleCalendarViewCommand(viewMode).execute(); + assertEquals(MESSAGE_VIEW_TOGGLE_SUCCESS, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ToggleCalendarViewEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java index 3eb5f2f18346..41f8240bd5e8 100644 --- a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java @@ -4,7 +4,7 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.Arrays; @@ -24,8 +24,8 @@ public class UndoCommandTest { private static final UndoRedoStack EMPTY_STACK = new UndoRedoStack(); private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST_PERSON); - private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_FIRST_PERSON); + private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST); + private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_FIRST); @Before public void setUp() { diff --git a/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java index 7d00a7471b86..adf1ace21b1b 100644 --- a/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.fail; import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.Test; @@ -28,7 +28,7 @@ public void executeUndo() throws Exception { deleteFirstPerson(expectedModel); assertEquals(expectedModel, model); - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); // undo() should cause the model's filtered list to show all persons dummyCommand.undo(); @@ -38,7 +38,7 @@ public void executeUndo() throws Exception { @Test public void redo() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); // redo() should cause the model's filtered list to show all persons dummyCommand.redo(); diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index c9a350c09657..30e35bb12972 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -15,9 +15,11 @@ import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_STUDENT; import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_STUDENT; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; @@ -28,6 +30,7 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.parser.AddCommandParser.MESSAGE_INVALID_TYPE; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; @@ -54,6 +57,10 @@ public void parse_allFieldsPresent_success() { assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); + // student preamble with whitespaces + assertParseSuccess(parser, PREAMBLE_STUDENT + PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_STUDENT, new AddCommand(expectedPerson)); + // multiple names - last name accepted assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); @@ -132,15 +139,14 @@ public void parse_invalidValue_failure() { // invalid tag assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_CONSTRAINTS); + + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_NAME_CONSTRAINTS); // two invalid values, only first invalid value reported assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, Name.MESSAGE_NAME_CONSTRAINTS); - // non-empty preamble + // invalid type assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, MESSAGE_INVALID_TYPE); } } diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 7466da232666..77d01008b1e1 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -5,7 +5,27 @@ import static org.junit.Assert.fail; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.logic.commands.CommandTestUtil.END_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.START_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.TITLE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TITLE; +import static seedu.address.logic.commands.ListCommand.COMMAND_WORD; +import static seedu.address.logic.commands.ListCommand.TYPE_APPOINTMENT; +import static seedu.address.logic.commands.ListCommand.TYPE_CONTACT; +import static seedu.address.logic.commands.ListCommand.TYPE_SHORTCUT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG_EXPORT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TYPE; +import static seedu.address.testutil.ExportCommandHelper.NAME_NEEDED; +import static seedu.address.testutil.ExportCommandHelper.PATH_NEEDED; +import static seedu.address.testutil.ExportCommandHelper.RANGE_ALL; +import static seedu.address.testutil.ExportCommandHelper.TAG_NEEDED; +import static seedu.address.testutil.ExportCommandHelper.TYPE_NEEDED; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.List; @@ -15,22 +35,38 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ChangeThemeCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteShortcutCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.ExportCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ImportCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RemoveCommand; import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SetAppointmentCommand; +import seedu.address.logic.commands.SetTaskCommand; +import seedu.address.logic.commands.ShortcutCommand; +import seedu.address.logic.commands.ToggleCalendarViewCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.AppointmentBuilder; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; @@ -57,8 +93,8 @@ public void parseCommand_clear() throws Exception { @Test public void parseCommand_delete() throws Exception { DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); + assertEquals(new DeleteCommand(INDEX_FIRST), command); } @Test @@ -66,8 +102,8 @@ public void parseCommand_edit() throws Exception { Person person = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getPersonDetails(person)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + + INDEX_FIRST.getOneBased() + " " + PersonUtil.getPersonDetails(person)); + assertEquals(new EditCommand(INDEX_FIRST, descriptor), command); } @Test @@ -105,15 +141,16 @@ public void parseCommand_history() throws Exception { @Test public void parseCommand_list() throws Exception { - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); + assertTrue(parser.parseCommand(COMMAND_WORD + " " + TYPE_CONTACT) instanceof ListCommand); + assertTrue(parser.parseCommand(COMMAND_WORD + " " + TYPE_APPOINTMENT) instanceof ListCommand); + assertTrue(parser.parseCommand(COMMAND_WORD + " " + TYPE_SHORTCUT) instanceof ListCommand); } @Test public void parseCommand_select() throws Exception { SelectCommand command = (SelectCommand) parser.parseCommand( - SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new SelectCommand(INDEX_FIRST_PERSON), command); + SelectCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); + assertEquals(new SelectCommand(INDEX_FIRST), command); } @Test @@ -141,4 +178,85 @@ public void parseCommand_unknownCommand_throwsParseException() throws Exception thrown.expectMessage(MESSAGE_UNKNOWN_COMMAND); parser.parseCommand("unknownCommand"); } + + //@@author shanmu9898 + @Test + public void parseCommand_export() throws Exception { + ExportCommand command = (ExportCommand) parser.parseCommand( + ExportCommand.COMMAND_WORD + " " + PREFIX_NAME + NAME_NEEDED + " " + PREFIX_RANGE + RANGE_ALL + + " " + PREFIX_TAG_EXPORT + TAG_NEEDED + " " + PREFIX_PATH + PATH_NEEDED + " " + PREFIX_TYPE + + TYPE_NEEDED); + assertEquals (new ExportCommand ("all", new Tag ("friends"), "./data", + "name", "xml"), command); + } + + @Test + public void parseCommand_import() throws Exception { + ImportCommand command = (ImportCommand) parser.parseCommand( + ImportCommand.COMMAND_WORD + " " + + "src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"); + assertEquals(new ImportCommand("src/test/data/XmlAddressBookStorageTest/importsamplefile.xml"), + command); + } + + @Test + public void parseCommand_shortcut() throws Exception { + ShortcutCommand command = (ShortcutCommand) parser.parseCommand( + ShortcutCommand.COMMAND_WORD + " " + "list" + " " + "l"); + assertEquals(new ShortcutCommand("list", "l"), command); + } + + @Test + public void parseCommand_deleteShortcut() throws Exception { + DeleteShortcutCommand command = (DeleteShortcutCommand) parser.parseCommand( + DeleteShortcutCommand.COMMAND_WORD + " " + "list" + " " + "l"); + assertEquals(new DeleteShortcutCommand("list", "l"), command); + } + //@@author + + + //@@author Sisyphus25 + @Test + public void parseCommand_toggleCalendarView() throws Exception { + ToggleCalendarViewCommand command = + (ToggleCalendarViewCommand) parser.parseCommand(ToggleCalendarViewCommand.COMMAND_WORD + " " + "m"); + assertEquals(new ToggleCalendarViewCommand('m'), command); + } + + @Test + public void parseCommand_setAppointment() throws Exception { + SetAppointmentCommand command = + (SetAppointmentCommand) parser.parseCommand(SetAppointmentCommand.COMMAND_WORD + + TITLE_DESC + START_TIME_DESC + END_TIME_DESC); + Appointment appointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + assertEquals(new SetAppointmentCommand(appointment), command); + } + + @Test + public void parseCommand_setTask() throws Exception { + SetTaskCommand command = + (SetTaskCommand) parser.parseCommand(SetTaskCommand.COMMAND_WORD + TITLE_DESC + END_TIME_DESC); + Task task = new Task(new Title(VALID_TITLE), new EventTime(VALID_END_TIME)); + assertEquals(new SetTaskCommand(task), command); + } + + @Test + public void parseCommand_changeTheme() throws Exception { + ChangeThemeCommand command = + (ChangeThemeCommand) parser.parseCommand(ChangeThemeCommand.COMMAND_WORD + " " + "dark"); + assertEquals(new ChangeThemeCommand("dark"), command); + } + + @Test + public void parseCommand_remove() throws Exception { + RemoveCommand commandRemoveAppointment = + (RemoveCommand) parser.parseCommand(RemoveCommand.COMMAND_WORD + " " + "appointment" + " " + "1"); + RemoveCommand commandRemoveTask = + (RemoveCommand) parser.parseCommand(RemoveCommand.COMMAND_WORD + " " + "task" + " " + "2"); + assertEquals(new RemoveCommand(Index.fromOneBased(1), "appointment"), commandRemoveAppointment); + assertEquals(new RemoveCommand(Index.fromOneBased(2), "task"), commandRemoveTask); + } + //@@author + + } diff --git a/src/test/java/seedu/address/logic/parser/ChangeThemeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ChangeThemeCommandParserTest.java new file mode 100644 index 000000000000..029f3bf5004f --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ChangeThemeCommandParserTest.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.ChangeThemeCommand; + +//@@author Sisyphus25 +/** + * Test scope: similar to ToggleCalendarViewCommandParser Test + */ +public class ChangeThemeCommandParserTest { + private ChangeThemeCommandParser parser = new ChangeThemeCommandParser(); + + @Test + public void parse_validArgs_returnsToggleCalendarViewCommand() { + assertParseSuccess(parser, "dark ", new ChangeThemeCommand("dark")); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "not a theme", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_INVALID_THEME)); + assertParseFailure(parser, "x", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeThemeCommand.MESSAGE_INVALID_THEME)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java index 825732ced6ac..7210fa35531f 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java @@ -3,7 +3,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import org.junit.Test; @@ -22,7 +22,7 @@ public class DeleteCommandParserTest { @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST)); } @Test diff --git a/src/test/java/seedu/address/logic/parser/DeleteShortcutCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteShortcutCommandParserTest.java new file mode 100644 index 000000000000..e46277b36356 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteShortcutCommandParserTest.java @@ -0,0 +1,42 @@ +//@@author shanmu9898 +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.DeleteShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class DeleteShortcutCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DeleteShortcutCommandParser deleteShortcutCommandParser = new DeleteShortcutCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + deleteShortcutCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + deleteShortcutCommandParser.parse(" "); + + } + + @Test + public void parse_validString_success() { + String inputCommandWord = "list"; + String inputShortcutWord = "l"; + String input = "list l"; + DeleteShortcutCommand expectedCommand = new DeleteShortcutCommand(inputCommandWord, inputShortcutWord); + assertParseSuccess(deleteShortcutCommandParser, input, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 24c138b41a7f..1b47cf350904 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -27,9 +27,9 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD; import org.junit.Test; @@ -85,7 +85,7 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_PHONE_CONSTRAINTS); // invalid phone assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_EMAIL_CONSTRAINTS); // invalid email assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_ADDRESS_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_TAG_CONSTRAINTS); // invalid tag + assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_TAG_NAME_CONSTRAINTS); // invalid tag // invalid phone followed by valid email assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_PHONE_CONSTRAINTS); @@ -96,9 +96,12 @@ public void parse_invalidValue_failure() { // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, // parsing it together with a valid tag results in error - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_TAG_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_TAG_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_TAG_CONSTRAINTS); + assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, + Tag.MESSAGE_TAG_NAME_CONSTRAINTS); + assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, + Tag.MESSAGE_TAG_NAME_CONSTRAINTS); + assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, + Tag.MESSAGE_TAG_NAME_CONSTRAINTS); // multiple invalid values, but only the first invalid value is captured assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, @@ -107,7 +110,7 @@ public void parse_invalidValue_failure() { @Test public void parse_allFieldsSpecified_success() { - Index targetIndex = INDEX_SECOND_PERSON; + Index targetIndex = INDEX_SECOND; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; @@ -121,7 +124,7 @@ public void parse_allFieldsSpecified_success() { @Test public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) @@ -134,7 +137,7 @@ public void parse_someFieldsSpecified_success() { @Test public void parse_oneFieldSpecified_success() { // name - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -167,7 +170,7 @@ public void parse_oneFieldSpecified_success() { @Test public void parse_multipleRepeatedFields_acceptsLast() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; @@ -183,7 +186,7 @@ public void parse_multipleRepeatedFields_acceptsLast() { @Test public void parse_invalidValueFollowedByValidValue_success() { // no other valid values specified - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -200,7 +203,7 @@ public void parse_invalidValueFollowedByValidValue_success() { @Test public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + TAG_EMPTY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); diff --git a/src/test/java/seedu/address/logic/parser/ExportCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ExportCommandParserTest.java new file mode 100644 index 000000000000..95d2febd1cd8 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ExportCommandParserTest.java @@ -0,0 +1,57 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.ExportCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +//@@author shanmu9898 +public class ExportCommandParserTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ExportCommandParser exportCommandParser = new ExportCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + exportCommandParser.parse(null); + } + + @Test + public void parse_differentType_invalidFormatCommandResult() throws ParseException { + Tag testingTag = new Tag("shouldnotbethistag"); + String testingInput = " n/name r/all p/./data te/random"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "normal"); + assertParseFailure(exportCommandParser, testingInput, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ExportCommand.MESSAGE_USAGE)); + + + } + + @Test + public void parse_optionalFieldsMissing_success() { + Tag testingTag = new Tag("shouldnotbethistag"); + String testingInput = " n/name r/all p/./data te/xml"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "xml"); + assertParseSuccess(exportCommandParser, testingInput, expectedCommand); + } + + + + @Test + public void parse_allfieldsPresent_success() { + Tag testingTag = new Tag("friends"); + String testingInput = " n/name r/all t/friends p/./data te/xml"; + ExportCommand expectedCommand = new ExportCommand("all", testingTag, "./data", "name", "xml"); + assertParseSuccess(exportCommandParser, testingInput, expectedCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java new file mode 100644 index 000000000000..f3349671f53f --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java @@ -0,0 +1,50 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.ImportCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author shanmu9898 +public class ImportCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ImportCommandParser importCommandParser = new ImportCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + importCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + importCommandParser.parse(" "); + + } + + @Test + public void parse_moreThanOneBlockOfString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + importCommandParser.parse("invalid two strings"); + } + + @Test + public void parse_validString_success() { + String input = "./src/test/data/XmlAddressBookStorgageTest/importsamplefile.xml"; + ImportCommand expectedCommand = new ImportCommand(input); + assertParseSuccess(importCommandParser, input, expectedCommand); + } + + + + +} diff --git a/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java new file mode 100644 index 000000000000..a254bd45e8a4 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.ListCommand; + +//@@author Sisyphus25 +public class ListCommandParserTest { + private ListCommandParser parser = new ListCommandParser(); + + @Test + public void parse_validArgs_returnsListCommand() { + assertParseSuccess(parser, "contacts", new ListCommand(ListCommand.TYPE_CONTACT)); + assertParseSuccess(parser, "students", new ListCommand(ListCommand.TYPE_STUDENT)); + assertParseSuccess(parser, "tasks", new ListCommand(ListCommand.TYPE_TASK)); + assertParseSuccess(parser, "appointments", new ListCommand(ListCommand.TYPE_APPOINTMENT)); + assertParseSuccess(parser, "shortcuts", new ListCommand(ListCommand.TYPE_SHORTCUT)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "ffffffd", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "event", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 54516c1c5e95..157f59c61933 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -5,7 +5,7 @@ import static org.junit.Assert.assertTrue; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.Collections; @@ -18,6 +18,8 @@ import org.junit.rules.ExpectedException; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; @@ -60,10 +62,10 @@ public void parseIndex_outOfRangeInput_throwsIllegalValueException() throws Exce @Test public void parseIndex_validInput_success() throws Exception { // No whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex("1")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex("1")); // Leading and trailing whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 ")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex(" 1 ")); } @Test @@ -78,11 +80,58 @@ public void parseName_invalidValue_throwsIllegalValueException() { Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseName(Optional.of(INVALID_NAME))); } + @Test public void parseName_optionalEmpty_returnsOptionalEmpty() throws Exception { assertFalse(ParserUtil.parseName(Optional.empty()).isPresent()); } + //@@author Sisyphus25 + @Test + public void parseTitle_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTitle((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTitle((Optional) null)); + } + + @Test + public void parseTitle_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTitle(" ")); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTitle(Optional.of(" "))); + } + + @Test + public void parseTitle_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseTitle(Optional.empty()).isPresent()); + } + + @Test + public void parseTitle_validValue_returnsTitle() throws Exception { + String validTitle = "Hanging out"; + Title expectedTitle = new Title(validTitle); + assertEquals(expectedTitle, ParserUtil.parseTitle(validTitle)); + assertEquals(Optional.of(expectedTitle), ParserUtil.parseTitle(Optional.of(validTitle))); + } + + @Test + public void parseEventTime_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEventTime((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEventTime((Optional) null)); + } + + @Test + public void parseEventTime_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseEventTime(Optional.empty()).isPresent()); + } + + @Test + public void parseEventTime_validValue_returnsEventTime() throws Exception { + String validTime = "20/10/2018 10:00"; + EventTime expectedEventTime = new EventTime(validTime); + assertEquals(expectedEventTime, ParserUtil.parseEventTime(validTime)); + assertEquals(Optional.of(expectedEventTime), ParserUtil.parseEventTime(Optional.of(validTime))); + } + + //@@author @Test public void parseName_validValueWithoutWhitespace_returnsName() throws Exception { Name expectedName = new Name(VALID_NAME); diff --git a/src/test/java/seedu/address/logic/parser/RemoveCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemoveCommandParserTest.java new file mode 100644 index 000000000000..62b75f733bbe --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/RemoveCommandParserTest.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; + +import org.junit.Test; + +import seedu.address.logic.commands.RemoveCommand; + +//@@author Sisyphus25 +public class RemoveCommandParserTest { + private RemoveCommandParser parser = new RemoveCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "appointment" + " 1", new RemoveCommand(INDEX_FIRST, "appointment")); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "not valid", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "Appointment" + " 1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "appointment" + " -1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/SelectCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SelectCommandParserTest.java index 513ee46acd33..c7b23a8089c8 100644 --- a/src/test/java/seedu/address/logic/parser/SelectCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/SelectCommandParserTest.java @@ -3,7 +3,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import org.junit.Test; @@ -19,7 +19,7 @@ public class SelectCommandParserTest { @Test public void parse_validArgs_returnsSelectCommand() { - assertParseSuccess(parser, "1", new SelectCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new SelectCommand(INDEX_FIRST)); } @Test diff --git a/src/test/java/seedu/address/logic/parser/SetAppointmentCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SetAppointmentCommandParserTest.java new file mode 100644 index 000000000000..d7806b4ab241 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/SetAppointmentCommandParserTest.java @@ -0,0 +1,81 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.END_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_END_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_START_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TITLE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.START_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.TITLE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TITLE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.SetAppointmentCommand; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; +import seedu.address.testutil.AppointmentBuilder; + +//@@author Sisyphus25 +public class SetAppointmentCommandParserTest { + private SetAppointmentCommandParser parser = new SetAppointmentCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Appointment expectedAppointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + new SetAppointmentCommand(expectedAppointment)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // no personToMeet + Appointment expectedAppointment = new AppointmentBuilder(VALID_TITLE, VALID_START_TIME, VALID_END_TIME).build(); + assertParseSuccess(parser, TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + new SetAppointmentCommand((expectedAppointment))); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetAppointmentCommand.MESSAGE_USAGE); + + // missing title prefix + assertParseFailure(parser, VALID_TITLE + START_TIME_DESC + END_TIME_DESC, + expectedMessage); + + // missing start time prefix + assertParseFailure(parser, TITLE_DESC + VALID_START_TIME + END_TIME_DESC, + expectedMessage); + + // missing start time prefix + assertParseFailure(parser, TITLE_DESC + START_TIME_DESC + VALID_END_TIME, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_TITLE + VALID_START_TIME + VALID_END_TIME, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid title + assertParseFailure(parser, INVALID_TITLE_DESC + START_TIME_DESC + END_TIME_DESC, + Title.MESSAGE_TITLE_CONSTRAINTS); + + // invalid start time + assertParseFailure(parser, TITLE_DESC + INVALID_START_TIME_DESC + END_TIME_DESC, + EventTime.MESSAGE_TIME_CONSTRAINTS); + + // invalid end time + assertParseFailure(parser, TITLE_DESC + START_TIME_DESC + INVALID_END_TIME_DESC, + EventTime.MESSAGE_TIME_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/SetTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SetTaskCommandParserTest.java new file mode 100644 index 000000000000..50e69d63fde1 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/SetTaskCommandParserTest.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.END_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_END_TIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TITLE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.TITLE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TITLE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.SetTaskCommand; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +public class SetTaskCommandParserTest { + private SetTaskCommandParser parser = new SetTaskCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Task expectedTask = new Task(new Title(VALID_TITLE), new EventTime(VALID_END_TIME)); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + TITLE_DESC + END_TIME_DESC, + new SetTaskCommand(expectedTask)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetTaskCommand.MESSAGE_USAGE); + + // missing title prefix + assertParseFailure(parser, VALID_TITLE + END_TIME_DESC, + expectedMessage); + + // missing end time prefix + assertParseFailure(parser, TITLE_DESC + VALID_END_TIME, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_TITLE + VALID_END_TIME, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid title + assertParseFailure(parser, INVALID_TITLE_DESC + END_TIME_DESC, Title.MESSAGE_TITLE_CONSTRAINTS); + + // invalid end time + assertParseFailure(parser, TITLE_DESC + INVALID_END_TIME_DESC, EventTime.MESSAGE_TIME_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ShortcutCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ShortcutCommandParserTest.java new file mode 100644 index 000000000000..f974a43563f2 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ShortcutCommandParserTest.java @@ -0,0 +1,42 @@ +//@@author shanmu9898 +package seedu.address.logic.parser; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.commands.ShortcutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class ShortcutCommandParserTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ShortcutCommandParser shortcutCommandParser = new ShortcutCommandParser(); + + @Test + public void parse_nullString_throwsNullPointerException() throws ParseException { + thrown.expect(NullPointerException.class); + shortcutCommandParser.parse(null); + + } + + @Test + public void parse_emptyString_throwsParseException() throws ParseException { + thrown.expect(ParseException.class); + shortcutCommandParser.parse(" "); + + } + + @Test + public void parse_validString_success() { + String inputCommandWord = "list"; + String inputShortcutWord = "l"; + String input = "list l"; + ShortcutCommand expectedCommand = new ShortcutCommand(inputCommandWord, inputShortcutWord); + assertParseSuccess(shortcutCommandParser, input, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ToggleCalendarViewParserTest.java b/src/test/java/seedu/address/logic/parser/ToggleCalendarViewParserTest.java new file mode 100644 index 000000000000..0b6019e3f15a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ToggleCalendarViewParserTest.java @@ -0,0 +1,27 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.ToggleCalendarViewCommand; + +//@@author Sisyphus25 +public class ToggleCalendarViewParserTest { + private ToggleCalendarViewParser parser = new ToggleCalendarViewParser(); + + @Test + public void parse_validArgs_returnsToggleCalendarViewCommand() { + assertParseSuccess(parser, "d", new ToggleCalendarViewCommand('d')); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "day", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ToggleCalendarViewCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "x", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ToggleCalendarViewCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index bf26f68896b8..42ccdd7eb634 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -1,7 +1,18 @@ package seedu.address.model; import static org.junit.Assert.assertEquals; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_NOTUSED; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_2; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_2; import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.TypicalPersons.STUDENT_AMY; +import static seedu.address.testutil.TypicalPersons.STUDENT_HOON; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import java.util.ArrayList; @@ -16,11 +27,18 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.ShortcutDoubles; import seedu.address.model.tag.Tag; +import seedu.address.testutil.AddressBookBuilder; +import seedu.address.testutil.PersonBuilder; public class AddressBookTest { - @Rule public ExpectedException thrown = ExpectedException.none(); @@ -29,7 +47,13 @@ public class AddressBookTest { @Test public void constructor() { assertEquals(Collections.emptyList(), addressBook.getPersonList()); + assertEquals(Collections.emptyList(), addressBook.getStudentList()); + assertEquals(Collections.emptyList(), addressBook.getContactList()); assertEquals(Collections.emptyList(), addressBook.getTagList()); + assertEquals(Collections.emptyList(), addressBook.getAppointmentList()); + assertEquals(Collections.emptyList(), addressBook.getTaskList()); + assertEquals(Collections.emptyList(), addressBook.getCommandsList()); + } @Test @@ -49,8 +73,13 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() { public void resetData_withDuplicatePersons_throwsAssertionError() { // Repeat ALICE twice List newPersons = Arrays.asList(ALICE, ALICE); + List newStudents = Arrays.asList(STUDENT_AMY, STUDENT_HOON); List newTags = new ArrayList<>(ALICE.getTags()); - AddressBookStub newData = new AddressBookStub(newPersons, newTags); + List newAppointments = Arrays.asList(TYPICAL_APPOINTMENT_1, TYPICAL_APPOINTMENT_2); + List newTasks = Arrays.asList(TYPICAL_TASK_1, TYPICAL_TASK_2); + List newCommands = Arrays.asList(new ShortcutDoubles("a", "add")); + AddressBookStub newData = new AddressBookStub(newPersons, newStudents, newTags, + newAppointments, newTasks, newCommands); thrown.expect(AssertionError.class); addressBook.resetData(newData); @@ -68,16 +97,49 @@ public void getTagList_modifyList_throwsUnsupportedOperationException() { addressBook.getTagList().remove(0); } + @Test + public void getAppointmentList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getAppointmentList().remove(0); + } + + @Test + public void getTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getTaskList().remove(0); + } + + //@@author shanmu9898 + @Test + public void getShortcutList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getCommandsList().remove(0); + } + //@@author shanmu9898 + /** - * A stub ReadOnlyAddressBook whose persons and tags lists can violate interface constraints. + * A stub ReadOnlyAddressBook whose persons, tags and events lists can violate interface constraints. */ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); + private final ObservableList students = FXCollections.observableArrayList(); + private final ObservableList contacts = FXCollections.observableArrayList(); private final ObservableList tags = FXCollections.observableArrayList(); + private final ObservableList appointments = FXCollections.observableArrayList(); + private final ObservableList tasks = FXCollections.observableArrayList(); + private final ObservableList commandslist = FXCollections.observableArrayList(); - AddressBookStub(Collection persons, Collection tags) { + AddressBookStub(Collection persons, Collection students, + Collection tags, Collection appointments, + Collection tasks, Collection commands) { this.persons.setAll(persons); + this.students.setAll(students); + this.contacts.setAll(persons); + this.contacts.addAll(students); this.tags.setAll(tags); + this.tasks.setAll(tasks); + this.appointments.setAll(appointments); + this.commandslist.setAll(commands); } @Override @@ -85,10 +147,74 @@ public ObservableList getPersonList() { return persons; } + @Override + public ObservableList getStudentList() { + return students; + } + + @Override + public ObservableList getContactList() { + return contacts; + } + @Override public ObservableList getTagList() { return tags; } + + @Override + public ObservableList getAppointmentList() { + return appointments; + } + + @Override + public ObservableList getTaskList() { + return tasks; + } + + @Override + public ObservableList getCommandsList() { + return commandslist; + } + } + + @Test + public void updatePerson_modifiedAddressBooks_noError() throws PersonNotFoundException, DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).build(); + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(AMY).build(); + + testAddressBook.updatePerson(BOB, AMY); + + assertEquals(testAddressBook, expectedAddressBook); + } + + //@@author shanmu9898 + @Test + public void removeTag_tagNotPresent_addressBookUnchanged() throws PersonNotFoundException, + DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + + testAddressBook.removeTag(new Tag(VALID_TAG_NOTUSED)); + + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + + assertEquals(expectedAddressBook, testAddressBook); + } + + @Test + public void removeTag_tagUsedByMultiplePeople_tagRemoved() throws PersonNotFoundException, + DuplicatePersonException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(BOB).withPerson(AMY).build(); + testAddressBook.removeTag(new Tag(VALID_TAG_FRIEND)); + + Person amyWithoutFriendTag = new PersonBuilder(AMY).withTags().build(); + Person bobWithoutFriendTag = new PersonBuilder(BOB).withTags(VALID_TAG_HUSBAND).build(); + + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(bobWithoutFriendTag) + .withPerson(amyWithoutFriendTag).build(); + + assertEquals(expectedAddressBook, testAddressBook); } + //@@author } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 59ce1b83693a..8140f7367833 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -1,10 +1,22 @@ package seedu.address.model; +import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_NOTUSED; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_3; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_3; import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.TypicalPersons.IDA; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_1; import java.util.Arrays; @@ -12,28 +24,120 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import junit.framework.TestCase; +import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.AppointmentListChangedEvent; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; +import seedu.address.model.tag.Tag; import seedu.address.testutil.AddressBookBuilder; +import seedu.address.testutil.PersonBuilder; +import seedu.address.ui.testutil.EventsCollectorRule; public class ModelManagerTest { @Rule public ExpectedException thrown = ExpectedException.none(); + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + private AddressBook addressBook = getTypicalAddressBook(); + private UserPrefs userPrefs = new UserPrefs(); + private ModelManager modelManager = new ModelManager(addressBook, userPrefs); @Test public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - ModelManager modelManager = new ModelManager(); thrown.expect(UnsupportedOperationException.class); modelManager.getFilteredPersonList().remove(0); } + //@@author shanmu9898 + @Test + public void getFilteredCommandList_modifyList_throwsUnsupportedOperationException() { + ModelManager modelManager = new ModelManager(); + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredCommandsList().remove(0); + } + //@@author + + @Test + public void getFilteredAppointmentList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredAppointmentList().remove(0); + } + + @Test + public void getFilteredTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredTaskList().remove(0); + } + + @Test + public void addPerson_addPersonToAddressBook_evokeAddressBookChangedEvent() throws DuplicatePersonException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.addPerson(IDA); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } + + @Test + public void removePerson_removePersonFromAddressBook_evokeAddressBookChangedEvent() throws PersonNotFoundException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.deletePerson(ALICE); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } + + @Test + public void addTask_addTaskToAddressBook_evokeAddressBookChangedEvent() + throws DuplicateEventException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.addTask(TYPICAL_TASK_3); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } + + @Test + public void removeTask_removeTaskFromAddressBook_evokeAddressBookChangedEvent() + throws EventNotFoundException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.deleteTask(TYPICAL_TASK_1); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } + + @Test + public void addTask_addAppointmentToAddressBook_evokeAppointmentListChangedEvent() + throws DuplicateEventException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.addAppointment(TYPICAL_APPOINTMENT_3); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AppointmentListChangedEvent); + TestCase.assertTrue(eventsCollectorRule.eventsCollector.getSize() == 2); + } + + @Test + public void removeTask_removeAppointmentFromAddressBook_evokeAppointmentListChangedEvent() + throws EventNotFoundException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.deleteAppointment(TYPICAL_APPOINTMENT_1); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AppointmentListChangedEvent); + TestCase.assertTrue(eventsCollectorRule.eventsCollector.getSize() == 2); + } + + //@@author shanmu9898 + @Test + public void addShortcut_addShortcutToAddressBook_evokeAddressBookChangedEvent() + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + ModelManager model = new ModelManager(addressBook, userPrefs); + modelManager.addCommandShortcut(SHORTCUT_DOUBLES_1); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof AddressBookChangedEvent); + } + //@@author shanmu9898 + @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); AddressBook differentAddressBook = new AddressBook(); - UserPrefs userPrefs = new UserPrefs(); // same values -> returns true - ModelManager modelManager = new ModelManager(addressBook, userPrefs); ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); assertTrue(modelManager.equals(modelManagerCopy)); @@ -62,4 +166,31 @@ public void equals() { differentUserPrefs.setAddressBookName("differentName"); assertTrue(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); } + //@@author shanmu9898 + @Test + public void deleteTag_tagNotPresent_modelUnchanged() throws DuplicatePersonException, PersonNotFoundException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(AMY).withPerson(BOB).build(); + UserPrefs userPrefs = new UserPrefs(); + ModelManager modelManager = new ModelManager(testAddressBook, userPrefs); + modelManager.deleteTag(new Tag(VALID_TAG_NOTUSED)); + + assertEquals(new ModelManager(testAddressBook, userPrefs), modelManager); + } + + + @Test + public void deleteTag_tagUsedByMultiplePeople_tagRemoved() throws DuplicatePersonException, + PersonNotFoundException { + AddressBook testAddressBook = new AddressBookBuilder().withPerson(AMY).withPerson(BOB).build(); + ModelManager modelManager = new ModelManager(testAddressBook, userPrefs); + modelManager.deleteTag(new Tag(VALID_TAG_FRIEND)); + + Person amyWithoutFriendTag = new PersonBuilder(AMY).withTags().build(); + Person bobWithoutFriendTag = new PersonBuilder(BOB).withTags(VALID_TAG_HUSBAND).build(); + AddressBook expectedAddressBook = new AddressBookBuilder().withPerson(amyWithoutFriendTag) + .withPerson(bobWithoutFriendTag).build(); + + assertEquals(new ModelManager(expectedAddressBook, userPrefs), modelManager); + } + //@@author } diff --git a/src/test/java/seedu/address/model/UniqueEventListTest.java b/src/test/java/seedu/address/model/UniqueEventListTest.java new file mode 100644 index 000000000000..8e9d56e3c3c0 --- /dev/null +++ b/src/test/java/seedu/address/model/UniqueEventListTest.java @@ -0,0 +1,20 @@ +package seedu.address.model; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.event.Appointment; +import seedu.address.model.event.UniqueEventList; + +public class UniqueEventListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueEventList uniqueAppointmentList = new UniqueEventList(); + thrown.expect(UnsupportedOperationException.class); + uniqueAppointmentList.asObservableList().remove(0); + } +} diff --git a/src/test/java/seedu/address/model/UniquePersonListTest.java b/src/test/java/seedu/address/model/UniquePersonListTest.java index ae39646daab2..b7662f000536 100644 --- a/src/test/java/seedu/address/model/UniquePersonListTest.java +++ b/src/test/java/seedu/address/model/UniquePersonListTest.java @@ -16,4 +16,5 @@ public void asObservableList_modifyList_throwsUnsupportedOperationException() { thrown.expect(UnsupportedOperationException.class); uniquePersonList.asObservableList().remove(0); } + } diff --git a/src/test/java/seedu/address/model/UniqueShortcutDoublesListTest.java b/src/test/java/seedu/address/model/UniqueShortcutDoublesListTest.java new file mode 100644 index 000000000000..7fab2c8e43e8 --- /dev/null +++ b/src/test/java/seedu/address/model/UniqueShortcutDoublesListTest.java @@ -0,0 +1,20 @@ +//@@author shanmu9898 +package seedu.address.model; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; + +public class UniqueShortcutDoublesListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueShortcutDoublesList uniqueShortcutDoublesList = new UniqueShortcutDoublesList(); + thrown.expect(UnsupportedOperationException.class); + uniqueShortcutDoublesList.asObservableList().remove(0); + } +} diff --git a/src/test/java/seedu/address/model/event/AppointmentTest.java b/src/test/java/seedu/address/model/event/AppointmentTest.java new file mode 100644 index 000000000000..738496a004a1 --- /dev/null +++ b/src/test/java/seedu/address/model/event/AppointmentTest.java @@ -0,0 +1,31 @@ +package seedu.address.model.event; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author Sisyphus25 +public class AppointmentTest { + private static final Title VALID_TITLE = new Title("Meet Student"); + private static final EventTime VALID_START_TIME = new EventTime("05/04/2018 10:00"); + private static final EventTime VALID_END_TIME = new EventTime("05/04/2018 11:00"); + private static final EventTime INVALID_END_TIME = new EventTime("05/04/2018 09:00"); + + @Test + public void constructor_invalidAppointmentTime_throwsIllegalArgumentException() { + Assert.assertThrows(IllegalArgumentException.class, () -> + new Appointment(VALID_TITLE, VALID_START_TIME, INVALID_END_TIME)); + } + + @Test + public void isValidTime() { + // invalid time stamps + assertFalse(Appointment.isValidTime(VALID_START_TIME, INVALID_END_TIME)); //End time is before Start Time + + // valid time stamps + assertTrue(Appointment.isValidTime(VALID_START_TIME, VALID_END_TIME)); + } +} diff --git a/src/test/java/seedu/address/model/event/EventTimeTest.java b/src/test/java/seedu/address/model/event/EventTimeTest.java new file mode 100644 index 000000000000..85ace749be50 --- /dev/null +++ b/src/test/java/seedu/address/model/event/EventTimeTest.java @@ -0,0 +1,51 @@ +package seedu.address.model.event; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author Sisyphus25 +public class EventTimeTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new EventTime(null)); + } + + @Test + public void constructor_invalid_throwsIllegalArgumentException() { + //incorrect format + //not a time stamp + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("invalidTimeStamp")); + //blank + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("")); + //invalid time stamp format + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("10/20 10:00")); + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("May 17 2018 10:00")); + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("17-05-2019 10:00")); + + //correct format but invalid time stamp + //invalid date + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("32/05/2019 10:00")); + //invalid month + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("32/13/2019 10:00")); + //invalid date month + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("29/02/2018 10:00")); + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("31/04/2018 10:00")); + //invalid time + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("29/02/2018 25:00")); + Assert.assertThrows(IllegalArgumentException.class, () -> new EventTime("29/02/2018 23:60")); + } + + @Test + public void isExpired() { + EventTime pastTime = new EventTime("20/10/2013 10:00"); + EventTime futureTime = new EventTime("20/10/2100 10:00"); + assertFalse(futureTime.isExpired()); + + assertTrue(pastTime.isExpired()); + } +} + diff --git a/src/test/java/seedu/address/model/event/TitleTest.java b/src/test/java/seedu/address/model/event/TitleTest.java new file mode 100644 index 000000000000..a499d595d60a --- /dev/null +++ b/src/test/java/seedu/address/model/event/TitleTest.java @@ -0,0 +1,37 @@ +package seedu.address.model.event; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author Sisyphus25 +public class TitleTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Title(null)); + } + + @Test + public void constructor_invalidTitle_throwsIllegalArgumentException() { + String invalidTitle = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> + new Title(invalidTitle)); + } + + @Test + public void isValidTitle() { + // null title + Assert.assertThrows(NullPointerException.class, () -> Title.isValidTitle(null)); + + // invalid title + assertFalse(Title.isValidTitle("")); // empty string + assertFalse(Title.isValidTitle(" ")); // spaces only + + // valid title + assertTrue(Title.isValidTitle("Meet Dave")); + assertTrue(Title.isValidTitle("-")); // one character + } +} diff --git a/src/test/java/seedu/address/model/shortcut/ShortcutDoubleTest.java b/src/test/java/seedu/address/model/shortcut/ShortcutDoubleTest.java new file mode 100644 index 000000000000..e3735d16c36e --- /dev/null +++ b/src/test/java/seedu/address/model/shortcut/ShortcutDoubleTest.java @@ -0,0 +1,51 @@ +//@@author shanmu9898 +package seedu.address.model.shortcut; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +public class ShortcutDoubleTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullCommandWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutDoubles("l", null); + } + + @Test + public void constructor_nullShortcutWord_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new ShortcutDoubles(null , "list"); + } + + @Test + public void equals() throws Exception { + ShortcutDoubles deleteFirstCommand = new ShortcutDoubles("l", "list"); + ShortcutDoubles deleteSecondCommand = new ShortcutDoubles("c", "clear"); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + ShortcutDoubles deleteFirstCommandCopy = new ShortcutDoubles("l", "list"); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } +} diff --git a/src/test/java/seedu/address/storage/XmlAdaptedAppointmentTest.java b/src/test/java/seedu/address/storage/XmlAdaptedAppointmentTest.java new file mode 100644 index 000000000000..d6bccb0b0cf7 --- /dev/null +++ b/src/test/java/seedu/address/storage/XmlAdaptedAppointmentTest.java @@ -0,0 +1,108 @@ +package seedu.address.storage; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_START_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TITLE; +import static seedu.address.storage.XmlAdaptedAppointment.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_2; + +import org.junit.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; +import seedu.address.testutil.Assert; + +//@@author Sisyphus25 +public class XmlAdaptedAppointmentTest { + + private static final String INVALID_TITLE = " "; + private static final String VALID_PERSON_TO_MEET = "Alice Email: alice@gmail.com"; + private static final String INVALID_TIME = "not a time stamp"; + + @Test + public void toModelType_validAppointmentDetails_returnsPerson() throws Exception { + XmlAdaptedAppointment appointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + assertEquals(TYPICAL_APPOINTMENT_1, appointment.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(INVALID_TITLE, VALID_START_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = Title.MESSAGE_TITLE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidStartTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, INVALID_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidStartEndTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, VALID_START_TIME, INVALID_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(null, VALID_START_TIME, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullStartTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, null, VALID_END_TIME, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Start Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_nullEndTime_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, VALID_START_TIME, null, VALID_PERSON_TO_MEET); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "End Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void toModelType_invalidTimePeriod_throwsIllegalValueException() { + XmlAdaptedAppointment appointment = + new XmlAdaptedAppointment(VALID_TITLE, "20/10/2018 10:00", "20/10/2018 09:00"); + String expectedMessage = Appointment.MESSAGE_TIME_PERIOD_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, appointment::toModelType); + } + + @Test + public void equals() { + XmlAdaptedAppointment appointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + + //same object + assertTrue(appointment.equals(appointment)); + + //same value + XmlAdaptedAppointment appointmentCopy = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_1); + assertTrue(appointment.equals(appointmentCopy)); + + //different type + assertFalse(appointment.equals(TYPICAL_APPOINTMENT_1)); + + //different obj + XmlAdaptedAppointment anotherAppointment = new XmlAdaptedAppointment(TYPICAL_APPOINTMENT_2); + assertFalse(appointment.equals(anotherAppointment)); + } +} diff --git a/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java b/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java index c3c91a5c27a7..85137113c9be 100644 --- a/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java @@ -1,7 +1,10 @@ package seedu.address.storage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static seedu.address.storage.XmlAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; import java.util.ArrayList; @@ -22,7 +25,8 @@ public class XmlAdaptedPersonTest { private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_TAG_NAME = "#friend"; + private static final String INVALID_TAG_COLOR_STYLE = "notacolor"; private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); @@ -101,10 +105,28 @@ public void toModelType_nullAddress_throwsIllegalValueException() { @Test public void toModelType_invalidTags_throwsIllegalValueException() { List invalidTags = new ArrayList<>(VALID_TAGS); - invalidTags.add(new XmlAdaptedTag(INVALID_TAG)); + invalidTags.add(new XmlAdaptedTag(INVALID_TAG_NAME, INVALID_TAG_COLOR_STYLE)); XmlAdaptedPerson person = new XmlAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); Assert.assertThrows(IllegalValueException.class, person::toModelType); } + @Test + public void equals() { + XmlAdaptedPerson person = new XmlAdaptedPerson(BENSON); + + //same object + assertTrue(person.equals(person)); + + //same value + XmlAdaptedPerson personCopy = new XmlAdaptedPerson(BENSON); + assertTrue(person.equals(personCopy)); + + //different type + assertFalse(person.equals(1)); + + //different obj + XmlAdaptedPerson anotherPerson = new XmlAdaptedPerson(ALICE); + assertFalse(person.equals(anotherPerson)); + } } diff --git a/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java b/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java new file mode 100644 index 000000000000..2ce4bb94c9b5 --- /dev/null +++ b/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java @@ -0,0 +1,81 @@ +package seedu.address.storage; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_END_TIME; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TITLE; +import static seedu.address.storage.XmlAdaptedTask.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_2; + +import org.junit.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Title; +import seedu.address.testutil.Assert; + +//@@author Sisyphus25 +public class XmlAdaptedTaskTest { + + private static final String INVALID_TITLE = " "; + private static final String INVALID_TIME = "not a time stamp"; + + @Test + public void toModelType_validTaskDetails_returnsPerson() throws Exception { + XmlAdaptedTask task = new XmlAdaptedTask(TYPICAL_TASK_1); + assertEquals(TYPICAL_TASK_1, task.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(INVALID_TITLE, VALID_END_TIME); + String expectedMessage = Title.MESSAGE_TITLE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_invalidTime_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_TITLE, INVALID_TIME); + String expectedMessage = EventTime.MESSAGE_TIME_CONSTRAINTS; + Assert.assertThrows(IllegalArgumentException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(null, VALID_END_TIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullTime_throwsIllegalValueException() { + XmlAdaptedTask task = + new XmlAdaptedTask(VALID_TITLE, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Time"); + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void equals() { + XmlAdaptedTask task = new XmlAdaptedTask(TYPICAL_TASK_1); + + //same object + assertTrue(task.equals(task)); + + //same value + XmlAdaptedTask taskCopy = new XmlAdaptedTask(TYPICAL_TASK_1); + assertTrue(task.equals(taskCopy)); + + //different type + assertFalse(task.equals(TYPICAL_TASK_1)); + + //different obj + XmlAdaptedTask anotherTask = new XmlAdaptedTask(TYPICAL_TASK_2); + assertFalse(task.equals(anotherTask)); + } +} diff --git a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java b/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java index 1bf3765cfba9..bea4b40fdad9 100644 --- a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java @@ -2,10 +2,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_3; import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.DING; import static seedu.address.testutil.TypicalPersons.HOON; import static seedu.address.testutil.TypicalPersons.IDA; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_3; import java.io.IOException; @@ -92,10 +95,17 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception { //Save and read without specifying file path original.addPerson(IDA); + original.addTask(TYPICAL_TASK_3); xmlAddressBookStorage.saveAddressBook(original); //file path not specified readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified assertEquals(original, new AddressBook(readBack)); + //Save and read without specifying file path + original.addPerson(DING); + original.addShortcutDoubles(SHORTCUT_DOUBLES_3); + xmlAddressBookStorage.saveAddressBook(original); //file path not specified + readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified + assertEquals(original, new AddressBook(readBack)); } @Test diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index 6e73a762b0c1..b31ae50e43ae 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -2,6 +2,8 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.exceptions.DuplicateEventException; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; @@ -47,6 +49,18 @@ public AddressBookBuilder withTag(String tagName) { return this; } + /** + * Add a new {@code Appointment} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withAppointment(Appointment appointment) { + try { + addressBook.addAppointment(appointment); + } catch (DuplicateEventException ive) { + throw new IllegalArgumentException("appointment is expected to be unique"); + } + return this; + } + public AddressBook build() { return addressBook; } diff --git a/src/test/java/seedu/address/testutil/AppointmentBuilder.java b/src/test/java/seedu/address/testutil/AppointmentBuilder.java new file mode 100644 index 000000000000..3f8eddc2510e --- /dev/null +++ b/src/test/java/seedu/address/testutil/AppointmentBuilder.java @@ -0,0 +1,46 @@ +package seedu.address.testutil; + +import static seedu.address.model.event.PersonToMeet.EMAIL_SPLITTER; + +import seedu.address.model.event.Appointment; + +import seedu.address.model.event.EventTime; +import seedu.address.model.event.PersonToMeet; +import seedu.address.model.event.Title; +import seedu.address.model.person.Person; + +//@@author Sisyphus25 +/** + * A utility class to help with building Appointment objects. + */ +public class AppointmentBuilder { + private Title title; + private EventTime time; + private EventTime endTime; + private PersonToMeet personToMeet; + + public AppointmentBuilder(String title, String time, String endTime) { + this(title, time, endTime, (String) null); + } + + public AppointmentBuilder(String title, String time, String endTime, Person personToMeet) { + this(title, time, endTime, personToMeet.getName() + EMAIL_SPLITTER + personToMeet.getEmail()); + } + + public AppointmentBuilder(String title, String time, String endTime, String personToMeet) { + this.title = new Title(title); + this.time = new EventTime(time); + this.endTime = new EventTime(endTime); + if (personToMeet != null) { + String[] components = personToMeet.split(EMAIL_SPLITTER); + this.personToMeet = new PersonToMeet(components[0], components[1]); + } + } + + /** + * @return an {@code Appointment} from the data feed to constructor + */ + public Appointment build() { + return new Appointment(title, time, endTime, personToMeet); + } +} diff --git a/src/test/java/seedu/address/testutil/ExportCommandHelper.java b/src/test/java/seedu/address/testutil/ExportCommandHelper.java new file mode 100644 index 000000000000..307b0a6082e3 --- /dev/null +++ b/src/test/java/seedu/address/testutil/ExportCommandHelper.java @@ -0,0 +1,15 @@ +package seedu.address.testutil; + +//@@author shanmu9898 +/** + * A utility class containing a list of {@code Index} objects to be used in tests. + */ +public class ExportCommandHelper { + public static final String RANGE_ALL = "all"; + public static final String TAG_NEEDED = "friends"; + public static final String PATH_NEEDED = "./data"; + public static final String NAME_NEEDED = "name"; + public static final String TYPE_NEEDED = "xml"; + + +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index b124fc1d73b1..267dd3ff6741 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -16,11 +16,11 @@ */ public class PersonBuilder { - public static final String DEFAULT_NAME = "Alice Pauline"; - public static final String DEFAULT_PHONE = "85355255"; - public static final String DEFAULT_EMAIL = "alice@gmail.com"; - public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; - public static final String DEFAULT_TAGS = "friends"; + private static final String DEFAULT_NAME = "Alice Pauline"; + private static final String DEFAULT_PHONE = "85355255"; + private static final String DEFAULT_EMAIL = "alice@gmail.com"; + private static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + private static final String DEFAULT_TAGS = "friends"; private Name name; private Phone phone; diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 642d4f174514..ad9310a965e9 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -1,5 +1,6 @@ package seedu.address.testutil; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_STUDENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; @@ -8,6 +9,7 @@ import seedu.address.logic.commands.AddCommand; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; /** * A utility class for Person. @@ -21,6 +23,13 @@ public static String getAddCommand(Person person) { return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); } + /** + * Returns an add command string for adding the {@code student}. + */ + public static String getAddStudentCommand(Student person) { + return AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + getPersonDetails(person); + } + /** * Returns the part of command string for the given {@code person}'s details. */ diff --git a/src/test/java/seedu/address/testutil/ShortcutCommandBuilder.java b/src/test/java/seedu/address/testutil/ShortcutCommandBuilder.java new file mode 100644 index 000000000000..f42b306c2e98 --- /dev/null +++ b/src/test/java/seedu/address/testutil/ShortcutCommandBuilder.java @@ -0,0 +1,26 @@ +//@@author shanmu9898 +package seedu.address.testutil; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * A utility class to help with building Shortcut objects. + */ +public class ShortcutCommandBuilder { + + private String shortcutWord; + private String commandWord; + + public ShortcutCommandBuilder(String shortcutWord, String commandWord) { + this.commandWord = commandWord; + this.shortcutWord = shortcutWord; + } + + + /** + * @return an {@code Appointment} from the data feed to constructor + */ + public ShortcutDoubles build() { + return new ShortcutDoubles(shortcutWord, commandWord); + } +} diff --git a/src/test/java/seedu/address/testutil/StudentBuilder.java b/src/test/java/seedu/address/testutil/StudentBuilder.java new file mode 100644 index 000000000000..7cc8ff07feef --- /dev/null +++ b/src/test/java/seedu/address/testutil/StudentBuilder.java @@ -0,0 +1,96 @@ +package seedu.address.testutil; + +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Student; +import seedu.address.model.tag.Tag; +import seedu.address.model.util.SampleDataUtil; + +/** + * A utility class to help with building Student objects. + */ +public class StudentBuilder { + + private static final String DEFAULT_NAME = "Alice Pauline"; + private static final String DEFAULT_PHONE = "85355255"; + private static final String DEFAULT_EMAIL = "alice@gmail.com"; + private static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + private static final String DEFAULT_TAGS = "friends"; + + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set tags; + + public StudentBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + tags = SampleDataUtil.getTagSet(DEFAULT_TAGS); + } + + /** + * Initializes the StudentBuilder with the data of {@code personToCopy}. + */ + public StudentBuilder(Student personToCopy) { + name = personToCopy.getName(); + phone = personToCopy.getPhone(); + email = personToCopy.getEmail(); + address = personToCopy.getAddress(); + tags = new HashSet<>(personToCopy.getTags()); + } + + /** + * Sets the {@code Name} of the {@code Person} that we are building. + */ + public StudentBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. + */ + public StudentBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + /** + * Sets the {@code Address} of the {@code Person} that we are building. + */ + + public StudentBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Person} that we are building. + */ + + public StudentBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Person} that we are building. + */ + + public StudentBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + public Student build() { + return new Student(name, phone, email, address, tags); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalEvents.java b/src/test/java/seedu/address/testutil/TypicalEvents.java new file mode 100644 index 000000000000..76c9757c8498 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalEvents.java @@ -0,0 +1,50 @@ +package seedu.address.testutil; + +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.TypicalPersons.CARL; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.event.Appointment; +import seedu.address.model.event.EventTime; +import seedu.address.model.event.Task; +import seedu.address.model.event.Title; + +//@@author Sisyphus25 +/** + * A utility class containing a list of event objects to be used in tests. + */ +public class TypicalEvents { + public static final Appointment TYPICAL_APPOINTMENT_1 = + new AppointmentBuilder("Meeting with parents", "09/10/2018 10:00", "09/10/2018 11:00", ALICE).build(); + public static final Appointment TYPICAL_APPOINTMENT_2 = + new AppointmentBuilder("Consultation session", "04/07/2018 10:00", "04/07/2018 11:00", BOB).build(); + public static final Appointment TYPICAL_APPOINTMENT_3 = + new AppointmentBuilder("Tutoring session", "30/04/2018 10:00", "30/04/2018 11:00", CARL).build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_1 = + new AppointmentBuilder("Meeting with parents", "09/10/2018 10:00", "09/10/2018 11:00").build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_2 = + new AppointmentBuilder("Consultation session", "04/07/2018 10:00", "04/07/2018 11:00", BOB).build(); + public static final Appointment APPOINTMENT_WITHOUT_PERSON_3 = + new AppointmentBuilder("Tutoring session", "30/04/2018 10:00", "30/04/2018 11:00").build(); + + + public static final Task TYPICAL_TASK_1 = + new Task(new Title("To do"), new EventTime("10/10/2018 10:00")); + public static final Task TYPICAL_TASK_2 = + new Task(new Title("Mark papers"), new EventTime("15/04/2018 23:00")); + public static final Task TYPICAL_TASK_3 = + new Task(new Title("Purchase markers"), new EventTime("19/04/2018 10:00")); + public static final Task TYPICAL_TASK_EXPIRED = + new Task(new Title("Expired task"), new EventTime("19/04/2013 10:00")); + + public static List getTypicalAppointments() { + return new ArrayList<>(Arrays.asList(TYPICAL_APPOINTMENT_1, TYPICAL_APPOINTMENT_2)); + } + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(TYPICAL_TASK_1, TYPICAL_TASK_2)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalImportedPersons.java b/src/test/java/seedu/address/testutil/TypicalImportedPersons.java new file mode 100644 index 000000000000..e934a495f9c1 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalImportedPersons.java @@ -0,0 +1,99 @@ +//@@author shanmu9898 +package seedu.address.testutil; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_STUDENT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.person.exceptions.DuplicatePersonException; + +//@@author shanmu9898 +/** + * Special Util class to get people for ImportCommandTest class. + */ +public class TypicalImportedPersons { + public static final Person FLICE = new PersonBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255") + .withTags("friends").build(); + public static final Person FENSON = new PersonBuilder().withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").build(); + public static final Person FARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").build(); + public static final Person FANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").build(); + public static final Person FLLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").build(); + public static final Person GIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").build(); + public static final Student FEORGE = new StudentBuilder().withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").build(); + public static final Student FVAN = new StudentBuilder().withName("Ivan Kutz").withPhone("9867723") + .withEmail("wolf@example.com").withAddress("Centre Street").build(); + public static final Student FOHN = new StudentBuilder().withName("John Blake").withPhone("9575232") + .withEmail("star@example.com").withAddress("Hollywood").build(); + + // Manually added + public static final Person FOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Person FDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + public static final Student FTUDENT_HOON = new StudentBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Student FTUDENT_IDA = new StudentBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Person FMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + public static final Person FOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .build(); + + public static final Student FTUDENT_AMY = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + public static final Student FTUDENT_BOB = new StudentBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_STUDENT) + .build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalImportedPersons() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical persons. + */ + public static AddressBook getImportedAddressBook() { + AddressBook ab = new AddressBook(); + for (Person person : getImportedPersons()) { + try { + ab.addPerson(person); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getImportedPersons() { + return new ArrayList<>(Arrays.asList(FLICE, FENSON, FARL, FANIEL, FLLE, GIONA, FEORGE)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e6139376570..134e60a4c207 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -6,7 +6,7 @@ * A utility class containing a list of {@code Index} objects to be used in tests. */ public class TypicalIndexes { - public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); - public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); - public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST = Index.fromOneBased(1); + public static final Index INDEX_SECOND = Index.fromOneBased(2); + public static final Index INDEX_THIRD = Index.fromOneBased(3); } diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index 6d7bdbfc55ed..a1dda2174876 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -10,13 +10,18 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_STUDENT; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import seedu.address.model.AddressBook; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; import seedu.address.model.person.Person; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; /** @@ -42,12 +47,23 @@ public class TypicalPersons { .withEmail("lydia@example.com").withAddress("little tokyo").build(); public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") .withEmail("anna@example.com").withAddress("4th street").build(); + public static final Student IVAN = new StudentBuilder().withName("Ivan Kutz").withPhone("9867723") + .withEmail("wolf@example.com").withAddress("Centre Street").build(); + public static final Student JOHN = new StudentBuilder().withName("John Blake").withPhone("9575232") + .withEmail("star@example.com").withAddress("Hollywood").build(); // Manually added public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") .withEmail("stefan@example.com").withAddress("little india").build(); public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") .withEmail("hans@example.com").withAddress("chicago ave").build(); + public static final Person DING = new PersonBuilder().withName("Ding Thunderstorm").withPhone("81524871") + .withEmail("hansolo@example.com").withAddress("Science Park Road").build(); + + public static final Student STUDENT_HOON = new StudentBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Student STUDENT_IDA = new StudentBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); // Manually added - Person's details found in {@code CommandTestUtil} public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) @@ -56,6 +72,12 @@ public class TypicalPersons { .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) .build(); + public static final Student STUDENT_AMY = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + public static final Student STUDENT_BOB = new StudentBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_STUDENT) + .build(); + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER private TypicalPersons() {} // prevents instantiation @@ -72,6 +94,20 @@ public static AddressBook getTypicalAddressBook() { throw new AssertionError("not possible"); } } + for (Appointment ap : TypicalEvents.getTypicalAppointments()) { + try { + ab.addAppointment(ap); + } catch (DuplicateEventException e) { + throw new AssertionError("not possible"); + } + } + for (Task t : TypicalEvents.getTypicalTasks()) { + try { + ab.addTask(t); + } catch (DuplicateEventException e) { + throw new AssertionError("not possible"); + } + } return ab; } diff --git a/src/test/java/seedu/address/testutil/TypicalShortcuts.java b/src/test/java/seedu/address/testutil/TypicalShortcuts.java new file mode 100644 index 000000000000..f7b7c1ac2d10 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalShortcuts.java @@ -0,0 +1,31 @@ +//@@author shanmu9898 +package seedu.address.testutil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.shortcuts.ShortcutDoubles; + +/** + * Few Typical Shortcuts + */ +public class TypicalShortcuts { + public static final ShortcutDoubles SHORTCUT_DOUBLES_1 = + new ShortcutCommandBuilder("l", "list").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_2 = + new ShortcutCommandBuilder("c", "clear").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_3 = + new ShortcutCommandBuilder("ll", "list").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_4 = + new ShortcutCommandBuilder("cc", "clear").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_5 = + new ShortcutCommandBuilder("a", "add").build(); + public static final ShortcutDoubles SHORTCUT_DOUBLES_6 = + new ShortcutCommandBuilder("aa", "add").build(); + + public static List getTypicalShortcuts() { + return new ArrayList<>(Arrays.asList(SHORTCUT_DOUBLES_1, SHORTCUT_DOUBLES_2)); + } + +} diff --git a/src/test/java/seedu/address/testutil/modelstub/ModelStub.java b/src/test/java/seedu/address/testutil/modelstub/ModelStub.java new file mode 100644 index 000000000000..c39ee2a66bf1 --- /dev/null +++ b/src/test/java/seedu/address/testutil/modelstub/ModelStub.java @@ -0,0 +1,147 @@ +package seedu.address.testutil.modelstub; + +import static org.junit.Assert.fail; + +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; +import seedu.address.model.person.Person; +import seedu.address.model.person.Student; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.shortcuts.ShortcutDoubles; +import seedu.address.model.shortcuts.UniqueShortcutDoublesList; +import seedu.address.model.tag.Tag; + +/** + * A default model stub that have all of the methods failing. + */ +public class ModelStub implements Model { + @Override + public void addPerson(Person person) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void addStudent(Student student) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void resetData(ReadOnlyAddressBook newData) { + fail("This method should not be called."); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + fail("This method should not be called."); + return null; + } + + @Override + public ObservableList getFilteredCommandsList() { + fail("This method should not be called."); + return null; + } + + @Override + public void deletePerson(Person target) throws PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void deleteStudent(Student target) throws PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void updateStudent(Student target, Student editedStudent) + throws DuplicatePersonException, PersonNotFoundException { + fail("This method should not be called."); + } + + //@@author shanmu9898 + @Override + public void addCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.DuplicateShortcutDoublesException { + fail("This method should not be called."); + } + + @Override + public void deleteCommandShortcut(ShortcutDoubles shortcutDoubles) + throws UniqueShortcutDoublesList.CommandShortcutNotFoundException { + fail("This method should not be called"); + } + //@@author + + @Override + public ObservableList getFilteredPersonList() { + fail("This method should not be called."); + return null; + } + + @Override + public ObservableList getFilteredAppointmentList() { + fail("This method should not be called."); + return null; + } + + @Override + public ObservableList getFilteredTaskList() { + fail("This method should not be called."); + return null; + } + + @Override + public String getCurrentActiveListType() { + fail("This method should not be called."); + return null; + } + + @Override + public void changeCurrentActiveListType(String type) { + fail("This method should not be called."); + } + + @Override + public void updateFilteredPersonList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public void deleteTag(Tag tag) { + fail("This method should not be called."); + } + + @Override + public void addAppointment(Appointment appointment) throws DuplicateEventException { + fail("This method should not be called."); + } + + @Override + public void deleteAppointment(Appointment appointment) throws EventNotFoundException { + fail("This method should not be called."); + } + + @Override + public void addTask(Task task) throws DuplicateEventException { + fail("This method should not be called."); + } + + @Override + public void deleteTask(Task task) throws EventNotFoundException { + fail("This method should not be called."); + } +} diff --git a/src/test/java/seedu/address/testutil/modelstub/ModelStubAcceptingTaskAdded.java b/src/test/java/seedu/address/testutil/modelstub/ModelStubAcceptingTaskAdded.java new file mode 100644 index 000000000000..7c491269fdd4 --- /dev/null +++ b/src/test/java/seedu/address/testutil/modelstub/ModelStubAcceptingTaskAdded.java @@ -0,0 +1,29 @@ +package seedu.address.testutil.modelstub; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +import seedu.address.model.AddressBook; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; + +//@@author Sisyphus25 +/** + * A Model stub that always accept the task being added. + */ +public class ModelStubAcceptingTaskAdded extends ModelStub { + public final ArrayList tasksAdded = new ArrayList<>(); + + @Override + public void addTask(Task event) throws DuplicateEventException { + requireNonNull(event); + tasksAdded.add(event); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } +} diff --git a/src/test/java/seedu/address/testutil/modelstub/ModelStubThrowingDuplicateEventException.java b/src/test/java/seedu/address/testutil/modelstub/ModelStubThrowingDuplicateEventException.java new file mode 100644 index 000000000000..2de7ec544b15 --- /dev/null +++ b/src/test/java/seedu/address/testutil/modelstub/ModelStubThrowingDuplicateEventException.java @@ -0,0 +1,28 @@ +package seedu.address.testutil.modelstub; + +import seedu.address.model.AddressBook; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.event.Appointment; +import seedu.address.model.event.Task; +import seedu.address.model.event.exceptions.DuplicateEventException; + +//@@author Sisyphus25 +/** + * A Model stub that always throw a DuplicateEventException when trying to add an appointment/task. + */ +public class ModelStubThrowingDuplicateEventException extends ModelStub { + @Override + public void addAppointment (Appointment appointment) throws DuplicateEventException { + throw new DuplicateEventException(); + } + + @Override + public void addTask (Task task) throws DuplicateEventException { + throw new DuplicateEventException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } +} diff --git a/src/test/java/seedu/address/ui/AppointmentCardTest.java b/src/test/java/seedu/address/ui/AppointmentCardTest.java new file mode 100644 index 000000000000..7e2cfe765bda --- /dev/null +++ b/src/test/java/seedu/address/ui/AppointmentCardTest.java @@ -0,0 +1,41 @@ +package seedu.address.ui; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_2; +import static seedu.address.testutil.TypicalEvents.TYPICAL_APPOINTMENT_3; + +import org.junit.Test; + +import seedu.address.model.event.Appointment; + +//@@author Sisyphus25-reused +//Reuse with modification from PersonCardTest +public class AppointmentCardTest extends GuiUnitTest { + + @Test + public void equals() { + Appointment appointment = TYPICAL_APPOINTMENT_2; + AppointmentCard appointmentCard = new AppointmentCard(appointment, 0); + + // same appointment, same index -> returns true + AppointmentCard copy = new AppointmentCard(appointment, 0); + assertTrue(appointmentCard.equals(copy)); + + // same object -> returns true + assertTrue(appointmentCard.equals(appointmentCard)); + + // null -> returns false + assertFalse(appointmentCard.equals(null)); + + // different types -> returns false + assertFalse(appointmentCard.equals(0)); + + // different appointment, same index -> returns false + assertFalse(appointmentCard.equals(new AppointmentCard(TYPICAL_APPOINTMENT_3, 0))); + + // same appointment, different index -> returns false + assertFalse(appointmentCard.equals(new AppointmentCard(appointment, 1))); + } + +} diff --git a/src/test/java/seedu/address/ui/BrowserPanelTest.java b/src/test/java/seedu/address/ui/BrowserPanelTest.java deleted file mode 100644 index 48aab940f8a8..000000000000 --- a/src/test/java/seedu/address/ui/BrowserPanelTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.ui; - -import static guitests.guihandles.WebViewUtil.waitUntilBrowserLoaded; -import static org.junit.Assert.assertEquals; -import static seedu.address.testutil.EventsUtil.postNow; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.ui.BrowserPanel.DEFAULT_PAGE; -import static seedu.address.ui.UiPart.FXML_FILE_FOLDER; - -import java.net.URL; - -import org.junit.Before; -import org.junit.Test; - -import guitests.guihandles.BrowserPanelHandle; -import seedu.address.MainApp; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; - -public class BrowserPanelTest extends GuiUnitTest { - private PersonPanelSelectionChangedEvent selectionChangedEventStub; - - private BrowserPanel browserPanel; - private BrowserPanelHandle browserPanelHandle; - - @Before - public void setUp() { - selectionChangedEventStub = new PersonPanelSelectionChangedEvent(new PersonCard(ALICE, 0)); - - guiRobot.interact(() -> browserPanel = new BrowserPanel()); - uiPartRule.setUiPart(browserPanel); - - browserPanelHandle = new BrowserPanelHandle(browserPanel.getRoot()); - } - - @Test - public void display() throws Exception { - // default web page - URL expectedDefaultPageUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - assertEquals(expectedDefaultPageUrl, browserPanelHandle.getLoadedUrl()); - - // associated web page of a person - postNow(selectionChangedEventStub); - URL expectedPersonUrl = new URL(BrowserPanel.SEARCH_PAGE_URL + ALICE.getName().fullName.replaceAll(" ", "%20")); - - waitUntilBrowserLoaded(browserPanelHandle); - assertEquals(expectedPersonUrl, browserPanelHandle.getLoadedUrl()); - } -} diff --git a/src/test/java/seedu/address/ui/CommandBoxTest.java b/src/test/java/seedu/address/ui/CommandBoxTest.java index f72304570a7a..ba30b8cb25e9 100644 --- a/src/test/java/seedu/address/ui/CommandBoxTest.java +++ b/src/test/java/seedu/address/ui/CommandBoxTest.java @@ -17,7 +17,8 @@ public class CommandBoxTest extends GuiUnitTest { - private static final String COMMAND_THAT_SUCCEEDS = ListCommand.COMMAND_WORD; + private static final String COMMAND_THAT_SUCCEEDS = ListCommand.COMMAND_WORD + + " " + ListCommand.TYPE_CONTACT; private static final String COMMAND_THAT_FAILS = "invalid command"; private ArrayList defaultStyleOfCommandBox; @@ -32,6 +33,7 @@ public void setUp() { CommandBox commandBox = new CommandBox(logic); commandBoxHandle = new CommandBoxHandle(getChildNode(commandBox.getRoot(), + CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); uiPartRule.setUiPart(commandBox); @@ -91,7 +93,7 @@ public void handleKeyPress_startingWithUp() { // insert command in the middle of retrieving previous commands guiRobot.push(KeyCode.UP); - String thirdCommand = "list"; + String thirdCommand = "list contacts"; commandBoxHandle.run(thirdCommand); assertInputHistory(KeyCode.UP, thirdCommand); assertInputHistory(KeyCode.UP, COMMAND_THAT_FAILS); @@ -119,7 +121,7 @@ public void handleKeyPress_startingWithDown() { // insert command in the middle of retrieving previous commands guiRobot.push(KeyCode.UP); - String thirdCommand = "list"; + String thirdCommand = "list contact"; commandBoxHandle.run(thirdCommand); assertInputHistory(KeyCode.DOWN, ""); assertInputHistory(KeyCode.UP, thirdCommand); diff --git a/src/test/java/seedu/address/ui/PersonListPanelTest.java b/src/test/java/seedu/address/ui/PersonListPanelTest.java index 906ec2bebc03..71a674b5c321 100644 --- a/src/test/java/seedu/address/ui/PersonListPanelTest.java +++ b/src/test/java/seedu/address/ui/PersonListPanelTest.java @@ -2,10 +2,10 @@ import static org.junit.Assert.assertEquals; import static seedu.address.testutil.EventsUtil.postNow; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalPersons; import static seedu.address.ui.testutil.GuiTestAssert.assertCardDisplaysPerson; -import static seedu.address.ui.testutil.GuiTestAssert.assertCardEquals; +import static seedu.address.ui.testutil.GuiTestAssert.assertPersonCardEquals; import org.junit.Before; import org.junit.Test; @@ -21,7 +21,7 @@ public class PersonListPanelTest extends GuiUnitTest { private static final ObservableList TYPICAL_PERSONS = FXCollections.observableList(getTypicalPersons()); - private static final JumpToListRequestEvent JUMP_TO_SECOND_EVENT = new JumpToListRequestEvent(INDEX_SECOND_PERSON); + private static final JumpToListRequestEvent JUMP_TO_SECOND_EVENT = new JumpToListRequestEvent(INDEX_SECOND); private PersonListPanelHandle personListPanelHandle; @@ -51,8 +51,8 @@ public void handleJumpToListRequestEvent() { postNow(JUMP_TO_SECOND_EVENT); guiRobot.pauseForHuman(); - PersonCardHandle expectedCard = personListPanelHandle.getPersonCardHandle(INDEX_SECOND_PERSON.getZeroBased()); + PersonCardHandle expectedCard = personListPanelHandle.getPersonCardHandle(INDEX_SECOND.getZeroBased()); PersonCardHandle selectedCard = personListPanelHandle.getHandleToSelectedCard(); - assertCardEquals(expectedCard, selectedCard); + assertPersonCardEquals(expectedCard, selectedCard); } } diff --git a/src/test/java/seedu/address/ui/ShortcutCardTest.java b/src/test/java/seedu/address/ui/ShortcutCardTest.java new file mode 100644 index 000000000000..6acc25434933 --- /dev/null +++ b/src/test/java/seedu/address/ui/ShortcutCardTest.java @@ -0,0 +1,41 @@ +//@@author shanmu9898 - unused +//package seedu.address.ui; +// +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertTrue; +//import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_3; +//import static seedu.address.testutil.TypicalShortcuts.SHORTCUT_DOUBLES_5; +// +//import org.junit.Test; +// +//import seedu.address.model.shortcuts.ShortcutDoubles; +// +//public class ShortcutCardTest { +// +// +// @Test +// public void equals() { +// ShortcutDoubles shortcutDoubles = SHORTCUT_DOUBLES_5; +// //ShortcutCard shortcutCard = new ShortcutCard(shortcutDoubles, 0); +// +// // same shortcut, same index -> returns true +// ShortcutCard copy = new ShortcutCard(shortcutDoubles, 0); +// assertTrue(shortcutCard.equals(copy)); +// +// // same object -> returns true +// assertTrue(shortcutCard.equals(shortcutCard)); +// +// // null -> returns false +// assertFalse(shortcutCard.equals(null)); +// +// // different types -> returns false +// assertFalse(shortcutCard.equals(0)); +// +// // different shortcut, same index -> returns false +// ShortcutDoubles differentshortcut = SHORTCUT_DOUBLES_3; +// assertFalse(shortcutCard.equals(new ShortcutCard(differentshortcut, 0))); +// +// // same shortcut, different index -> returns false +// assertFalse(shortcutCard.equals(new ShortcutCard(shortcutDoubles, 1))); +// } +//} diff --git a/src/test/java/seedu/address/ui/TaskCardTest.java b/src/test/java/seedu/address/ui/TaskCardTest.java new file mode 100644 index 000000000000..e3b7a6519d50 --- /dev/null +++ b/src/test/java/seedu/address/ui/TaskCardTest.java @@ -0,0 +1,44 @@ +package seedu.address.ui; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_1; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_2; +import static seedu.address.testutil.TypicalEvents.TYPICAL_TASK_EXPIRED; + +import org.junit.Test; + +import seedu.address.model.event.Task; + +//@@author Sisyphus25-reused +//Reuse with modification from PersonCardTest +public class TaskCardTest extends GuiUnitTest { + + @Test + public void equals() { + Task task = TYPICAL_TASK_2; + TaskCard taskCard = new TaskCard(task, 0); + + // same task, same index -> returns true + TaskCard copy = new TaskCard(task, 0); + assertTrue(taskCard.equals(copy)); + + // same object -> returns true + assertTrue(taskCard.equals(taskCard)); + + // null -> returns false + assertFalse(taskCard.equals(null)); + + // different types -> returns false + assertFalse(taskCard.equals(0)); + + // different task, same index -> returns false + Task differentTask = TYPICAL_TASK_1; + assertFalse(taskCard.equals(new TaskCard(differentTask, 0))); + + Task expiredTask = TYPICAL_TASK_EXPIRED; + TaskCard expiredTaskCard = new TaskCard(TYPICAL_TASK_EXPIRED, 1); + // same task, different index -> returns false + assertFalse(taskCard.equals(expiredTaskCard)); + } +} diff --git a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java index d21cc2fb3739..cb91a818d558 100644 --- a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java +++ b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -14,16 +15,21 @@ * A set of assertion methods useful for writing GUI tests. */ public class GuiTestAssert { + private static final String LABEL_DEFAULT_STYLE = "label"; + /** * Asserts that {@code actualCard} displays the same values as {@code expectedCard}. */ - public static void assertCardEquals(PersonCardHandle expectedCard, PersonCardHandle actualCard) { + public static void assertPersonCardEquals(PersonCardHandle expectedCard, PersonCardHandle actualCard) { assertEquals(expectedCard.getId(), actualCard.getId()); assertEquals(expectedCard.getAddress(), actualCard.getAddress()); assertEquals(expectedCard.getEmail(), actualCard.getEmail()); assertEquals(expectedCard.getName(), actualCard.getName()); assertEquals(expectedCard.getPhone(), actualCard.getPhone()); - assertEquals(expectedCard.getTags(), actualCard.getTags()); + assertEquals(expectedCard.getTagsNames(), actualCard.getTagsNames()); + + expectedCard.getTagsNames().forEach(tag -> + assertEquals(expectedCard.getTagStyleClasses(tag), actualCard.getTagStyleClasses(tag))); } /** @@ -34,8 +40,22 @@ public static void assertCardDisplaysPerson(Person expectedPerson, PersonCardHan assertEquals(expectedPerson.getPhone().value, actualCard.getPhone()); assertEquals(expectedPerson.getEmail().value, actualCard.getEmail()); assertEquals(expectedPerson.getAddress().value, actualCard.getAddress()); - assertEquals(expectedPerson.getTags().stream().map(tag -> tag.tagName).collect(Collectors.toList()), - actualCard.getTags()); + + assertTagsEqual(expectedPerson, actualCard); + } + + /** + * Asserts that the tags in {@code actualCard} matches all the tags in {@code expectedPerson} with the correct + * color. + */ + private static void assertTagsEqual(Person expectedPerson, PersonCardHandle actualCard) { + List expectedTagsNames = expectedPerson.getTags().stream() + .map(tag -> tag.tagName).collect(Collectors.toList()); + assertEquals(expectedTagsNames, actualCard.getTagsNames()); + expectedPerson.getTags().forEach(tag -> + assertEquals(Arrays.asList(LABEL_DEFAULT_STYLE, tag.tagColorStyle), + actualCard.getTagStyleClasses(tag.tagName)) + ); } /** @@ -70,4 +90,6 @@ public static void assertListSize(PersonListPanelHandle personListPanelHandle, i public static void assertResultMessage(ResultDisplayHandle resultDisplayHandle, String expected) { assertEquals(expected, resultDisplayHandle.getText()); } + + } diff --git a/src/test/java/systemtests/AddCommandSystemTest.java b/src/test/java/systemtests/AddCommandSystemTest.java index 3254b60154c4..cc3063cc3cf1 100644 --- a/src/test/java/systemtests/AddCommandSystemTest.java +++ b/src/test/java/systemtests/AddCommandSystemTest.java @@ -14,8 +14,8 @@ import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_STUDENT; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_STUDENT; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; @@ -24,15 +24,16 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_STUDENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.HOON; +import static seedu.address.testutil.TypicalPersons.GEORGE; import static seedu.address.testutil.TypicalPersons.IDA; +import static seedu.address.testutil.TypicalPersons.IVAN; +import static seedu.address.testutil.TypicalPersons.JOHN; import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.TypicalPersons.STUDENT_AMY; +import static seedu.address.testutil.TypicalPersons.STUDENT_BOB; +import static seedu.address.testutil.TypicalPersons.STUDENT_HOON; import org.junit.Test; @@ -47,10 +48,11 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Student; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; +import seedu.address.testutil.StudentBuilder; public class AddCommandSystemTest extends AddressBookSystemTest { @@ -60,12 +62,12 @@ public void add() throws Exception { /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ - /* Case: add a person without tags to a non-empty address book, command with leading spaces and trailing spaces + /* Case: add a student without tags to a non-empty address book, command with leading spaces and trailing spaces * -> added */ - Person toAdd = AMY; - String command = " " + AddCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " - + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + TAG_DESC_FRIEND + " "; + Student toAdd = STUDENT_AMY; + String command = " " + AddCommand.COMMAND_WORD + " " + PREAMBLE_STUDENT + " " + NAME_DESC_AMY + " " + + PHONE_DESC_AMY + " " + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + TAG_DESC_STUDENT + " "; assertCommandSuccess(command, toAdd); /* Case: undo adding Amy to the list -> Amy deleted */ @@ -75,116 +77,129 @@ public void add() throws Exception { /* Case: redo adding Amy to the list -> Amy added again */ command = RedoCommand.COMMAND_WORD; - model.addPerson(toAdd); + model.addStudent(toAdd); expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; assertCommandSuccess(command, model, expectedResultMessage); - /* Case: add a person with all fields same as another person in the address book except name -> added */ - toAdd = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) - .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - command = AddCommand.COMMAND_WORD + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY - + TAG_DESC_FRIEND; + /* Case: add a student with all fields same as another person in the address book except name -> added */ + toAdd = new StudentBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + TAG_DESC_STUDENT; assertCommandSuccess(command, toAdd); - /* Case: add a person with all fields same as another person in the address book except phone -> added */ - toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) - .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY - + TAG_DESC_FRIEND; + /* Case: add a student with all fields same as another person in the address book except phone -> added */ + toAdd = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + TAG_DESC_STUDENT; assertCommandSuccess(command, toAdd); - /* Case: add a person with all fields same as another person in the address book except email -> added */ - toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + TAG_DESC_FRIEND; + /* Case: add a student with all fields same as another person in the address book except email -> added */ + toAdd = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_STUDENT).build(); + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_AMY + TAG_DESC_STUDENT; assertCommandSuccess(command, toAdd); - /* Case: add a person with all fields same as another person in the address book except address -> added */ - toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) - .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND).build(); - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_BOB - + TAG_DESC_FRIEND; + /* Case: add a student with all fields same as another person in the address book except address -> added */ + toAdd = new StudentBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_STUDENT).build(); + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_BOB + TAG_DESC_STUDENT; assertCommandSuccess(command, toAdd); /* Case: add to empty address book -> added */ deleteAllPersons(); - assertCommandSuccess(ALICE); + assertCommandSuccess(GEORGE); - /* Case: add a person with tags, command with parameters in random order -> added */ - toAdd = BOB; - command = AddCommand.COMMAND_WORD + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB - + TAG_DESC_HUSBAND + EMAIL_DESC_BOB; + /* Case: add a student with tags, command with parameters in random order -> added */ + toAdd = STUDENT_BOB; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB + + TAG_DESC_STUDENT + EMAIL_DESC_BOB; assertCommandSuccess(command, toAdd); - /* Case: add a person, missing tags -> added */ - assertCommandSuccess(HOON); + /* Case: add a student, missing tags -> added */ + assertCommandSuccess(STUDENT_HOON); + + /* Case: add a default person -> added */ + assertCommandSuccess(IDA); /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ /* Case: filters the person list before adding -> added */ showPersonsWithName(KEYWORD_MATCHING_MEIER); - assertCommandSuccess(IDA); + assertCommandSuccess(JOHN); /* ------------------------ Perform add operation while a person card is selected --------------------------- */ - /* Case: selects first card in the person list, add a person -> added, card selection remains unchanged */ + /* Case: selects first card in the person list, add a student -> added, card selection remains unchanged */ selectPerson(Index.fromOneBased(1)); - assertCommandSuccess(CARL); + assertCommandSuccess(IVAN); /* ----------------------------------- Perform invalid add operations --------------------------------------- */ - /* Case: add a duplicate person -> rejected */ - command = PersonUtil.getAddCommand(HOON); + /* Case: add a duplicate student -> rejected */ + command = PersonUtil.getAddStudentCommand(STUDENT_HOON); assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_PERSON); - /* Case: add a duplicate person except with different tags -> rejected */ - // "friends" is an existing tag used in the default model, see TypicalPersons#ALICE + /* Case: add a duplicate student except with different tags -> rejected */ + // "student" is an existing tag used in the default model, see TypicalPersons#STUDENT_ALICE // This test will fail if a new tag that is not in the model is used, see the bug documented in // AddressBook#addPerson(Person) - command = PersonUtil.getAddCommand(HOON) + " " + PREFIX_TAG.getPrefix() + "friends"; + command = PersonUtil.getAddStudentCommand(STUDENT_HOON) + " " + PREFIX_TAG.getPrefix() + "student"; assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_PERSON); /* Case: missing name -> rejected */ - command = AddCommand.COMMAND_WORD + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); /* Case: missing phone -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); /* Case: missing email -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY; assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); /* Case: missing address -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY; assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); /* Case: invalid keyword -> rejected */ - command = "adds " + PersonUtil.getPersonDetails(toAdd); + command = "adds " + PREAMBLE_STUDENT + PersonUtil.getPersonDetails(toAdd); assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + /* Case: invalid type -> rejected */ + command = AddCommand.COMMAND_WORD + " stu " + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY; + assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); + /* Case: invalid name -> rejected */ - command = AddCommand.COMMAND_WORD + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY; assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); /* Case: invalid phone -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY; assertCommandFailure(command, Phone.MESSAGE_PHONE_CONSTRAINTS); /* Case: invalid email -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + ADDRESS_DESC_AMY; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + + ADDRESS_DESC_AMY; assertCommandFailure(command, Email.MESSAGE_EMAIL_CONSTRAINTS); /* Case: invalid address -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_ADDRESS_DESC; + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + INVALID_ADDRESS_DESC; assertCommandFailure(command, Address.MESSAGE_ADDRESS_CONSTRAINTS); /* Case: invalid tag -> rejected */ - command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + command = AddCommand.COMMAND_WORD + PREAMBLE_STUDENT + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + INVALID_TAG_DESC; - assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + assertCommandFailure(command, Tag.MESSAGE_TAG_NAME_CONSTRAINTS); } /** @@ -205,6 +220,15 @@ private void assertCommandSuccess(Person toAdd) { assertCommandSuccess(PersonUtil.getAddCommand(toAdd), toAdd); } + /** + * Performs the same verification as {@code assertCommandSuccess(Person)}. + * @see AddCommandSystemTest#assertCommandSuccess(Person) + */ + private void assertCommandSuccess(Student toAdd) { + assertCommandSuccess(PersonUtil.getAddStudentCommand(toAdd), toAdd); + } + + /** * Performs the same verification as {@code assertCommandSuccess(Person)}. Executes {@code command} * instead. @@ -217,7 +241,24 @@ private void assertCommandSuccess(String command, Person toAdd) { } catch (DuplicatePersonException dpe) { throw new IllegalArgumentException("toAdd already exists in the model."); } - String expectedResultMessage = String.format(AddCommand.MESSAGE_SUCCESS, toAdd); + String expectedResultMessage = String.format(AddCommand.MESSAGE_ADD_PERSON_SUCCESS, toAdd); + + assertCommandSuccess(command, expectedModel, expectedResultMessage); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(Person)}. Executes {@code command} + * instead. + * @see AddCommandSystemTest#assertCommandSuccess(Person) + */ + private void assertCommandSuccess(String command, Student toAdd) { + Model expectedModel = getModel(); + try { + expectedModel.addStudent(toAdd); + } catch (DuplicatePersonException dpe) { + throw new IllegalArgumentException("toAdd already exists in the model."); + } + String expectedResultMessage = String.format(AddCommand.MESSAGE_ADD_STUDENT_SUCCESS, toAdd); assertCommandSuccess(command, expectedModel, expectedResultMessage); } diff --git a/src/test/java/systemtests/AddressBookSystemTest.java b/src/test/java/systemtests/AddressBookSystemTest.java index 97cdf96d65b8..6d5bfeec2a75 100644 --- a/src/test/java/systemtests/AddressBookSystemTest.java +++ b/src/test/java/systemtests/AddressBookSystemTest.java @@ -1,18 +1,14 @@ package systemtests; -import static guitests.guihandles.WebViewUtil.waitUntilBrowserLoaded; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static seedu.address.ui.BrowserPanel.DEFAULT_PAGE; import static seedu.address.ui.StatusBarFooter.SYNC_STATUS_INITIAL; import static seedu.address.ui.StatusBarFooter.SYNC_STATUS_UPDATED; -import static seedu.address.ui.UiPart.FXML_FILE_FOLDER; import static seedu.address.ui.testutil.GuiTestAssert.assertListMatching; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Arrays; + import java.util.Date; import java.util.List; @@ -21,14 +17,13 @@ import org.junit.BeforeClass; import org.junit.ClassRule; -import guitests.guihandles.BrowserPanelHandle; +import guitests.guihandles.CalendarPanelHandle; import guitests.guihandles.CommandBoxHandle; import guitests.guihandles.MainMenuHandle; import guitests.guihandles.MainWindowHandle; import guitests.guihandles.PersonListPanelHandle; import guitests.guihandles.ResultDisplayHandle; import guitests.guihandles.StatusBarFooterHandle; -import seedu.address.MainApp; import seedu.address.TestApp; import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.index.Index; @@ -39,7 +34,6 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.testutil.TypicalPersons; -import seedu.address.ui.BrowserPanel; import seedu.address.ui.CommandBox; /** @@ -69,7 +63,6 @@ public void setUp() { testApp = setupHelper.setupApplication(this::getInitialData, getDataFileLocation()); mainWindowHandle = setupHelper.setupMainWindowHandle(); - waitUntilBrowserLoaded(getBrowserPanel()); assertApplicationStartingStateIsCorrect(); } @@ -109,8 +102,8 @@ public MainMenuHandle getMainMenu() { return mainWindowHandle.getMainMenu(); } - public BrowserPanelHandle getBrowserPanel() { - return mainWindowHandle.getBrowserPanel(); + public CalendarPanelHandle getCalendarPanel() { + return mainWindowHandle.getCalendarPanel(); } public StatusBarFooterHandle getStatusBarFooter() { @@ -132,15 +125,13 @@ protected void executeCommand(String command) { clockRule.setInjectedClockToCurrentTime(); mainWindowHandle.getCommandBox().run(command); - - waitUntilBrowserLoaded(getBrowserPanel()); } /** * Displays all persons in the address book. */ protected void showAllPersons() { - executeCommand(ListCommand.COMMAND_WORD); + executeCommand(ListCommand.COMMAND_WORD + " " + ListCommand.TYPE_CONTACT); assertEquals(getModel().getAddressBook().getPersonList().size(), getModel().getFilteredPersonList().size()); } @@ -188,7 +179,6 @@ protected void assertApplicationDisplaysExpected(String expectedCommandInput, St */ private void rememberStates() { StatusBarFooterHandle statusBarFooterHandle = getStatusBarFooter(); - getBrowserPanel().rememberUrl(); statusBarFooterHandle.rememberSaveLocation(); statusBarFooterHandle.rememberSyncStatus(); getPersonListPanel().rememberSelectedPersonCard(); @@ -197,39 +187,27 @@ private void rememberStates() { /** * Asserts that the previously selected card is now deselected and the browser's url remains displaying the details * of the previously selected person. - * @see BrowserPanelHandle#isUrlChanged() */ protected void assertSelectedCardDeselected() { - assertFalse(getBrowserPanel().isUrlChanged()); assertFalse(getPersonListPanel().isAnyCardSelected()); } /** * Asserts that the browser's url is changed to display the details of the person in the person list panel at * {@code expectedSelectedCardIndex}, and only the card at {@code expectedSelectedCardIndex} is selected. - * @see BrowserPanelHandle#isUrlChanged() * @see PersonListPanelHandle#isSelectedPersonCardChanged() */ protected void assertSelectedCardChanged(Index expectedSelectedCardIndex) { String selectedCardName = getPersonListPanel().getHandleToSelectedCard().getName(); - URL expectedUrl; - try { - expectedUrl = new URL(BrowserPanel.SEARCH_PAGE_URL + selectedCardName.replaceAll(" ", "%20")); - } catch (MalformedURLException mue) { - throw new AssertionError("URL expected to be valid."); - } - assertEquals(expectedUrl, getBrowserPanel().getLoadedUrl()); assertEquals(expectedSelectedCardIndex.getZeroBased(), getPersonListPanel().getSelectedCardIndex()); } /** * Asserts that the browser's url and the selected card in the person list panel remain unchanged. - * @see BrowserPanelHandle#isUrlChanged() * @see PersonListPanelHandle#isSelectedPersonCardChanged() */ protected void assertSelectedCardUnchanged() { - assertFalse(getBrowserPanel().isUrlChanged()); assertFalse(getPersonListPanel().isSelectedPersonCardChanged()); } @@ -276,7 +254,6 @@ private void assertApplicationStartingStateIsCorrect() { assertEquals("", getCommandBox().getInput()); assertEquals("", getResultDisplay().getText()); assertListMatching(getPersonListPanel(), getModel().getFilteredPersonList()); - assertEquals(MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE), getBrowserPanel().getLoadedUrl()); assertEquals("./" + testApp.getStorageSaveLocation(), getStatusBarFooter().getSaveLocation()); assertEquals(SYNC_STATUS_INITIAL, getStatusBarFooter().getSyncStatus()); } catch (Exception e) { diff --git a/src/test/java/systemtests/DeleteCommandSystemTest.java b/src/test/java/systemtests/DeleteCommandSystemTest.java index c0de78e4aba6..94afb9b53cf5 100644 --- a/src/test/java/systemtests/DeleteCommandSystemTest.java +++ b/src/test/java/systemtests/DeleteCommandSystemTest.java @@ -7,7 +7,7 @@ import static seedu.address.testutil.TestUtil.getLastIndex; import static seedu.address.testutil.TestUtil.getMidIndex; import static seedu.address.testutil.TestUtil.getPerson; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; import org.junit.Test; @@ -32,8 +32,8 @@ public void delete() { /* Case: delete the first person in the list, command with leading spaces and trailing spaces -> deleted */ Model expectedModel = getModel(); - String command = " " + DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + " "; - Person deletedPerson = removePerson(expectedModel, INDEX_FIRST_PERSON); + String command = " " + DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + " "; + Person deletedPerson = removePerson(expectedModel, INDEX_FIRST); String expectedResultMessage = String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPerson); assertCommandSuccess(command, expectedModel, expectedResultMessage); @@ -61,7 +61,7 @@ public void delete() { /* Case: filtered person list, delete index within bounds of address book and person list -> deleted */ showPersonsWithName(KEYWORD_MATCHING_MEIER); - Index index = INDEX_FIRST_PERSON; + Index index = INDEX_FIRST; assertTrue(index.getZeroBased() < getModel().getFilteredPersonList().size()); assertCommandSuccess(index); diff --git a/src/test/java/systemtests/EditCommandSystemTest.java b/src/test/java/systemtests/EditCommandSystemTest.java index 820933203dd9..debbd32f0a68 100644 --- a/src/test/java/systemtests/EditCommandSystemTest.java +++ b/src/test/java/systemtests/EditCommandSystemTest.java @@ -25,7 +25,7 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.AMY; import static seedu.address.testutil.TypicalPersons.BOB; import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; @@ -60,7 +60,7 @@ public void edit() throws Exception { /* Case: edit all fields, command with leading spaces, trailing spaces and multiple spaces between each field * -> edited */ - Index index = INDEX_FIRST_PERSON; + Index index = INDEX_FIRST; String command = " " + EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + NAME_DESC_BOB + " " + PHONE_DESC_BOB + " " + EMAIL_DESC_BOB + " " + ADDRESS_DESC_BOB + " " + TAG_DESC_HUSBAND + " "; Person editedPerson = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) @@ -76,7 +76,7 @@ public void edit() throws Exception { command = RedoCommand.COMMAND_WORD; expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; model.updatePerson( - getModel().getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()), editedPerson); + getModel().getFilteredPersonList().get(INDEX_FIRST.getZeroBased()), editedPerson); assertCommandSuccess(command, model, expectedResultMessage); /* Case: edit a person with new values same as existing values -> edited */ @@ -85,14 +85,14 @@ public void edit() throws Exception { assertCommandSuccess(command, index, BOB); /* Case: edit some fields -> edited */ - index = INDEX_FIRST_PERSON; + index = INDEX_FIRST; command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TAG_DESC_FRIEND; Person personToEdit = getModel().getFilteredPersonList().get(index.getZeroBased()); editedPerson = new PersonBuilder(personToEdit).withTags(VALID_TAG_FRIEND).build(); assertCommandSuccess(command, index, editedPerson); /* Case: clear tags -> cleared */ - index = INDEX_FIRST_PERSON; + index = INDEX_FIRST; command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + PREFIX_TAG.getPrefix(); editedPerson = new PersonBuilder(personToEdit).withTags().build(); assertCommandSuccess(command, index, editedPerson); @@ -101,7 +101,7 @@ public void edit() throws Exception { /* Case: filtered person list, edit index within bounds of address book and person list -> edited */ showPersonsWithName(KEYWORD_MATCHING_MEIER); - index = INDEX_FIRST_PERSON; + index = INDEX_FIRST; assertTrue(index.getZeroBased() < getModel().getFilteredPersonList().size()); command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + NAME_DESC_BOB; personToEdit = getModel().getFilteredPersonList().get(index.getZeroBased()); @@ -122,7 +122,7 @@ public void edit() throws Exception { * browser url changes */ showAllPersons(); - index = INDEX_FIRST_PERSON; + index = INDEX_FIRST; selectPerson(index); command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + TAG_DESC_FRIEND; @@ -150,33 +150,33 @@ public void edit() throws Exception { String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); /* Case: missing all fields -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased(), + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased(), EditCommand.MESSAGE_NOT_EDITED); /* Case: invalid name -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_NAME_DESC, + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + INVALID_NAME_DESC, Name.MESSAGE_NAME_CONSTRAINTS); /* Case: invalid phone -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_PHONE_DESC, + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + INVALID_PHONE_DESC, Phone.MESSAGE_PHONE_CONSTRAINTS); /* Case: invalid email -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_EMAIL_DESC, + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + INVALID_EMAIL_DESC, Email.MESSAGE_EMAIL_CONSTRAINTS); /* Case: invalid address -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_ADDRESS_DESC, + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + INVALID_ADDRESS_DESC, Address.MESSAGE_ADDRESS_CONSTRAINTS); /* Case: invalid tag -> rejected */ - assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_TAG_DESC, - Tag.MESSAGE_TAG_CONSTRAINTS); + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + INVALID_TAG_DESC, + Tag.MESSAGE_TAG_NAME_CONSTRAINTS); /* Case: edit a person with new values same as another person's values -> rejected */ executeCommand(PersonUtil.getAddCommand(BOB)); assertTrue(getModel().getAddressBook().getPersonList().contains(BOB)); - index = INDEX_FIRST_PERSON; + index = INDEX_FIRST; assertFalse(getModel().getFilteredPersonList().get(index.getZeroBased()).equals(BOB)); command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_HUSBAND; diff --git a/src/test/java/systemtests/HelpCommandSystemTest.java b/src/test/java/systemtests/HelpCommandSystemTest.java index 1aa4a5f294f4..7836ae35c0bf 100644 --- a/src/test/java/systemtests/HelpCommandSystemTest.java +++ b/src/test/java/systemtests/HelpCommandSystemTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.ui.testutil.GuiTestAssert.assertListMatching; import org.junit.Test; @@ -14,7 +14,6 @@ import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.SelectCommand; -import seedu.address.ui.BrowserPanel; import seedu.address.ui.StatusBarFooter; /** @@ -43,9 +42,9 @@ public void openHelpWindow() { getMainMenu().openHelpWindowUsingAccelerator(); assertHelpWindowOpen(); - getBrowserPanel().click(); + getCalendarPanel().click(); getMainMenu().openHelpWindowUsingAccelerator(); - assertHelpWindowNotOpen(); + assertHelpWindowOpen(); //use menu button getMainMenu().openHelpWindowUsingMenu(); @@ -60,16 +59,15 @@ public void openHelpWindow() { getMainWindowHandle().focus(); // assert that while the help window is open the UI updates correctly for a command execution - executeCommand(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + executeCommand(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); assertEquals("", getCommandBox().getInput()); assertCommandBoxShowsDefaultStyle(); assertNotEquals(HelpCommand.SHOWING_HELP_MESSAGE, getResultDisplay().getText()); - assertNotEquals(BrowserPanel.DEFAULT_PAGE, getBrowserPanel().getLoadedUrl()); assertListMatching(getPersonListPanel(), getModel().getFilteredPersonList()); // assert that the status bar too is updated correctly while the help window is open // note: the select command tested above does not update the status bar - executeCommand(DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + executeCommand(DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); assertNotEquals(StatusBarFooter.SYNC_STATUS_INITIAL, getStatusBarFooter().getSyncStatus()); } diff --git a/src/test/java/systemtests/SelectCommandSystemTest.java b/src/test/java/systemtests/SelectCommandSystemTest.java index c7deb73454b1..b4ca11b65193 100644 --- a/src/test/java/systemtests/SelectCommandSystemTest.java +++ b/src/test/java/systemtests/SelectCommandSystemTest.java @@ -5,7 +5,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; import static seedu.address.logic.commands.SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; import static seedu.address.testutil.TypicalPersons.getTypicalPersons; @@ -25,8 +25,8 @@ public void select() { /* Case: select the first card in the person list, command with leading spaces and trailing spaces * -> selected */ - String command = " " + SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + " "; - assertCommandSuccess(command, INDEX_FIRST_PERSON); + String command = " " + SelectCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased() + " "; + assertCommandSuccess(command, INDEX_FIRST); /* Case: select the last card in the person list -> selected */ Index personCount = Index.fromOneBased(getTypicalPersons().size()); @@ -93,7 +93,7 @@ public void select() { /* Case: select from empty address book -> rejected */ deleteAllPersons(); - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased(), + assertCommandFailure(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased(), MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); }