diff --git a/.gitignore b/.gitignore index 45a20de82e87..7833ad31ee1c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ lib/* *.iml *.log *.log.* -*.csv config.json src/test/data/sandbox/ preferences.json @@ -14,3 +13,6 @@ preferences.json classes/ /data/ /bin/ +gradle.prefs +org.eclipse.jdt.core.prefs +data.txt diff --git a/.project b/.project index 1c9339c5f927..afd7cdc78703 100644 --- a/.project +++ b/.project @@ -1,7 +1,7 @@ - addressbook-level4 - Project addressbook-level4 created by Buildship. + Mastermind + this is a to do task manager. diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 4e0fc71ac89f..928a3d0337fd 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -5,7 +5,7 @@ connection.gradle.user.home=null connection.java.home=null connection.jvm.arguments= connection.project.dir= -derived.resources=.gradle,build +derived.resources=.gradle.build eclipse.preferences.version=1 natures=org.eclipse.jdt.core.javanature -project.path=\: +project.path=\: \ No newline at end of file diff --git a/README.md b/README.md index 249a00b3899c..001b94820277 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,14 @@ -[![Build Status](https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master)](https://travis-ci.org/se-edu/addressbook-level4) -[![Coverage Status](https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master)](https://coveralls.io/github/se-edu/addressbook-level4?branch=master) +[![Build Status](https://travis-ci.org/CS2103AUG2016-W11-C3/main.svg?branch=master)](https://travis-ci.org/CS2103AUG2016-W11-C3/main/builds) +[![Coverage Status](https://coveralls.io/repos/github/CS2103AUG2016-W11-C3/main/badge.svg?branch=master)](https://coveralls.io/github/CS2103AUG2016-W11-C3/main?branch=master) -# Address Book (Level 4) +# Mastermind -
+
-* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using +* This is a desktop task manager application called Mastermind. It has a GUI but most of the user interactions is through 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 [level 3](https://github.com/se-edu/addressbook-level3): - * 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. - +* Mastermind is a powerful task manager that allow users to manage tasks anywhere and get things done. +* Mastermind is catered towards users that spend most of their time on the computer and prefer to use keyboard over mouse as inputs. #### Site Map * [User Guide](docs/UserGuide.md) @@ -29,6 +22,7 @@ * Some parts of this sample application were inspired by the excellent [Java FX tutorial](http://code.makery.ch/library/javafx-8-tutorial/) by *Marco Jakob*. +* This application is inspired from the [sample projects](https://github.com/se-edu/) created by SE_EDU initiatives #### Licence : [MIT](LICENSE) diff --git a/build.gradle b/build.gradle index 46b06c1e42ec..f746624cda73 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,11 @@ allprojects { compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDataTypeVersion" compile "com.google.guava:guava:$guavaVersion" + compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' + compile 'org.ocpsoft.prettytime:prettytime-nlp:4.0.0.Final' + compile 'net.sf.biweekly:biweekly:0.6.0' + compile group: 'org.apache.commons', name: 'commons-csv', version: '1.1' + testCompile "junit:junit:$junitVersion" testCompile "org.testfx:testfx-core:$testFxVersion" diff --git a/collated/docs/A0124797R.md b/collated/docs/A0124797R.md new file mode 100644 index 000000000000..1eb6718bc334 --- /dev/null +++ b/collated/docs/A0124797R.md @@ -0,0 +1,210 @@ +# A0124797R +###### /UserGuide.md +``` md + +### Finding tasks that contain any keywords: `find` + +Even with the list category feature, scrolling through possibly thousands of tasks to find a specific one can be quite troublesome. Fret not! _Mastermind_ provides you with the `find` command to quickly search for a specific task your are looking for. + +`find` command displays a result of all tasks whose description contain any of the given keywords. + +_Format:_ + +```java +find ... +``` + +>* The search is case-insensitive. +>* The order of the keywords does not matter. +>* Tasks matching at least one keyword will be returned. + +_Examples:_ + +```java +// returns Dinner on 10/10/16 at 1900hrs (Date) +> find Dinner +``` +```java +// returns CS2010 PS10 on 11 Oct by 1000hrs (Assignment) +> find "cs2010 ps10" +``` +> using find will always switch back to the home tab. + +### Finding all tasks that contain specific tags: `findtag` + +You have tagged your tasks previously and now you want to know what tasks are in that category. +you can use `findtag` command to quickly search for tasks that are tagged with that specific tag. + +`findtag` command display result of all tasks whose tags contain the given keywords. + +_Format:_ + +```java +findtag ... +``` + +>* The search is case-sensitive. +>* The order of the keywords does not matter. +>* Tasks matching at least one keyword will be returned. + +_Examples:_ + +```java +// returns Dinner on 10/10/16 at 1900hrs (Date) +> findtag date +``` + + +### Show upcoming tasks : `upcoming` + +What if you have too many task to do and only want to know tasks that are going to be due. You can ask _Mastermind_ to list `upcoming` tasks! + +_Format:_ +```java +upcoming [tab_name] +``` + +> * Shows all floating tasks as well if there is no `[tab_name]` included. +> * Does not show tasks that are already due. + +_Examples:_ +```java +// list all tasks that are due within a weeks time. +> upcoming +``` +```java +// list all deadlines that are due within a weeks time. +> upcoming deadlines +``` + + + +### Changing save location : `relocate` + +_Mastermind_ allows you to relocate the save file to a new destination folder. + +_Format:_ +``` +relocate +``` + +_Example:_ +```java +// save file has been relocated to new destination folder at ~/document/mastermind +> relocate ~/document/mastermind +``` + +> You can relocate into a cloud shared folder to access your save data from any other computer! +> +> Remember to input a folder and not a file! + +### Importing file : `import` + +_Mastermind_ allows you to import file from other to do list into _Mastermind_. + +Currently _Mastermind_ supports _.csv_ and _.ics_ file. + +> _.csv_ must be compliant to Google Calendar [specification](https://support.google.com/calendar/answer/37118?hl=en). + +_Format:_ +``` +import from +``` + +_Example:_ +```java +// import success +> import from ~/document/mastermind/data.ics +``` + +### Exporting data: `export` + +_Mastermind_ can assist you in exporting your data to _.csv_ format too. The _.csv_ file is fully compatible with Google Calendar so you can use it to synchronize with your personal calendar. + +_Format:_ +```java +export [tasks] [deadlines] [events] [archives] to +``` + +_Example:_ +```java +// export all data to personal folder +> export to C:\Users\Jim\mastermind.csv +``` + +```java +// export only deadlines and events data to personal folder +> export events deadlines to C:\Users\Jim\mastermind.csv +``` +``` +###### /AboutUs.md +``` md +#### [Dylan Chew Zhi Jiang](https://github.com/zavfel)
+
+* Components in charge of: [Logic](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#logic-component) +* Aspects/tools in charge of: Testing, Code quality +* Features implemented: + * [List](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#listing-all-tasks-of-a-category-list) + * [Mark](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#completing-tasks--mark) + * [Unmark](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#unmarks-tasks--unmark) + * [Previous](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#repeating-a-previous-command-) + * [Upcoming](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#show-upcoming-tasks--upcoming) + * allow adding of recurring task in [Add](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adds-a-recurring-deadline) + + * [Import (csv)](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#importing-file--import) + +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0124797R.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0124797R.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0124797R.md)] +* Other major contributions: + * Did the initial refactoring from AddressBook to ToDoList + * Set up Travis and Coveralls + * Class Diagrams + +----- +``` +###### /DeveloperGuide.md +``` md +### Model component + + + +**API** : [`Model.java`](../src/main/java/harmony/mastermind/model/Model.java) + +The `Model`: +* Stores a `UserPref` object that represents the user's preferences +* Stores _Mastermind_'s data +* Exposes a `UnmodifiableObservableList` 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. + +##### Task & TaskBuilder + +The `Task.java` and `TaskBuilder.java` can be found in `harmony.mastermind.model.task` package. + +We identified three different types of Tasks (Floating, Deadlines, Events), which differ only by the existence of start date and end date in their respective attributes. While inheritance seems to be a viable solution, it's not necessary true that a floating task is interchangeable with deadline and event, or vice versa. Moreover, how can we prevent the user from entering a start date without end date? + +Eventually, we resolved this issue by using the [Builder Pattern](https://www.tutorialspoint.com/design_pattern/builder_pattern.htm). This gives us the following benefits: + +- **Avoid long constructor**: `Task` object has many optional attributes, therefore it's impractical to keep multiple constructors with long parameter list. Builder pattern mitigates such problem by using step by step approach to build the `Task` object. + +- **Increase Readability**: The object creation process uses fluent method name that is much more readable than conventional constructor approach. + +- **Enforce mandatory attributes**: The only way to set the start and end date through `TaskBuilder` is by using `asEvent()`, `asDeadline()`, `asFloating()` methods. Ideally, the `TaskBuilder` should return an immutable `ReadOnlyTask` upon building. There's no way that the user can create a `Task` with start date but without an end date. + +_Example:_ +```java +TaskBuilder taskBuilder = new TaskBuilder(taskName); +taskBuilder.withCreationDate(createdDate); +taskBuilder.withTags(tags); +taskBuilder.asRecurring(recur); + +if (isEvent()){ + taskBuilder.asEvent(startDate,endDate); +} else if (isDeadline()){ + taskBuilder.asDeadline(endDate); +} else if (isFloating()){ + taskBuilder.asFloating(); +} + +Task task = taskBuilder.build(); +``` + +``` diff --git a/collated/docs/A0138862W.md b/collated/docs/A0138862W.md new file mode 100644 index 000000000000..182dd91368bf --- /dev/null +++ b/collated/docs/A0138862W.md @@ -0,0 +1,368 @@ +# A0138862W +###### /UserGuide.md +``` md + +### Completing tasks : `mark` + +Mission accomplished! Now you can ask _Mastermind_ to `mark` a task as completed. + +_Mastermind_ will archive the task for you. See Archives tab to review your completed task. + +_Format:_ +```java +mark +``` +```java +mark due +``` + +> ```mark``` only affects tasks that are not complete yet. It has no effect on completed tasks. + +> Using `mark due` it will mark all tasks that are due. + +_Examples:_ +```java +// use "find" command to look for a specific task +> find CS2010 + +// select the "find" result and mark the task at index 1 as completed +> mark 1 +``` + +```java +// marks all due tasks in the tab +> mark due +``` +### Unmarking a task : `unmark` + +Oh no! You realise that one of your task is not complete yet but you have marked it. Not to worry, you can `unmark` that task. + +_Format:_ +```java +unmark +``` + +> ```unmark``` only affects task that are completed. It has no effect on an uncompleted task. + +_Examples:_ +```java +// list all the tasks that are completed +> list archives + +// mark task at index 1 as completed +> unmark 1 +``` + +```java +// use "find" command to look for a specific task +> find CS2010 + +// select the "find" result and mark the task at index 1 as completed +> unmark 1 +``` + + +### Deleting a task : `delete` + +You just realize Taylor Swift concert is cancelled! Oh no! + +Sadly, you have to ask _Mastermind_ to remove the event from your bucket list. _Mastermind_ does the removal for you with a sympathetic pat on your shoulder. + +_Format:_ +```java +delete +``` + +>* Deletes the task at the specified `index`. +>* The index refers to the index number shown in the most recent listing. + +_Examples:_ +```java +// delete entry listed at index 1 +> delete 1 +``` + +### Undo a command : `undo` + +Suddenly, you received a call that Taylor Swift concert is coming back up! Maybe you should buy the ticket after all. + +Don't worry. _Mastermind_ has a built in time machine that helps you travel to the past! You can execute the `undo` command to recover the task you just deleted! + +_Format:_ +```java +undo +``` + +> `undo` only affect commands such as `add`, `edit`, `delete`, `mark`, `unmark`. It has no effect on command such as `list`, `find`, `relocate`, `import`, `export`, `history` and `clear`. + +Example: +```java +// deleted the task: "Buy ticket for Taylor Swift concert" +> delete 1 + +// Undo the last action +> undo + +// returns +// Undo successfully. +// =====Undo Details===== +// [Undo Delete Command] Task added: Buy ticket for Taylor Swift concert. Tags: +// ================== +``` + +> You can `undo` as many times as you like. +> +> However, the `undo` takes effect on commands that you entered in the current session only. _Mastermind_ will forget the `undo` history once you close the application. + +### Redo a command : `redo` + +_Mastermind_ can travel back to the present too! If you ever regret your `undo` command, _Mastermind_ can `redo` command that you just undid. + +_Format:_ +```java +redo +``` +> `redo` is only available immediately after an `undo` command is used. + +_Example:_ +```java +// Redo the last command being undone. Refer to undo command. +> redo + +// returns +// Redo successfully. +// =====Undo Details===== +// [Redo Delete Command] Task delete: Buy ticket for Taylor Swift concert. Tags: +// ================== +``` + +> You can `redo` as many times as you like. +> +> However, the `redo` takes effect on commands that you entered in the current session only. _Mastermind_ will forget the `redo` history once you close the application. +> +> Upon executing a new command (except `undo` and `redo`), _Mastermind_ will forget any existing command remaining in the `redo` history. + + + +### Listing all tasks of a category: `list` + +After adding some tasks into _Mastermind_, you can `list` them according to their category. In addition to the [three main categories](#adding-a-task-add-do) mentioned in `add` command, _Mastermind_ also keeps a summarized view under Home tab; the Archives tab is reserved for task marked as completed. + +_Format:_ +```java +list [tab_name] +``` + +> Possible values of `tab_name` includes (case-insensitive): +> `Home` +> `Tasks` +> `Events` +> `Deadlines` +> `Archives` +> +> _Quick Tip: You can also press Ctrl + 1, 2, 3, 4 or 5 to switch to the respective tabs._ + +``` +###### /AboutUs.md +``` md +#### [Wong Kang Fei](https://github.com/kfwong) +
+* Components in charge of: [UI](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#ui-component) +* Aspects/tools in charge of: Git, Travis, UI, Logic +* Features implemented: + * [Undo](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#undo-a-command--undo) + * [Redo](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#redo-a-command--redo) + * [Add](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adding-a-task-add-do) + + * [Import (ics)](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#importing-file--import) + + * [Export (csv)](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adding-a-task-add-do) + + * [History](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#recall-your-action-history-history) + +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0138862W.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0138862W.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0138862W.md)] +* Other major contributions: + * Set up Travis and Coveralls + * Git expert + * UI Revamp + * Action history + +----- +``` +###### /DeveloperGuide.md +``` md +#### Logic component +`Logic` is the brain of the application as it controls and manages the overall flow of the application. Upon receiving the user input from `UI`, it will process the input using the `Parser` and return the result of executing the user input back to the `UI`. The inputs `Logic` takes in are command words such as `add`, `edit`, `delete`, etc. `Logic` will then execute them accordingly based on their functionality. If you were to work on this execution of user input, you will need to access `Storage` through the `EventsCenter` to retrieve and update the state of tasks. + + + +**API** : [`Logic.java`](../src/main/java/harmony/mastermind/logic/Logic.java) + +1. `Logic` uses the `Parser` class to parse the user command. +2. This results in a `Command` object which is executed by the `LogicManager`. +3. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `UI`. + +##### Parser +`Parser` class uses [Regular Expression](https://www.tutorialspoint.com/java/java_regular_expressions.htm) (regex) to extract parameters from raw input. Each `Command` defines the regex in their respective class variable `COMMAND_ARGUMENTS_REGEX`. + +`parseCommand()` method registers all the recognized `Command` under `switch` statement. + +The regular expression can be tested at [regex101](https://regex101.com). + +You can visit the following link to inspect the regex test result for `AddCommand` and `EditCommand`: + +- `AddCommand`: [https://regex101.com/r/M2A3tB/15](https://regex101.com/r/M2A3tB/15) + +- `EditCommand`: [https://regex101.com/r/VcdUmR/4](https://regex101.com/r/VcdUmR/4) + +##### Add +The `add` command allows the user to add tasks, deadlines or events to _Mastermind_. Tasks, deadlines and events are differentiated by which attribute is updated. +> * Floating tasks are tasks without start and end date. +> * Deadlines are tasks with an end date. +> * Events are tasks with both start and end date. +> +All tasks are stored as object attributes such as name, description, end date, start date and type. + +You can refer to Figure 4 and Figure 5 below and the next page for the sequence diagram of `add`. + + + +> Note how the `Model` simply raises a `TaskManagerChangedEvent` when _Mastermind_'s data is changed, instead of directly 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. + + + +> Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is how we reduce direct coupling between 2 components. + + +##### Edit +This command allows the users to update attributes of items they have already added in _Mastermind_. + +The user can update the task by choosing the index of the task they want to change. They will then choose the specific field, such as start date, that they want to change. + +However, the user can only update one item at a time. To update, the item being updated must be found, and removed from the `Storage`. After updating the attribute, the item is re-added back into `Storage`. If the update is successful, the details of the item will be shown to the user at the console output. Otherwise, an error message is generated. + +The details are shown in the following diagrams. + + + +2 events are raised during the execution of `Edit`. + + + + +##### Undo & Redo +The `undo` and `redo` commands allow user to recover from mistakes. The user can execute `undo` or `redo` command multiple times to the earliest entry. + +Note that _Mastermind_ does not support `undo` or `redo` for `import`, `export`, `list`, `upcoming` and `clear` commands. The undo and redo history will be cleared upon executing such commands. + +The `undo` and `redo` commands are session based, meaning that the history will be cleared when the user exit the application. + +[insert undo class diagram] + +The `undo` command makes use of [Strategy Pattern](http://www.tutorialspoint.com/design_pattern/strategy_pattern.htm) to manage the logic flow. All commands that support `undo` operation must implement the `Undoable` interface. Similarly, to support `redo` operation, the command should implement the `Redoable` interface. + +We chose this design pattern because different command has different `undo` behaviour during runtime. Furthermore, it's more intuitive and reasonable to tell the specialized command how to `undo` its own execution. + +It's also aligned with the [Open/Close Principle](http://www.oodesign.com/open-close-principle.html) since the `UndoCommand.java` is close for modification, but open for extension to anticipate more `undo` strategies in the future. + +[insert undo sequence diagram] + +We combine this design with the `Stack` data structure to make sure the commands `undo` in proper sequence. The `ModelManager` will manage this stack as `undoHistoryStack`. + +At the end of the command execution, the command should push itself to the `undoHistoryStack`. + +Similarly, `redo` command makes use of the Strategy Pattern as well. `ModelManager` keeps two different stacks to control the undo and redo command flow. + + +##### Mark +The `mark` command allows users to mark their tasks/deadlines/events as completed. This removes the task from the tasks/deadlines/events field, and moves it into the `Archives`. The `mark` command will not delete the task immediately. In the event that users want to unmark the task, users can do so by using the `unmark` Command. Additionally, User can specify `mark due` to bulk mark all tasks that are due. + + +##### Unmark +The `unmark` command allows users to unmark the tasks in the `Archives` tab. _Mastermind_ will add them back to their original position in their orginal tab. + +> This command will only work in the `Archives` tab. + + +##### Delete +This command allows the user to delete any task they have input into the program beforehand. Users have to specify the index to delete after the command word. + +Details are illustrated in the following diagrams. + + + +> Again, note how that `Model` simply raises an event instead of relying on `Storage` directly. + +And `EventsCenter` reacts to the event accordingly. + + + + +##### Clear +`Clear` wipes all tasks(floating tasks, deadlines and events) currently registered in _Mastermind_. + +After inputting the command, the data is cleared from the `Storage` by raising an event to the `EventCenter`. + +> Undo and redo histories are also cleared from _Mastermind_. + + +##### Find +To find an item by name, the user will search through the `Storage` by calling "find ``". It calls `FindCommand` to find the exact terms of the keywords entered by the user. + +##### Findtag +To find an item by tag, the user will search through the `Storage` by calling "findtag ``". It calls `FindTagCommand` to find the exact terms of the keywords entered by the user. + +##### List +Calling `list [tab_name]` will display the items in that category. + + +##### Upcoming +Calling `upcoming [task_type]` will updates the list to show the user task in the upcoming week. If `task_type` is indicated, it will show only tasks of that specified type. + + +##### Relocate +This command allows the user to specify the save location of the data file. The file path the user inputs is contained in an event and passed to the `Storage` component from the `Logic` component via the `EventsCenter`. `Relocate` relies on `FileUtil` to check whether the input file path is valid and writable first. It then raises an event to the `EventsCenter` to inform `Storage` to handle the command. Hence there is no coupling from `Logic` to `Storage`. + + +##### Import +The `import` command allows user to migrate their existing task list from other application to _Mastermind_. It supports `.csv` and `.ics` format currently. This command should append the imported task lists in _Mastermind_ instead of overwriting it. + +Note that the `.csv` format must be compatible with [Google Calendar specification](https://support.google.com/calendar/answer/37118?hl=en) in order for _Mastermind_ succesfully parses the data. + +The `.ics` format must comply with the [iCalendar](http://icalendar.org/) format. Please refer to the [RFC5545](http://icalendar.org/RFC-Specifications/iCalendar-RFC-5545/) for specification details. + +The `import` command makes use of the following 3rd party libraries: +- [BiWeekly](https://github.com/mangstadt/biweekly) for parsing `.ics` format. +- [Apache Commons CSV](https://commons.apache.org/proper/commons-csv/) for parsing `.csv` format. + +##### Export +The `export` command allows user to export their task list from _Mastermind_ to `.csv` format. The exported format should comply with [Google Calendar specification](https://support.google.com/calendar/answer/37118?hl=en). + +Due to the limitation of Google Calendar, it's mandatory to specify a valid start date and end date. In order to export floating and deadline tasks, we have made the following decisions: + +- Floating Task: start & end date are set to exporting date; `All Day Event` field set to `TRUE`. +- Deadline Task: start & end date share the same date; `All Day Event` field set to `FALSE`. +- Event Task: start & end date are exported directly from _Mastermind_; `All Day Event` field set to`FALSE`. + +In addition, the tags defined in _Mastermind_ will be exported in `Description` field. + +##### History +This command allows users to toggle the Action History bar via the command line. The command will raise an event which will be handled by `UI`. + + +##### Help +Calling `help` will show a popup containing a command summary. To close the popup, press any key. + +> An event will be raised in `Logic`, which will be handled by the `UI`. + + + +##### Exit +This `exit` command runs when the user tries to exit the program, allowing the program to close. + +> The undo and redo history will not be kept. + +``` diff --git a/collated/docs/A0139194X.md b/collated/docs/A0139194X.md new file mode 100644 index 000000000000..b59b83670558 --- /dev/null +++ b/collated/docs/A0139194X.md @@ -0,0 +1,188 @@ +# A0139194X +###### /ContactUs.md +``` md +# Contact Us + +* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/CS2103AUG2016-W11-C3/main/issues) + if you noticed bugs or have suggestions on how to improve. + +* **Contributing** : We welcome pull requests. Follow the process described [here](https://github.com/oss-generic/process) + +* **Email us** : You can also reach us at `teamharmonyw11c3 [at] gmail.com` +``` +###### /UserGuide.md +``` md + +### Recall your action history: `history` + +_Mastermind_ remembers the list of commands you executed. If you ever forget what you've executed hours ago or are unsure of what you can `undo` or `redo`, you can ask _Mastermind_ to show you the history. + +_Format:_ +```java +history +``` + +_Example:_ +```java +> history +``` + +> * _Quick Tip: You can also click on the status bar above the command field to open up your action history._ +> * Clicking on the history entry will display the result of that particular command you executed on the right panel. +> * The command will toggle the status bar open or close. + + + +### Clearing all entries: `clear` + +Want to clear everything? _Mastermind_ can do some spring cleaning for you in all categories. + +_Format:_ +```java +clear +``` + +_Example:_ +```java +// All tasks in all categories are deleted +> clear +``` +> `clear` command cannot be undone. Make sure you really mean to discard everything on _Mastermind_! + + +### Exiting the program : `exit` + +_Mastermind_ says, "Goodbye!" + +Exits the application. + +> Worried that you have not saved your file? take a look [here](#faq) + +_Format:_ +``` +exit +``` + +_Example:_ +```java +// Mastermind says, "Goodbye!" +> exit +``` + + + +## Special Features + +### Autocomplete +_Mastermind_ is smart. While typing into _Mastermind_, it will prompt you on commands that contains letters you input. You can press Enter to complete the sentence. +_Mastermind_ will also learn the inputs you type for easier typing in the future. You can press Esc to stop the instance of Autocomplete. + + +### Repeating a previous command: + +Lazy to retype a similar command? Want to paste the previous command back to the field? +_Mastermind_ can do just that! + +> You can go back as many previous command as you want. +> Similarly, you can press to get the next command. + +_Format:_ + (Up arrow key) + + +## FAQ + +**Q**: How do I transfer my data to another Computer? +**A**: Install the application in the other computer and overwrite the empty mastermind.xml file it creates with the mastermind.xml file that contains the data of your previous Mastermind. +Alternatively you can use the export and import function. However if you use this method, the tags will not be added to the new application. + +**Q**: Is my data secure? +**A**: Your data is stored locally on your hard drive as a .xml file. Your data is as secure as your computer + +**Q**: Where is the save button or command? +**A**: Mastermind's data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. + + +## Command Summary + +Command | Format +-------- | :-------- +[Help](#viewing-help--help) | `help` +[Add, Do](#adding-a-task-add-do) | (add|do) from [start_date] to [end_date] [daily|weekly|monthly|yearly] [#comma_separated_tags] +[Edit, Update, Change](#editing-a-task--edit-update-change) | (edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #] +[Mark](#completing-tasks--mark) | mark +[Unmark](#unmarking-a-task--unmark) | `unmark ` +[Delete](#deleting-a-task--delete) | `delete ` +[Undo](#undo-a-command--undo) | `undo` +[Redo](#redo-a-command--redo) | `redo` +[List](#listing-all-tasks-of-a-category-list) | `list [tab_name]` +[Find](#finding-tasks-that-contain-any-keywords-find) | `find ...` +[Find tag](#finding-all-tasks-that-contain-specific-tags-findtag) | `findtag ...` +[Upcoming](#show-upcoming-tasks--upcoming) | `upcoming [tab_name]` +[Relocate](#changing-save-location--relocate) | `relocate ` +[Import](#importing-file--import-from) | `import from ` +[Export](#exporting-data-export) | `export [tasks] [deadlines] [events] [archives] to ` +[History](#recall-your-action-history-history) | `history` +[Clear](#clearing-all-entries-clear) | `clear` +[Exit](#exiting-the-program--exit) | `exit` + + +## Special Feature Summary + +Command | Format +-------- | :-------- +[Autocomplete](#autocomplete) | Enter +[Previous](#repeating-a-previous-command-↑) | +``` +###### /AboutUs.md +``` md +#### [Albert Yeoh Ji Bin](https://github.com/bertojo) +
+* Components in charge of: [Data](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#storage-component) +* Aspects/tools in charge of: Data, Integration, Documentation, Deliverables +* Features implemented: + * [Relocate Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#changing-save-location--relocate) + * [Help Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#viewing-help--help) + * [Clear Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#clearing-all-entries-clear) +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0139194X.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0139194X.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0139194X.md)] +* Other major contributions: + * Documentation + * Proof reading + * Issue tracking + * Sequence Diagrams + +----- + +# Contributors + +We welcome contributions. See [Contact Us](ContactUs.md) page for more info. + +* [Damith C. Rajapakse](http://www.comp.nus.edu.sg/~damithch/) +* [Joshua Lee](https://github.com/lejolly) +* [Leow YiJin](https://github.com/yijinl) +* [Martin Choo](https://github.com/m133225) +* [Thien Nguyen](https://github.com/ndt93) +* [You Liang](https://github.com/yl-coder) +* [Akshay Narayan](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Aokkhoy) +* [Sam Yong](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Amauris) +``` +###### /DeveloperGuide.md +``` md +### Storage component + + + +**API** : [`Storage.java`](../src/main/java/harmony/mastermind/storage/Storage.java) + +The `Storage` component: +* can save `UserPref` objects in json format and read it back. +* can save the _Mastermind_'s data in xml format and read it back. + +`Storage` is completely decoupled from the other components. It functions by taking events from the `EventsCenter`. + +### Common classes + +Classes used by multiple components are in the `harmony.mastermind.commons` package. +This component will be maintained by developers working on any of the other components because of its wide scope of application. You can find 4 packages, namely: `core`, `events`, `exceptions` and `utils`. + +``` diff --git a/collated/docs/A0143378Y.md b/collated/docs/A0143378Y.md new file mode 100644 index 000000000000..12351d1a833b --- /dev/null +++ b/collated/docs/A0143378Y.md @@ -0,0 +1,1207 @@ +# A0143378Y +###### /UserGuide.md +``` md +# User Guide + +* [Quick Start](#quick-start) +* [Features](#features) +* [Special Features](#special-features) +* [FAQ](#faq) +* [Command Summary](#command-summary) +* [Special Command Summary](#special-command-summary) + + +## Quick Start + +1. Ensure you have Java version `1.8.0_60` or later installed in your Computer. + +2. Download the latest `mastermind.jar` from our repository's [releases](https://github.com/CS2103AUG2016-W11-C3/main/releases) tab. +3. Copy the file to the folder you want to use as the home folder for your Mastermind. +4. Double-click the file to start the app. The app should appear in a few seconds. + > + +5. Type the command in the command box and press Enter to execute it. + + + > Some example commands you can try: + > + > - `add CS2103T tutorial by tomorrow 11am` + > - adds a task named `CS2103T tutorial` with deadline due tomorrow at 11am. + > - `delete 1` + > - deletes the 1st task shown in the current list. + + +6. Refer to the [Features](#features) section below for details of each command. + + +## Features + +### Command Formatting Style + +>* Words in `angle brackets < >` are required parameters, fields in `square brackets [ ]` are optional. +>* Fields that are appended with `ellipsis ...` such as `...` indicates that the field accept multiple of values. +>* Tasks will be added to the categories (event, deadlines, floating task) according to the keywords to the parameters inputed. +>* Parameters can be in any order. +>* Separate different tags with `comma ,`. +>* There are no limit to the number of tags a task can have. + +### Viewing help : `help` + +First, let's get familiar with the command features that _Mastermind_ offers! Type `help` and press Enter to display all the possible command usage. + +_Format:_ +```java +help +``` + +> _Quick Tip: Press any key to close the popup!_ + +### Adding a task: `add`, `do` + +For now, an empty list is not very interesting. Try to create a new task using `add` or `do` command. Both commands are equivalent. They exist to help you in constructing a more fluent command structure. + +_Mastermind_ automatically helps you to organize your task into three main categories: +- **Event**: Task with `startDate` and `endDate` specified. +- **Deadline**: Task with only `endDate` specified. +- **Floating Task**: Task without `startDate` and `endDate`. + +> _Quick Tip: Tag your task so you can find them easier with `find` command!_ +> - You can have multiple tags to your tasks! Simply just separate the different tags using a comma without a space. +> - There are no limit to the number of tags a task can have, feel free to tag as many as you like! + +#### Adds an event +_Format:_ +```java +(add|do) from to [#comma_separated_tags] +``` + +_Example:_ +```java +> add attend workshop from today 7pm to next monday 1pm #programming,java +``` +#### Adds a task with deadline +_Format:_ + +```java +(add|do) by [#comma_separated_tags] +``` + +_Example:_ + +```java +> add submit homework by next sunday 11pm #math,physics +``` +#### Adds a floating task +_Format:_ +```java +(add|do) [#comma_separated_tags] +``` + +_Example:_ +```java +> do chores #cleaning +``` + +#### Adds a recurring deadline +_Format:_ +```java +(add|do) by [daily|weekly|monthly|yearly] [#comma_separated_tags] +``` + +_Example:_ +```java +> do chores today daily #cleaning +> do workshop from tomorrow 3pm to tomorrow 5pm weekly +``` + +> _Quick Tip: You can add recurring events too!_ +> Use keywords like ```'daily', 'weekly', 'monthly' and 'yearly'``` for recurring tasks. + +> _Mastermind_ uses [natural language processing](http://www.ocpsoft.org/prettytime/nlp/) for `` & ``, therefore it does not enforce any specific date format. Below are the possible list of date constructs that _Mastermind_ accepts: +> +> `tomorrow 6pm` +> `next saturday` +> `13 Oct 2016 6pm` +> `...` +> +> The list above is not exhaustive. Feel free to try any possible combination you have in mind! +> +> If the date you entered is incorrectly parsed, please enter a more explicit combination such as "tomorrow 6pm" instead of "tomorrow at 6". + + +### Editing a task : `edit`, `update`, `change` +Perhaps now you have a change of schedule, or you are unsatisfied with the task name you just created. _Mastermind_ allows you to quickly modify the task you created before using `edit` command. + +_Format:_ +```java +(edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #,] +``` + +>* At least one optional field is required. +>* You can edit as many fields as required. +>* Each field is separated by a comma. +>* You can omit the last `comma ,` if there's no more fields following: +>`edit 1 name to dinner with parents, start date to tomorrow 7pm` +>* Any malformed field will be dropped. + +_Examples:_ + +```java +// Selects the 2nd task in Mastermind and edit the task name to Dinner. +> edit 2 name to parents with dinner, end date to tomorrow 7pm, recur daily, tags to #meal,family +``` +```java +// Selects the 1st task and edit the `startDate` to tomorrow 8pm. +> change 1 start date to tomorrow 8pm +``` +``` +###### /AboutUs.md +``` md +#### [Lim Hui Qi](https://github.com/LuMiN0uSaRc) +
+* Components in charge of: [Memory](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#model-component) +* Aspects/tools in charge of: UI, Scheduling and deadlines, Tracking +* Features implemented: + * [Edit](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#editing-a-task--edit) + * [Find](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#finding-all-tasks-containing-any-keyword-in-their-description-find) +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0143378Y.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0143378Y.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0143378Y.md)] +* Other major contributions: + * Documentation + * Smart learning text field + * Autocomplete + * Issue tracking + * Sorting based on event date and task within Memory + * History for Memory + +----- +``` +###### /DeveloperGuide.md +``` md +# Developer Guide + +* [Introduction](#introduction) +* [Target Audience](#target-audience) +* [Setting Up](#setting-up) +* [Design](#design) +* [Implementation](#implementation) +* [Testing](#testing) +* [Continuous Integration](#continuous-integration) +* [Making a Release](#making-a-release) +* [Managing Dependencies](#managing-dependencies) +* [Appendix A: User Stories](#appendix-a--user-stories) +* [Appendix B: Use Cases](#appendix-b--use-cases) +* [Appendix C: Non Functional Requirements](#appendix-c--non-functional-requirements) +* [Appendix D: Glossary](#appendix-d--glossary) +* [Appendix E: Product Survey](#appendix-e--product-survey) + +## Introduction +Welcome to _Mastermind_'s Developer Guide! + +_Mastermind_ is a to-do task manager that helps user keep track of their tasks. We aim to provide our users with an effective and efficient solution to managing their tasks so they are able to better handle their time, as well as completing tasks required. + +This developer guide is for both existing and new developers of the team who are interested in working on _Mastermind_ in the future. + +This guide will show you the Product Architecture, APIs and the details regarding the different components. + +The next segment will show you how to set-up to make sure that you have the necessary tools before getting started. Feel free to approach our team for any clarifications that you may face during the process. Good luck and have fun coding! + + +## Setting up + +#### Prerequisites + +1. **JDK `1.8.0_60`** or later. + > Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + +2. **Eclipse** IDE. +3. **e(fx)clipse** plugin for Eclipse (Follow from step 2 onwards given in + [this page](http://www.eclipse.org/efxclipse/install.html#for-the-ambitious)). +4. **Buildship Gradle Integration** plugin from the Eclipse Marketplace. + + +#### Importing the project into Eclipse + +1. Fork this repo, and clone the fork to your computer. +2. Open Eclipse (Note: Ensure you have installed the **e(fx)clipse** and **buildship** plugins as given in the prerequisites above). +3. Click `File` > `Import`. +4. Click `Gradle` > `Gradle Project` > `Next` > `Next`. +5. Click `Browse`, then locate the project's directory. +6. Click `Finish`. + + > * If you are asked whether to 'keep' or 'overwrite' config files, choose to 'keep'. + > * Depending on your connection speed and server load, it can even take up to 30 minutes for the set up to finish + (This is because Gradle downloads library files from servers during the project set up process). + > * If Eclipse auto-changed any settings files during the import process, you can discard those changes. + +### Troubleshooting Project Setup +1. **Eclipse reports compile errors after new commits are pulled from Git:** + + >Reason: Eclipse fails to recognize new files that appeared due to the Git pull. + > + >Solution: Refresh the project in Eclipse. Right click on the project (in Eclipse package explorer), choose Gradle -> Refresh Gradle Project. + +2. **Eclipse reports some required libraries missing:** + + >Reason: Required libraries may not have been downloaded during the project import. + > + >Solution: Run tests using Gradle once (to refresh the libraries). + + + +## Design + +### Software Architecture + +To start off, let us introduce you to the overall structure of _Mastermind_. Do have a basic understanding of _Mastermind_'s different components before focusing on them individually. + +_Mastermind_ is split up into 5 main components, namely the `UI`, `Logic`, `Model`, `Storage` and `Commons`, as shown below, in Figure 1. + + + +The **_Architecture Diagram_** given above explains the high-level design of the App. +Given below is a quick overview of each component. + +**`Main`** has only one class called [`MainApp`](../src/main/java/harmony/mastermind/MainApp.java). It is responsible for: +* At app launch: Initializes the components in the correct sequence, and connect them up with each other. +* At shut down: Shuts down the components and invoke clean up method where necessary. + +[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +Two of those classes play an important role at the architecture level. +* `EventsCenter` : This class (written using [Google's Event Bus library](https://github.com/google/guava/wiki/EventBusExplained)) + is used by components to communicate with other components using events (i.e. a form of _Event Driven_ design). +* `LogsCenter` : Used by many classes to write log messages to the App's log files. + +The rest of the App consists four components. +* [**`UI`**](#ui-component) : The UI of the App. +* [**`Logic`**](#logic-component) : The command executor. +* [**`Model`**](#model-component) : Holds the data of the App in-memory. +* [**`Storage`**](#storage-component) : Reads data from, and writes data to, the hard disk. +* [**`Memory`**](#memory-component) : Reads data from, and writes data to, the hard disk. + +Each of the four components: +* Defines its _API_, which is an interface with the same name as the component. `Logic.java` +* Exposes its functionality using a `{Component Name}Manager` class e.g. `LogicManager.java` + + +The sections below give more details of each component. + +#### UI component + +`UI` is implemented by JavaFX 8 and consists of the main panel Main Window. This component primarily handles user input such as text input which will be entered via Command Line Input (CLI) as shown in Figure 2. On top of text input, users are also allowed to use keypress or mouse click. Inputs are passed on to the `Logic` component. + +If you are intending to work on the `UI`, you will need to update the application's internal state, which also includes: +1. UiManager.java +2. UiPartLoader.java +3. UiPart.java + + + + +**API** : [`Ui.java`](../src/main/java/harmony/mastermind/ui/Ui.java) + +The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](../src/main/java/harmony/mastermind/ui/MainWindow.java) is specified in [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml). + +The `UI` component: +* Executes user commands using the `Logic` component. +* Binds itself to data in the `Model` so that the UI can automatically update when data in the `Model` change. +* Responds to some of the events raised from various parts of the App and updates `UI` accordingly. + +[insert ui class diagram] + +We separated the UI into smaller components. Each components should handles the events raised by `EventCenter` if needed. + +- `MainWindow.java`: handles the Tab name updates, initialize smaller components. + - `CommandBox.java`: handles user input, listen to textfield change event. + - `ActionHistoryPane.java`: display command result. + - `ActionHistoryEntry.java`: renders each entry in the action history list. + - `DefaultTableView.java`: an abstract class that define default table rendering algorithms. It should be extended by the following classes: + - `HomeTableView.java`: display all tasks. + - `TasksTableView.java`: display only floating tasks. + - `EventsTableView.java`: display only events. + - `DeadlinesTableView.java`: display only deadlines. + - `ArchivesTableView.java`: display marked tasks. +- `HelpPopup.java`: display help window. + +``` +###### /DeveloperGuide.md +``` md +## Memory Component +This is the temporary memory where most changes regarding memory take place. This is before the information to be stored is transferred permanently in the storage. + + + +## Implementation + +### Logging + +We are using `java.util.logging.Logger` as our logger, and `LogsCenter` is used to manage the logging levels of loggers and handlers (for output of log messages). + +- The logging level can be controlled using the `logLevel` setting in the configuration file. + > See [Configuration](#configuration). +- The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. + +- Currently log messages are output through: `Console` and `.log`. + +**Logging Levels** + +- SEVERE + - Critical use case affected, which may possibly cause the termination of the application. + +- WARNING + - Can continue, but with caution. + +- INFO + - Information important for the application's purpose. + - e.g. update to local model/request sent to cloud + - Information that the layman user can understand. + +- FINE + - Used for superficial debugging purposes to pinpoint components that the fault/bug is likely to arise from. + - Should include more detailed information as compared to `INFO` i.e. log useful information! + - e.g. print the actual list instead of just its size. + +### Configuration + +Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file. _Mastermind_ will load the xml data file from the file path specified in `config.json`. +> Stored as: `config.json`. + + +## Testing + +**In Eclipse**: +> If you are not using a recent Eclipse version (i.e. _Neon_ or later), enable assertions in JUnit tests as described [here](http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option). + +* To run all tests, right-click on the `src/test/java` folder and choose + `Run as` > `JUnit Test`. +* To run a subset of tests, you can right-click on a test package, test class, or a test and choose to `Run as` a `JUnit Test`. + +**Using Gradle**: +* See [UsingGradle.md](UsingGradle.md) for how to run tests using Gradle. + +Tests can be found in the `./src/test/java` folder. + +1. **GUI Tests** - These are _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `guitests` package. + +2. **Non-GUI Tests** - These are tests not involving the GUI. They include, + 1. _Unit tests_ targeting the lowest level methods/classes. + e.g. `harmony.mastermind` + 2. _Integration tests_ that are checking the integration of multiple code units + (those code units are assumed to be working). + e.g. `harmony.mastermind.commons.core` + 3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how they are connected together. + e.g. `harmony.mastermind.storage` + +**Headless GUI Testing** : +Thanks to the ([TestFX](https://github.com/TestFX/TestFX)) library we use, our GUI tests can be run in the _headless_ mode. + In the headless mode, GUI tests do not show up on the screen. + That means the developer can do other things on the Computer while the tests are running. See [UsingGradle.md](UsingGradle.md#running-tests) to learn how to run tests in headless mode. + +## Dev Ops + +### Build Automation +See [UsingGradle.md](UsingGradle.md) to learn how to use Gradle for build automation. + +### Continuous Integration +We use [Travis CI](https://travis-ci.org/) to perform Continuous Integration on our projects to ensure that every time we merge a new feature into the main branch, automated testing is done to verify that the app is working. See [UsingTravis.md](UsingTravis.md) for more details. + +## Making a Release + +Here are the steps to create a new release. + + 1. Generate a JAR file [using Gradle](UsingGradle.md#creating-the-jar-file). + 2. Tag the repo with the version number. e.g. `v0.1` + 3. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/) and upload the JAR file you created. + +## Managing Dependencies + +A project often depends on third party libraries. For example, _Mastermind_ depends on the [Jackson library](http://wiki.fasterxml.com/JacksonHome) 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: +* Include those libraries in the repo (this bloats the repo size). +* Require developers to download those libraries manually (this creates extra work for developers). + +## Appendix A : User Stories + +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` + + +Priority | As a ... | I want to ... | So that I can... +-------- | :-------- | :--------- | :----------- +`* * *` | new user | see usage instructions | refer to instructions when I forget how to use the app +`* * *` | user | add a new task | so I can register my things to do +`* * *` | user | add a floating task | have a task without a deadline +`* * *` | user | add a recurring task | add repeating tasks only once +`* * *` | user | delete a task | remove entries that I no longer need +`* * *` | user | edit a task | update entries as needed +`* * *` | user | find a task by name | locate details of the task without having to go through the entire list +`* * *` | user | find a task by deadline | locate tasks that are due soon without having to go through the entire list +`* * *` | user | undo a task entered | undo a mistake easily +`* * *` | user | re-do a task entered | redo a mistake easily +`* * *` | user | sort list by alphabetical order and date | find my tasks easily +`* * *` | user | mark tasks as done | archive my completed tasks +`* * *` | user | specify the location of file storage | choose where to save the to do list +`* *` | user | see my tasks in user interface | have an easier time using the app +`* *` | user | show upcoming tasks | can see tasks that are nearing +`*` | user | specify my own natural language | customise the app +`*` | user | set categories | organise my tasks +`*` | user | block out timings | reserve time slots for tasks +`*` | user | create subtasks | breakdown my tasks into smaller problems +`*` | user | set reminders for tasks | reduce chances of forgetting to do a task +`*` | user | import a list of to do tasks | add in tasks without registering them individually +`*` | user | export a list of tasks | export to another computer + + + +## Appendix B : Use Cases + +(For all use cases below, the **System** is _Mastermind_ and the **Actor** is the _User_, unless specified otherwise) + +| # | Use Case | Descriptions | +|---|---|---| +| [UC1](#uc1-display-help) | Display Help | Display help when requested. | +| [UC2](#uc2-adddo-a-task) | Add/Do a Task | Adding a task. A task can be concrete (have defined date/time) or floating (without date/time). | +| [UC3](#uc3-edit-a-task) | Edit a Task | Edit the details of a single task. The command only update fields specified by the User. Unspecified field remains untouched. | +| [UC4](#uc4-mark-task-as-done) | Mark Task as done | Mark a task as done by index. A marked task should be automatically archived and exclude from display and search. | +| [UC5](#uc5-unmark-a-task) | Unmark a Task | Unmark a task as done by index. The Archived task will add back to the respective tabs.| +| [UC6](#uc6-delete-a-task) | Delete a task | Remove a task entry by index. | +| [UC7](#uc7-undo-action) | Undo Action | Undo last action performed. | +| [UC8](#uc8-redo-action) | Redo Action | Redo an action performed in UC7. | +| [UC9](#uc9-lists-tasks) | Lists Tasks | Display lists of tasks added into the System. | +| [UC10](#uc10-find-tasks) | Find Tasks | Search for task by name with keywords. | +| [UC11](#uc11-find-tasks-by-tag) | Find Tasks by tag | Search for task by tag with keywords. | +| [UC12](#uc12-upcoming-task) | Show upcoming Tasks | Display floating tasks and task that is due in a weeks time. | +| [UC13](#uc13-relocate-storage-location) | Relocate storage location | Change the current storage to other directory specified by the user. | +| [UC14](#uc14-importing-files-to-mastermind) | Import File | Adds tasks identified in file | +| [UC15](#uc15-exporting-files-to-.csv-or-.ical) | Export | Exports data as .csv or .ical | +| [UC16](#uc16-history) | History | Toggles the Action History Bar | +| [UC17](#uc17-clear-everything) | Clears everything | System performs bulk delete on the data (Deadlines, events, tasks). | +| [UC17](#uc17-exit-application) | Exit application | Quit the application | + +--- + +### UC1: Display help + +Display help when requested. + +##### Main Success Scenario: + +1. User requests to display help. + +2. System display the help popup. + +3. User presses any key to close the popup. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC2: Add/Do a Task + +Adding a task. A task can be concrete (have defined date/time), recurring (repeats with defined date/time) or floating (without date/time). + +##### Main Success Scenario: + +1. User requests to add a task. + +2. System accepts the command & parameters, creates the task and displays successful message to User. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +1b. User enters task with date/time. + +* 1b1. System accepts the command as concrete task. + +* 1b2. Use case resume at 2. + +1c. User enters task without date/time. + +* 1c1. System accepts the command as floating task. + +* 1c2. Use case resume at 2. + +1d. User enters task with start date after end date. + +* 1d1. System display error message. + +* 1d2. Use case ends. + +1e. User enters recurring task without date/time. + +* 1e1. System accepts the command as floating task. + +* 1e2. Use case resumes at 2. + +1f. User enters recurring task with date/time. + +* 1f1. System accepts the command as recurring task. + +* 1f2. Use case resume at 2. + + +--- + +### UC3: Edit a Task + +Edit the details of a single task. The command only update fields specified by the User. Unspecified field remains untouched. + +##### Main Success Scenario + +1. User request to edit a task by index. + +2. System find and update the task. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +--- + +### UC4: Mark Task as done + +Mark a task entry by index or keyword 'due'. + +##### Main Success Scenario + +1. User request to mark a task by index. + +2. System find and mark the task and remove from respective tab and add to archives tab. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +2b. User tries to mark a Task that is already marked. + +* 2b1. System check that task associated with the index is already marked. + +* 2b2. System display unsuccessful message. + +* 2b3. Use case ends. + +2c. User wants to mark all due tasks. + +* 2c1. System checks for all tasks that are due + +* 2c2. System removes all tasks that are due + +* 2c3. Use case continues to 3. + +--- + +### UC5: Unmark a Task + +Unmark a task entry by index. + +##### Main Success Scenario + +1. User request to unmark a task by index. + +2. System find and unmark the task and remove from archives tab and add to the respective tab. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +2b. User tries to Unmark a Task that not marked yet. + +* 2b1. System check that task associated with the index is not marked yet. + +* 2b2. System display unsuccessful message. + +* 2b3. Use case ends. + +--- + +### UC6: Delete a Task + +Remove a task entry by index. + +##### Main Success Scenario + +1. User request to delete a task by index. + +2. System find and remove the task. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +--- + +### UC7: Undo Action + +Undo last action performed. + +##### Main Success Scenario + +1. User requests to undo last action performed. + +2. System pop from Undo stack and performs undo on the last action performed. + +3. System put the action into Redo stack. + +3. System display successful message and details of the undo operation. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any previous action in Undo stack. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC8: Redo Action + +Redo an action performed in UC7. + +##### Main Success Scenario + +1. User requests to redo an action performed in UC7. + +2. System pop from Redo stack and performs redo on the last action performed in UC7. + +3. System display successful message and details of the redo operation. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any previous action in Redo stack. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC9: Lists Tasks + +Display lists of tasks added into the System. + +##### Main Success Scenario: + +1. User requests to lists tasks. + +2. System display list of tasks. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC10: Find Tasks by name + +Search for task by name. + +Note: The combination is filtered using **OR** operation. + + +##### Main Success Scenario: + +1. User request to search for a task by name. + +2. System Search for a task that matches the parameters. + +3. System displays the matching results to the User. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC11: Find Tasks by tags. + +Search for tasks by tags. + +Note: The combination is filtered using **OR** operation. + +##### Main Success Scenario: + +1. User request to search for a tasks by tags. + +2. System Search for a task that matches the parameters. + +3. System displays the matching results to the User. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC11: Upcoming Tasks + +Shows all floating tasks, deadlines and events that are due in the upcoming week. + +##### Main Success Scenario + +1. User requests to load upcoming command. + +2. System filter and display upcoming tasks. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + + +1b. User requests to show upcoming deadlines. + +* 1b1. System filters only deadlines. + +* 1b2. Use case resumes at 2. + +1c. User requests to show upcoming deadlines. + +* 1c1. System filters only deadlines. + +* 1c2. Use case resumes at 2. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + + + +--- + +### UC12: Relocate storage location + +Change the current storage to other directory specified by the user. + +##### Main Success Scenario + +1. User requests to relocate the storage directory. + +2. System changes the storage directory according to user input. + +3. System copies current storage to the new location. + +4. System deletes old file at old storage location. + +5. System displays successful message. + +6. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid storage location. + +* 2a1. System displays invalid path message. + +* 2a2. Use case ends. + +2b. Storage location is not accessible/writable. + +* 2b1. System displays unwrittable file message. + +* 2b2. Use case ends. + +--- + +### UC13: Importing files to Mastermind + +Import `.ics` or `.csv` file and add the relevant tasks into Mastermind. + +##### Main Success Scenario + +1. User requests to import file. + +2. System locate the file and attempt to read. + +3. System adds tasks identified from file into Mastermind. + +4. System displays successful message. + +5. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid file location. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +2b. Invalid file type. + +* 2b1. System display unsuccessful message. + +* 2b2. Use case ends. + +--- + +### UC14: Exporting files to .csv +Exports selected data to `.csv`. + +##### Main Success Scenario + + +1. User requests to export file. + +2. System locates the export location. + +3. System attempts to write tasks identified from Mastermind into file + +4. System displays successful message. + +5. Use case ends. + +##### Extensions +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid file location. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC15: History +Toggles the Action History Bar + +##### Main Success Scenario +1. User requests to toggle Action Histroy Bar + +2. Action History Bar toggled. + +3. Use case ends. + +##### Extensions +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC16: Clear everything + +System performs bulk delete on the data (Deadlines, events, tasks). +##### Main Success Scenario + +1. User requests to clear Mastermind + +2. System proceed to perform bulk action described in UC6 for the specified category. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC17: Exit application + +Quit the application. + +##### Main Success Scenario + +1. User requests to exit application. + +2. System perform synchronization and save it to the storage. + +3. System exit application. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +--- + +## Appendix C : Non Functional Requirements + +1. Should backup tasks list. +2. Should work as a stand alone. +3. Should be able to store 1000 tasks. +4. Should be maintainable and scalable. +5. Should not use relational databases. +6. Should be user friendly for new users. +7. Should be able to be accessed offline. +8. Should come with automated unit testing. +9. Should follow the Object-oriented paradigm. +10. Should work without requiring an installer. +11. Should be able to startup and quit within 1 second. +12. Should display up to date tasks when command is given. +13. Should store data locally and should be in a .xml file. +14. Should work well without any third party framework/library. +15. Should have a Command Line Interface as the primary mode of input. +16. Should be able to display tasks within 1 second when command is given. +17. Should be able to run on all [mainstream OS](#mainstream-os) for desktop. +18. Should have a simple GUI that displays [relevant information](#relevant-information). + + +## Appendix D : Glossary + +##### Mainstream OS + +> Windows, Linux, Unix, OS-X. + +##### Relevant Information + +> Tasks, due dates, tags. + + +## Appendix E : Product Survey + +##### Google Calendar + +> Pros: +> * Able to sync calendars from other people +> * Chrome extension for offline connectivity +> * Multiple viewing options (Calendar/To do list view) +> * Has a Command Line Interface (CLI) + +> Cons: +> * Unable to support floating task +> * Unable to mark tasks as done +> * Unable to block out and free up timings +> * CLI commands only for addition of tasks +> * Bad interface + +##### Wunderlist + +> Pros: +> * Able to set categories +> * Able to mark tasks as done +> * Able to read tasks from e-mails +> * Able to assign tasks to someone +> * Able to search for tasks easily +> * Able to migrate tasks from one category to another easily +> * Web and offline desktop version available + +> Cons: +> * Unable to create subtask +> * Unable to support recurring tasks +> * Unable to block out time slots +> * Unable to set start date for tasks +> * Only has a list view + +##### Todoist + +> Pros: +> * Able to set categories +> * Able to collaborate with others +> * Able to have sub-projects and sub-tasks +> * Able to support recurring tasks +> * Able to sort tasks by priority level +> * Able to integrate from e-mail +> * Able to backup automatically + +> Cons: +> * Unable to block out timings +> * Unable to export out To-do list +> * Minimal CLI +> * Have to do a lot of clicking + +##### Any.Do + +> Pros: +> * Able to set categories by type and day +> * Able to show completed tasks +> * Able to collaborate with others +> * Able to support sub-tasks +> * Able to add attachments +> * Able to support recurring tasks +> * Able to mark task as done +> * Able to notify and remind user +> * Able to have action shortcuts +> * Able to have different types of views + +> Cons: +> * Unable to support floating tasks +> * No CLI + +##### Evernote + +> Pros: +> * Able to quick search +> * Able to support handwriting, embedded images/audio and links +> * Able to work with camera + +> Cons: +> * No CLI +> * No Calendar view + +##### Trello + +> Pros: +> * Able to mark tasks as "in-progress" +> * Able to view as calendar + +> Cons: +> * Unable to import or export +> * Relies on UI interaction +> * No CLI +> * Need to pay for premium use to access 3rd party features +> * No desktop version +``` diff --git a/collated/main/A0124797R.md b/collated/main/A0124797R.md new file mode 100644 index 000000000000..66a078d98fd2 --- /dev/null +++ b/collated/main/A0124797R.md @@ -0,0 +1,2571 @@ +# A0124797R +###### /java/harmony/mastermind/commons/exceptions/NotRecurringTaskException.java +``` java +/** + * Signals that current Task is not recurring Task and not able to perform methods that are + * for recurring tasks + */ +public class NotRecurringTaskException extends Exception{ + + public NotRecurringTaskException() { + super("Task is not a recurring Task"); + } + +} +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java +/** + * 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 int FAIL = -1; + private final TaskManager taskManager; + private final FilteredList filteredTasks; + private final FilteredList filteredFloatingTasks; + private final FilteredList filteredEvents; + private final FilteredList filteredDeadlines; + private final FilteredList filteredArchives; + private final Stack undoHistory; + private final Stack redoHistory; + private final Stack commandHistory; + + public static final String TAB_HOME = "Home"; + public static final String TAB_TASKS = "Tasks"; + public static final String TAB_EVENTS = "Events"; + public static final String TAB_DEADLINES = "Deadlines"; + public static final String TAB_ARCHIVES = "Archives"; + + private String currentTab; + + /** + * Initializes a ModelManager with the given TaskManager TaskManager and its + * variables should not be null + */ + public ModelManager(TaskManager src, UserPrefs userPrefs) { + super(); + assert src != null; + assert userPrefs != null; + + logger.fine("Initializing with task manager: " + + src + + " and user prefs " + + userPrefs); + + taskManager = new TaskManager(src); + filteredTasks = new FilteredList<>(taskManager.getTasks()); + filteredFloatingTasks = new FilteredList<>(taskManager.getFloatingTasks()); + filteredEvents = new FilteredList<>(taskManager.getEvents()); + filteredDeadlines = new FilteredList<>(taskManager.getDeadlines()); + filteredArchives = new FilteredList<>(taskManager.getArchives()); + undoHistory = new Stack<>(); + redoHistory = new Stack<>(); + commandHistory = new Stack(); + currentTab = TAB_HOME; + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + public ModelManager(ReadOnlyTaskManager initialData, UserPrefs userPrefs) { + taskManager = new TaskManager(initialData); + filteredTasks = new FilteredList<>(taskManager.getTasks()); + filteredFloatingTasks = new FilteredList<>(taskManager.getFloatingTasks()); + filteredEvents = new FilteredList<>(taskManager.getEvents()); + filteredDeadlines = new FilteredList<>(taskManager.getDeadlines()); + filteredArchives = new FilteredList<>(taskManager.getArchives()); + undoHistory = new Stack<>(); + redoHistory = new Stack<>(); + commandHistory = new Stack(); + indicateTaskManagerChanged(); + currentTab = TAB_HOME; + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + /** update current tab to the specified tab*/ + public void updateCurrentTab(String tab) { + this.currentTab = tab; + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + /** Deletes the given archived Task */ + public synchronized void deleteArchive(ReadOnlyTask target) throws TaskNotFoundException, ArchiveTaskList.TaskNotFoundException { + taskManager.removeArchive(target); + indicateTaskManagerChanged(); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public synchronized void markTask(Task target) throws TaskNotFoundException { + taskManager.markTask(target); + indicateTaskManagerChanged(); + } + + @Override + public synchronized void markDue(ArrayList targets) throws TaskNotFoundException { + for (Task t: targets) { + taskManager.markTask(t); + } + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + } + + @Override + public synchronized void unmarkTask(Task target) throws ArchiveTaskList.TaskNotFoundException, UniqueTaskList.DuplicateTaskException { + taskManager.unmarkTask(target); + indicateTaskManagerChanged(); + } + + @Override + public String getCurrentTab(){ + return this.currentTab; + } + + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + //=========== Filtered List Accessors =============================================================== + + @Override + public UnmodifiableObservableList getFilteredFloatingTaskList() { + return new UnmodifiableObservableList<>(filteredFloatingTasks); + } + + @Override + public UnmodifiableObservableList getFilteredEventList() { + return new UnmodifiableObservableList<>(filteredEvents); + } + + @Override + public UnmodifiableObservableList getFilteredDeadlineList() { + return new UnmodifiableObservableList<>(filteredDeadlines); + } + + @Override + public UnmodifiableObservableList getFilteredArchiveList() { + return new UnmodifiableObservableList<>(filteredArchives); + } + + // =========== Methods for file access ================================ + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public synchronized void addNextTask(Task task) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException { + taskManager.addNextTask(task); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public ObservableList getListToMark() { + return getCurrentObservableList(); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public void updateFilteredListToShow(String tab) { + switch (tab) { + case TAB_HOME: + currentTab = TAB_HOME; + break; + case TAB_TASKS: + currentTab = TAB_TASKS; + break; + case TAB_EVENTS: + currentTab = TAB_EVENTS; + break; + case TAB_DEADLINES: + currentTab = TAB_DEADLINES; + break; + case TAB_ARCHIVES: + currentTab = TAB_ARCHIVES; + break; + } + updateFilteredListToShowAll(); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public void updateFilteredListToShowAll() { + switch (currentTab) { + case TAB_HOME: + filteredTasks.setPredicate(null); + break; + case TAB_TASKS: + filteredFloatingTasks.setPredicate(null); + break; + case TAB_EVENTS: + filteredEvents.setPredicate(null); + break; + case TAB_DEADLINES: + filteredDeadlines.setPredicate(null); + break; + case TAB_ARCHIVES: + filteredArchives.setPredicate(null); + break; + } + } + + @Override + public int getCurrentListSize() { + switch (currentTab) { + case TAB_HOME: + return filteredTasks.size(); + case TAB_TASKS: + return filteredFloatingTasks.size(); + case TAB_EVENTS: + return filteredEvents.size(); + case TAB_DEADLINES: + return filteredDeadlines.size(); + case TAB_ARCHIVES: + return filteredArchives.size(); + default: + //should not reach here + return FAIL; + } + } + + @Override + public void updateFilteredListToShowUpcoming(long time, String taskType) { + updateFilteredList(new PredicateExpression(new DateQualifier(time, taskType))); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public void updateFilteredTagTaskList(Set keywords) { + updateFilteredList(new PredicateExpression(new TagQualifier(keywords))); + } + + /** + * update filtered list of specific tabs + * @param expression + */ + private void updateFilteredList(Expression expression) { + switch (currentTab) { + case TAB_HOME: + filteredTasks.setPredicate(expression::satisfies); + break; + case TAB_TASKS: + filteredFloatingTasks.setPredicate(expression::satisfies); + break; + case TAB_EVENTS: + filteredEvents.setPredicate(expression::satisfies); + break; + case TAB_DEADLINES: + filteredDeadlines.setPredicate(expression::satisfies); + break; + case TAB_ARCHIVES: + filteredArchives.setPredicate(expression::satisfies); + break; + } + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + /** + * Returns an ObservableList of the filtered tasks in current Tab + */ + private ObservableList getCurrentObservableList() { + ObservableList list = filteredTasks; + + switch (currentTab) { + case "Home": + list = filteredTasks; + break; + case "Tasks": + list = filteredFloatingTasks; + break; + case "Events": + list = filteredEvents; + break; + case "Deadlines": + list = filteredDeadlines; + break; + case "Archives": + list = filteredArchives; + break; + } + + return list; + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + /** Returns an UnmodifiableObservableList of filtered tasks in current Tab */ + public UnmodifiableObservableList getCurrentList() { + return new UnmodifiableObservableList(getCurrentObservableList()); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + /** + * used as a qualifier to filter tasks by tags + */ + private class TagQualifier implements Qualifier { + private Set tagKeyWords; + + TagQualifier(Set tagKeyWords) { + this.tagKeyWords = tagKeyWords; + } + + @Override + public boolean run(ReadOnlyTask task) { + final Set tagList = task.getTags().toSet(); + + return !Collections.disjoint(tagList, tagKeyWords); + } + + @Override + public String toString() { + return "tags=" + + String.join(", ", tagKeyWords.toString()); + } + } + + /** + * used as a qualifier to filter dates to use in {@code UpcomingCommand} + */ + private class DateQualifier implements Qualifier { + private final String TYPE_DEADLINE = "deadlines"; + private final String TYPE_EVENT = "events"; + private final String TYPE_DUE = "due"; + + private long timeNow; + private long oneWeekFromNow; + private final long oneWeek = 604800000; + private String taskType; + + DateQualifier(long time, String taskType) { + this.timeNow = new Date().getTime(); + this.oneWeekFromNow = time + oneWeek; + this.taskType = taskType; + } + + @Override + public boolean run(ReadOnlyTask task) { + switch (taskType) { + case TYPE_DEADLINE: + return isUpcomingDeadline(task); + case TYPE_EVENT: + return isUpcomingEvent(task); + case TYPE_DUE: + return isTaskDue(task); + default: + return isUpcomingAll(task); + } + } + + @Override + public String toString() { + return "Date before:" + + oneWeekFromNow; + } + + private boolean isUpcomingAll(ReadOnlyTask task) { + if (task.isFloating()) { + return true; + } else { + return isUpcoming(task); + } + } + + private boolean isUpcomingEvent(ReadOnlyTask task) { + if (task.isFloating() || task.isDeadline()) { + return false; + } else { + return isUpcoming(task); + } + } + + private boolean isUpcomingDeadline(ReadOnlyTask task) { + if (task.isFloating() || task.isEvent()) { + return false; + } else { + return isUpcoming(task); + } + } + + private boolean isTaskDue(ReadOnlyTask task) { + if (task.isFloating()) { + return false; + } else { + return isDue(task); + } + } + + /** + * Checks if end date of task is within one week from now. + */ + private boolean isUpcoming(ReadOnlyTask task) { + long taskTime = task.getEndDate().getTime(); + boolean isUpcoming = taskTime < oneWeekFromNow && taskTime > timeNow; + return isUpcoming; + } + + /** + * Checks if tasks has already past + */ + private boolean isDue(ReadOnlyTask task) { + long taskTime = task.getEndDate().getTime(); + return taskTime < timeNow; + } + } + + + /** + * handle changing of tabs when using specific commands + * @param event + */ + @Subscribe + private void handleTabChangedEvent(TabChangedEvent event){ + this.updateCurrentTab(event.toTabId); + } + +``` +###### /java/harmony/mastermind/model/ReadOnlyTaskManager.java +``` java +/** + * Unmodifiable view of an task manager + * + */ +public interface ReadOnlyTaskManager { + + UniqueTagList getUniqueTagList(); + + UniqueTaskList getUniqueTaskList(); + + UniqueTaskList getUniqueFloatingTaskList(); + + UniqueTaskList getUniqueEventList(); + + UniqueTaskList getUniqueDeadlineList(); + + ArchiveTaskList getUniqueArchiveList(); + + /** Returns an unmodifiable view of tasks list */ + List getTaskList(); + + /** Returns an unmodifiable view of tasks list */ + List getFloatingTaskList(); + + /** Returns an unmodifiable view of tasks list*/ + List getEventList(); + + /** Returns an unmodifiable view of tasks list */ + List getDeadlineList(); + + /** Returns an unmodifiable view of archive list */ + List getArchiveList(); + + /** Returns an unmodifiable view of tags list */ + List getTagList(); + + + + +} +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Deletes the given Archived Task */ + void deleteArchive(ReadOnlyTask target) throws TaskNotFoundException, ArchiveTaskList.TaskNotFoundException; + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Add the next recurring task */ + void addNextTask(Task task) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException; + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Marks the given task as done */ + void markTask(Task target) throws UniqueTaskList.TaskNotFoundException; + + /** Marks the given List of due tasks as done */ + void markDue(ArrayList targets) throws UniqueTaskList.TaskNotFoundException; + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Updates the completed task as not done */ + void unmarkTask(Task target) throws UniqueTaskList.DuplicateTaskException, + ArchiveTaskList.TaskNotFoundException; + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Returns the current list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getCurrentList(); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Returns the filtered floating task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredFloatingTaskList(); + + /** Returns the filtered event list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredEventList(); + + /** Returns the filtered deadline list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredDeadlineList(); + + /** Returns the filtered archive list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredArchiveList(); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** update current tab to the specified tab*/ + void updateCurrentTab(String tab); + + /** Updates the filter of the filtered task list for current tab to show all tasks */ + void updateFilteredListToShowAll(); + + /** Updates the filter of the filtered task list for specified tab to show all tasks */ + void updateFilteredListToShow(String tab); + + /** Updates the filter of the filtered task list + * for Home tab to show all upcoming tasks */ + void updateFilteredListToShowUpcoming(long time, String taskType); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + String getCurrentTab(); + + /** get the filtered list size for the current tab */ + int getCurrentListSize(); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** reads the file indicated */ + BufferedReader importFile(String fileToImport) throws FileNotFoundException; + + +} +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public String getRecur() { + return this.recur; + } + + @Override + public boolean isRecur() { + return recur != null; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Task // instanceof handles nulls + && this.toString().equals(((Task) other).toString())); // state check + + } + + /** checks if task is marked */ + @Override + public boolean isMarked() { + return this.isMarked; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /** set task as marked */ + public Task mark() { + this.isMarked = true; + return this; + } + + /** set task as not mark */ + public Task unmark() { + this.isMarked = false; + return this; + } + + @Override + public String toString() { + return getAsText(); + } + +``` +###### /java/harmony/mastermind/model/task/ArchiveTaskList.java +``` java +public class ArchiveTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + + /** + * Signals that an operation targeting a specified task in the list would fail because + * there is no such matching task in the list. + */ + public static class TaskNotFoundException extends Exception {} + + + /** + * Constructs empty ArchiveTaskList. + */ + public ArchiveTaskList() {} + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(ReadOnlyTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Adds an archived task to the list. + */ + public void add(Task toAdd) { + assert toAdd != null; + + internalList.add(toAdd); + } + + /** + * Removes the equivalent task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + public ObservableList getInternalList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ArchiveTaskList // instanceof handles nulls + && this.internalList.equals( + ((ArchiveTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} +``` +###### /java/harmony/mastermind/model/task/ReadOnlyTask.java +``` java +public interface ReadOnlyTask { + //provide safe read, unmodifiable task object + final int INDEX_DAY = 0; + final int INDEX_MONTH = 1; + final int INDEX_DATE = 2; + final int INDEX_TIME = 3; + final int INDEX_YEAR = 5; + final int INDEX_HOUR = 0; + final int INDEX_MINUTE = 1; + + public String getName(); + public Date getStartDate(); + public Date getEndDate(); + public Date getCreatedDate(); + public UniqueTagList getTags(); + public String getRecur(); + + public boolean isMarked(); + public boolean isFloating(); + public boolean isDeadline(); + public boolean isEvent(); + public boolean isRecur(); + + public boolean isDue(); + public boolean isHappening(); + public Duration getDueDuration(); + public Duration getEventDuration(); + + default boolean isSameTask(ReadOnlyTask task) { + return task == this // short circuit if same object + || (this.toString().equals(task.toString())); // state check + } + + /** + * Formats the task as text, showing all task details. + */ +``` +###### /java/harmony/mastermind/model/task/ReadOnlyTask.java +``` java + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + + if (getStartDate() != null) { + builder.append(" start:" + parseForConsole(getStartDate())); + } + + if (getEndDate() != null) { + builder.append(" end:" + parseForConsole(getEndDate())); + } + + if (!getTags().toString().isEmpty()) { + builder.append(" Tags: ") + .append(getTags().toString()); + } + + return builder.toString(); + } + +``` +###### /java/harmony/mastermind/model/task/ReadOnlyTask.java +``` java + /** + * Formats the Date as text, showing Task's date. + */ + default String parse(Date date) { + String[] dateArr = date.toString().split(" "); + String[] timeArr = dateArr[INDEX_TIME].split(":"); + final StringBuilder builder = new StringBuilder(); + + builder.append(dateArr[INDEX_DAY] + " ").append(timeArr[INDEX_HOUR] + ":" + timeArr[INDEX_MINUTE] + " ") + .append(dateArr[INDEX_DATE] + " ").append(dateArr[INDEX_MONTH] + " ").append(dateArr[INDEX_YEAR]); + + return builder.toString(); + + } + +``` +###### /java/harmony/mastermind/model/task/ReadOnlyTask.java +``` java + /** + * Formats the Date as text, showing Task's date. + */ + default String parseForConsole(Date date) { + String[] dateArr = date.toString().split(" "); + String[] timeArr = dateArr[INDEX_TIME].split(":"); + String year = dateArr[INDEX_YEAR].substring(2, 4); + final StringBuilder builder = new StringBuilder(); + + builder.append(timeArr[INDEX_HOUR] + ":" + timeArr[INDEX_MINUTE] + "|") + .append(dateArr[INDEX_DATE]).append(dateArr[INDEX_MONTH]).append(year); + + return builder.toString(); + + } + +} +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java +/** + * Wraps all data at the task-manager level + * Duplicates are not allowed (by .equals comparison) + */ +public class TaskManager implements ReadOnlyTaskManager { + private static final int INDEX_RECURRENCE_KEYWORD = 0; + private static final int INDEX_RECURRENCE_AMOUNT = 1; + + private static final int ONE_DAY = 1; + private static final int ONE_WEEK = 7; + private static final int ONE_MONTH = 1; + private static final int ONE_YEAR = 1; + + private final UniqueTaskList tasks; + private final UniqueTaskList floatingTasks; + private final UniqueTaskList events; + private final UniqueTaskList deadlines; + private final ArchiveTaskList archives; + private final UniqueTagList tags; + private final TaskListComparator comparator; + + { + tasks = new UniqueTaskList(); + floatingTasks = new UniqueTaskList(); + events = new UniqueTaskList(); + deadlines = new UniqueTaskList(); + archives = new ArchiveTaskList(); + tags = new UniqueTagList(); + comparator = new TaskListComparator(); + } + + public TaskManager() {} + + /** + * Tasks and Tags are copied into this TaskManager + */ + public TaskManager(ReadOnlyTaskManager toBeCopied) { + this(toBeCopied.getUniqueTaskList(), toBeCopied.getUniqueFloatingTaskList(), + toBeCopied.getUniqueEventList(), toBeCopied.getUniqueDeadlineList(), + toBeCopied.getUniqueTagList(), toBeCopied.getUniqueArchiveList()); + } + + /** + * Tasks and Tags are copied into this TaskManager + */ + public TaskManager(UniqueTaskList tasks, UniqueTaskList floatingTasks, UniqueTaskList events, UniqueTaskList deadlines, UniqueTagList tags, ArchiveTaskList archiveTasks) { + resetData(tasks.getInternalList(), floatingTasks.getInternalList(), events.getInternalList(), + deadlines.getInternalList(), tags.getInternalList(), archiveTasks.getInternalList()); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** returns an {@code ObservableList} of floating tasks*/ + public ObservableList getFloatingTasks() { + return floatingTasks.getInternalList(); + } + + /** returns an {@code ObservableList} of events*/ + public ObservableList getEvents() { + return events.getInternalList(); + } + + /** returns an {@code ObservableList} of deadlines*/ + public ObservableList getDeadlines() { + return deadlines.getInternalList(); + } + + /** returns an {@code ObservableList} of archives*/ + public ObservableList getArchives() { + return archives.getInternalList(); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + public void setFloatingTasks(List floatingTasks) { + this.floatingTasks.getInternalList().setAll(floatingTasks); + } + + public void setEvents(List events) { + this.events.getInternalList().setAll(events); + } + + public void setDeadlines(List deadlines) { + this.deadlines.getInternalList().setAll(deadlines); + } + + public void setArchiveTasks(Collection archiveTasks) { + this.archives.getInternalList().setAll(archiveTasks); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + public void resetData(Collection newTasks, + Collection newFloatingTasks, + Collection newEvents, + Collection newDeadlines, + Collection newTags, + Collection newArchiveTasks) { + + setTasks(newTasks.stream().map(Task::new).collect(Collectors.toList())); + setFloatingTasks(newFloatingTasks.stream().map(Task::new).collect(Collectors.toList())); + setEvents(newEvents.stream().map(Task::new).collect(Collectors.toList())); + setDeadlines(newDeadlines.stream().map(Task::new).collect(Collectors.toList())); + setTags(newTags); + setArchiveTasks(newArchiveTasks.stream().map(Task::new).map(Task::mark).collect(Collectors.toList())); + } + + public void resetData(ReadOnlyTaskManager newData) { + resetData(newData.getTaskList(), newData.getFloatingTaskList(), newData.getEventList(), + newData.getDeadlineList(), newData.getTagList(), newData.getArchiveList()); + } + + + +//// task-level operations + + /** + * Adds a task to the task manager and synchronize with all the tabs. + * Also checks the new task's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the task to point to those in {@link #tags}. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent task already exists. + */ + public void addTask(Task t) throws UniqueTaskList.DuplicateTaskException { + syncTagsWithMasterList(t); + tasks.add(t); + syncAddTask(t); + this.getUniqueTaskList().getInternalList().sort(comparator); + + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * Adds the next recurring task to the task manager. + * Also checks the new task's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the task to point to those in {@link #tags}. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent task already exists. + */ + public void addNextTask(Task t) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException { + syncTagsWithMasterList(t); + Task newT = getNextTask(t); + tasks.add(newT); + syncAddTask(newT); + EventsCenter.getInstance().post(new HighlightLastActionedRowRequestEvent(newT)); + } + + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * returns a Task with the next recurring date given a recurring Task + * @throws NotRecurringTaskException + */ + public Task getNextTask(Task t) throws NotRecurringTaskException { + if (t.isFloating() || t.getRecur() == null){ + throw new NotRecurringTaskException(); + } + + Task newT = null; + String[] recurVal = t.getRecur().split(" "); + String nextRecur = getNextRecur(t.getRecur()); + Date nextEndDate = getNextDate(t.getEndDate(),recurVal[INDEX_RECURRENCE_KEYWORD]); + + if (t.isDeadline()) { + newT = new Task(t.getName(), nextEndDate, t.getTags(), nextRecur, new Date()); + }else if (t.isEvent()) { + Date nextStartDate = getNextDate(t.getStartDate(), recurVal[INDEX_RECURRENCE_KEYWORD]); + newT = new Task(t.getName(), nextStartDate, nextEndDate, t.getTags(), nextRecur, new Date()); + } + + return newT; + + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * returns the next date based on the type of recurring task + */ + private Date getNextDate(Date d, String recur) { + Calendar c = Calendar.getInstance(); + c.setTime(d); + int date; + switch (recur) { + case "daily" : + date = c.get(Calendar.DATE); + c.set(Calendar.DATE, date + ONE_DAY); + break; + case "weekly" : + date = c.get(Calendar.DATE); + c.set(Calendar.DATE, date + ONE_WEEK); + break; + case "monthly" : + date = c.get(Calendar.MONTH); + c.set(Calendar.MONTH, date + ONE_MONTH); + break; + case "yearly" : + date = c.get(Calendar.YEAR); + c.set(Calendar.YEAR, date + ONE_YEAR); + break; + default : + assert false; + } + + return c.getTime(); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * returns the next date based on the type of recurring task + */ + private String getNextRecur(String recur) { + String[] recurArr = recur.split(" "); + if (recurArr.length==1) { + return recur; + }else { + int counter = Integer.parseInt(recurArr[INDEX_RECURRENCE_AMOUNT]); + + if (counter>2) { + return recurArr[0] + " " + Integer.toString(counter-1); + } else { + return null; + } + } + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * Removes an archived task as indicated + */ + public boolean removeArchive(ReadOnlyTask key) throws ArchiveTaskList.TaskNotFoundException { + if (archives.remove(key)) { + return true; + } else { + throw new ArchiveTaskList.TaskNotFoundException(); + } + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * marks task as completed by + * removing the task from tasks and adds into archivedtasks + */ + public boolean markTask(Task key) throws UniqueTaskList.TaskNotFoundException { + if (tasks.remove(key)) { + archives.add(key.mark()); + this.getUniqueTaskList().getInternalList().sort(comparator); + syncRemoveTask(key); + return true; + } else { + throw new UniqueTaskList.TaskNotFoundException(); + } + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * marks task as not completed by + * removing the task from archivedTasks and adds into tasks + */ + public boolean unmarkTask(Task key) throws DuplicateTaskException, ArchiveTaskList.TaskNotFoundException { + if (archives.remove(key)) { + tasks.add(key.unmark()); + this.getUniqueTaskList().getInternalList().sort(comparator); + syncAddTask(key.unmark()); + return true; + } else { + throw new ArchiveTaskList.TaskNotFoundException(); + } + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * Synchronize adding of tasks across the tabs + */ + private void syncAddTask(Task task) throws DuplicateTaskException{ + if (task.isFloating()) { + floatingTasks.add(task); + } else if (task.isDeadline()) { + deadlines.add(task); + } else if (task.isEvent()) { + events.add(task); + } + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + /** + * Synchronize removing of tasks across the tabs + */ + private void syncRemoveTask(ReadOnlyTask task) throws TaskNotFoundException{ + if (task.isFloating()) { + floatingTasks.remove(task); + } else if (task.isDeadline()) { + deadlines.remove(task); + } else if (task.isEvent()) { + events.remove(task); + } + } + + +//// tag-level operations + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + @Override + public List getFloatingTaskList() { + return Collections.unmodifiableList(floatingTasks.getInternalList()); + } + + @Override + public List getEventList() { + return Collections.unmodifiableList(events.getInternalList()); + } + + @Override + public List getDeadlineList() { + return Collections.unmodifiableList(deadlines.getInternalList()); + } + + @Override + public List getArchiveList() { + return Collections.unmodifiableList(archives.getInternalList()); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + @Override + public UniqueTaskList getUniqueFloatingTaskList() { + return this.floatingTasks; + } + + @Override + public UniqueTaskList getUniqueEventList() { + return this.events; + } + + @Override + public UniqueTaskList getUniqueDeadlineList() { + return this.deadlines; + } + + @Override + public ArchiveTaskList getUniqueArchiveList() { + return this.archives; + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskManager // instanceof handles nulls + && this.tasks.equals(((TaskManager) other).tasks) + && this.floatingTasks.equals(((TaskManager) other).floatingTasks) + && this.events.equals(((TaskManager) other).events) + && this.deadlines.equals(((TaskManager) other).deadlines) + && this.tags.equals(((TaskManager) other).tags) + && this.archives.equals(((TaskManager) other).archives)); + } + +``` +###### /java/harmony/mastermind/storage/XmlAdaptedArchive.java +``` java +/** + * JAXB-friendly version of the archived Task. + * + */ +public class XmlAdaptedArchive { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date startDate; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedArchive() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedArchive(ReadOnlyTask source) { + name = source.getName(); + startDate = source.getStartDate(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's ReadOnlyTask object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date startDate = this.startDate; + final Date endDate = this.endDate; + final String recur = this.recur; + final UniqueTagList tags = new UniqueTagList(taskTags); + final Date createdDate = this.createdDate; + + return new Task(name, startDate, endDate, tags, recur, createdDate).mark(); + } +} +``` +###### /java/harmony/mastermind/storage/XmlAdaptedDeadline.java +``` java +/** + * JAXB-friendly version of the deadline type Task. + */ +public class XmlAdaptedDeadline { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedDeadline() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedDeadline + */ + public XmlAdaptedDeadline(ReadOnlyTask source) { + name = source.getName(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted deadline object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted deadline + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date endDate = this.endDate; + final String recur = this.recur; + final UniqueTagList tags = new UniqueTagList(taskTags); + + return new Task(name, endDate, tags, recur, createdDate); + } +} +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + /** + * Conversion task manager to a JAXB-friendly version + */ + public XmlSerializableTaskManager(ReadOnlyTaskManager src) { + floatingTasks.addAll(src.getFloatingTaskList().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); + events.addAll(src.getEventList().stream().map(XmlAdaptedEvent::new).collect(Collectors.toList())); + deadlines.addAll(src.getDeadlineList().stream().map(XmlAdaptedDeadline::new).collect(Collectors.toList())); + tags = src.getTagList(); + archives.addAll(src.getArchiveList().stream() + .map(XmlAdaptedArchive::new).collect(Collectors.toList())); + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public UniqueTaskList getUniqueTaskList() { + UniqueTaskList lists = new UniqueTaskList(); + + for (XmlAdaptedDeadline xmld : deadlines) { + try { + lists.add(xmld.toModelType()); + } catch (IllegalValueException e) { + + } + } + for (XmlAdaptedEvent xmle : events) { + try { + lists.add(xmle.toModelType()); + } catch (IllegalValueException e) { + + } + } + for (XmlAdaptedTask xmlt : floatingTasks) { + try { + lists.add(xmlt.toModelType()); + } catch (IllegalValueException e) { + + } + } + lists.getInternalList().sort(comparator); + return lists; + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public UniqueTaskList getUniqueFloatingTaskList() { + UniqueTaskList lists = new UniqueTaskList(); + + for (XmlAdaptedTask xmlt : floatingTasks) { + try { + lists.add(xmlt.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public UniqueTaskList getUniqueEventList() { + UniqueTaskList lists = new UniqueTaskList(); + for (XmlAdaptedEvent xmle : events) { + try { + lists.add(xmle.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public UniqueTaskList getUniqueDeadlineList() { + UniqueTaskList lists = new UniqueTaskList(); + for (XmlAdaptedDeadline xmld : deadlines) { + try { + lists.add(xmld.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public ArchiveTaskList getUniqueArchiveList() { + ArchiveTaskList lists = new ArchiveTaskList(); + for (XmlAdaptedArchive p : archives) { + try { + lists.add(p.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + @Override + public UniqueTagList getUniqueTagList() { + try { + return new UniqueTagList(tags); + } catch (UniqueTagList.DuplicateTagException e) { + e.printStackTrace(); + return null; + } + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getTaskList() { + List tasks = getFloatingTaskList(); + List event = getEventList(); + List deadline = getDeadlineList(); + + tasks.addAll(event); + tasks.addAll(deadline); + + return tasks; + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getFloatingTaskList() { + return floatingTasks.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getEventList() { + return events.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getDeadlineList() { + return deadlines.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getArchiveList() { + return archives.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + +``` +###### /java/harmony/mastermind/storage/XmlAdaptedEvent.java +``` java +/** + * JAXB-friendly version of the event type Task. + */ +public class XmlAdaptedEvent { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date startDate; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedEvent() {} + + + /** + * Converts a given event into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedEvent + */ + public XmlAdaptedEvent(ReadOnlyTask source) { + name = source.getName(); + startDate = source.getStartDate(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted event object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date startDate = this.startDate; + final Date endDate = this.endDate; + final String recur = this.recur; + final Date createdDate = this.createdDate; + final UniqueTagList tags = new UniqueTagList(taskTags); + + return new Task(name, startDate, endDate, tags, recur, createdDate); + } +} +``` +###### /java/harmony/mastermind/logic/LogicManager.java +``` java + public CommandResult execute(String commandText) { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + Command command = parser.parseCommand(commandText); + command.setData(model, storage); + CommandResult result = command.execute(); + return result; + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + + @Override + public ObservableList getFilteredFloatingTaskList() { + return model.getFilteredFloatingTaskList(); + } + + @Override + public ObservableList getFilteredEventList() { + return model.getFilteredEventList(); + } + + @Override + public ObservableList getFilteredDeadlineList() { + return model.getFilteredDeadlineList(); + } + + @Override + public ObservableList getFilteredArchiveList() { + return model.getFilteredArchiveList(); + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Parses arguments in the context of the mark task command.
+ * + * @return the prepared mark command + */ + private Command prepareMark(String args) { + Optional index = parseIndex(args); + if (!index.isPresent()) { + if (args.trim().equals("due")) { + return new MarkCommand(args.trim()); + } + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + } + return new MarkCommand(index.get()); + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Parses arguments in the context of the list task command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareList(String args) { + Optional type = parseTab(args.toLowerCase()); + if (type.isPresent()) { + if (type.get().equals("empty")) { + return new ListCommand(); + } else { + return new ListCommand(type); + } + } else { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + } + + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Parses arguments in the context of the unmark task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareUnmark(String args) { + Optional index = parseIndex(args); + if (!index.isPresent()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + } + return new UnmarkCommand(index.get()); + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Returns the specified Tab name in the {@code ListCommand} + * IF a correct Tab name is given. + * Returns an {@code Optional.empty()} otherwise. + */ + private Optional parseTab(String command) { + if (command.isEmpty()) { + return Optional.of("empty"); + } + + final Matcher matcher = TAB_ARGS_FORMAT.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String type = matcher.group("tab").toLowerCase(); + + return Optional.of(type); + + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Parses arguments in the context of the upcoming task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareUpcoming(String args) { + + Optional taskType = parseUpcoming(args); + + if (taskType.isPresent()) { + return new UpcomingCommand(taskType.get()); + } else { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UpcomingCommand.MESSAGE_USAGE)); + } + + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Returns the specified arguments for {@code UpcomingCommand} + * IF a correct arguments is given. + */ + private Optional parseUpcoming(String command) { + if (command.isEmpty()) { + return Optional.of("empty"); + } + + final Matcher matcher = UpcomingCommand.COMMAND_ARGUMENTS_PATTERN.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String type = matcher.group("taskType").toLowerCase(); + + return Optional.of(type); + + } + + /** + * Parses arguments in the context of the find tag command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareFindTag(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + + final Set tagSet = new HashSet<>(); + + try { + + for (String tagName : keywords) { + tagSet.add(new Tag(tagName)); + } + + return new FindTagCommand(tagSet); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } + } + +``` +###### /java/harmony/mastermind/logic/Logic.java +``` java +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @return the result of the command execution. + */ + CommandResult execute(String commandText); + + /** Returns the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns the filtered list of floating tasks*/ + ObservableList getFilteredFloatingTaskList(); + + /** Returns the filtered list of events*/ + ObservableList getFilteredEventList(); + + /** Returns the filtered list of deadlines*/ + ObservableList getFilteredDeadlineList(); + + /** Returns the filtered list of archived tasks*/ + ObservableList getFilteredArchiveList(); + + +} +``` +###### /java/harmony/mastermind/logic/commands/MarkCommand.java +``` java +/** + * marks a task as complete and moves it to the archives tab + */ +public class MarkCommand extends Command implements Undoable, Redoable { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": mark a task as done " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + + COMMAND_WORD + + " 1"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + public static final String COMMAND_DESCRIPTION = "Marking a task as done"; + + public static final String MESSAGE_MARK_SUCCESS = "%1$s has been archived"; + public static final String MESSAGE_MARK_DUE_SUCCESS = "All Tasks that are due has been archived"; + public static final String MESSAGE_MARK_FAILURE = "Selected is already marked"; + public static final String MESSAGE_MARK_RECURRING_FAILURE = "Unable to add recurring Task"; + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Mark Command] %1$s has been unmarked"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Mark Command] %1$s has been archived"; + + private int targetIndex; + private String type; + private ArrayList tasksToMark; + + public MarkCommand(int targetIndex) { + this.targetIndex = targetIndex; + this.type = "empty"; + tasksToMark = new ArrayList(); + } + + public MarkCommand(String taskType) { + this.type = taskType; + tasksToMark = new ArrayList(); + } + + @Override + public CommandResult execute() { + try { + if (this.model.getCurrentTab().equals(ModelManager.TAB_ARCHIVES)) { + return new CommandResult(COMMAND_WORD, MarkCommand.MESSAGE_MARK_FAILURE); + } + + executeMark(); + + model.pushToUndoHistory(this); + model.clearRedoHistory(); + + if (type.equals("due")) { + return new CommandResult(COMMAND_WORD, MESSAGE_MARK_DUE_SUCCESS); + } else { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_MARK_SUCCESS, tasksToMark.get(0))); + } + } catch (TaskNotFoundException pnfe) { + return new CommandResult(COMMAND_WORD,Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } catch (NotRecurringTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } + + } + +``` +###### /java/harmony/mastermind/logic/commands/MarkCommand.java +``` java + private void executeMark() throws TaskNotFoundException, DuplicateTaskException, NotRecurringTaskException { + ObservableList lastShownList = model.getListToMark(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new TaskNotFoundException(); + } + + if (type.equals("empty")) { + tasksToMark.add(lastShownList.get(targetIndex - 1)); + Task taskToMark = tasksToMark.get(0); + model.markTask(taskToMark); + if (taskToMark.isRecur()) { + model.addNextTask(taskToMark); + } + } else if (type.equals("due")){ + model.updateFilteredListToShowUpcoming(new Date().getTime(),type); + lastShownList = model.getListToMark(); + for (Task task: lastShownList) { + tasksToMark.add(task); + } + + model.markDue(tasksToMark); + } + + + } +} +``` +###### /java/harmony/mastermind/logic/commands/FindTagCommand.java +``` java +/** + * Finds and lists all tasks in task manager whose tag contains any of the argument keywords. + * Keyword matching is case sensitive. + */ + +public class FindTagCommand extends Command { + + public static final String COMMAND_WORD = "findtag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + "Finds all tasks whose tags contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n\t" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n\t" + + "Example: " + COMMAND_WORD + " meal finals"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " ..."; + public static final String COMMAND_DESCRIPTION = "Finds all tasks whose tags contain any of the specified keywords " + + "(case-sensitive)"; + + private final Set keywords; + + public FindTagCommand(Set keywords) throws IllegalValueException { + + this.keywords = keywords; + } + + /** + * Returns copy of keywords in this command. + */ + public Set getKeywords() { + return new HashSet<>(keywords); + } + + @Override + public CommandResult execute() { + model.updateFilteredTagTaskList(keywords); + return new CommandResult(COMMAND_WORD, getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } + +} +``` +###### /java/harmony/mastermind/logic/commands/ListCommand.java +``` java + public ListCommand() { + tab = ModelManager.TAB_HOME.toLowerCase(); + } + + public ListCommand(Optional args) { + tab = args.get(); + } + + @Override + public CommandResult execute() { + assert tab != null; + + CommandResult cResult = null; + String tabToShow = null; + + + if (tab.equals(ModelManager.TAB_TASKS.toLowerCase())) { + tabToShow = ModelManager.TAB_TASKS; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_TASKS); + } else if (tab.equals(ModelManager.TAB_EVENTS.toLowerCase())) { + tabToShow = ModelManager.TAB_EVENTS; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_EVENTS); + } else if (tab.equals(ModelManager.TAB_DEADLINES.toLowerCase())) { + tabToShow = ModelManager.TAB_DEADLINES; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_DEADLINES); + } else if (tab.equals(ModelManager.TAB_ARCHIVES.toLowerCase())) { + tabToShow = ModelManager.TAB_ARCHIVES; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_ARCHIVES); + } else if (tab.equals(ModelManager.TAB_HOME.toLowerCase())){ + tabToShow = ModelManager.TAB_HOME; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS); + } + + model.updateFilteredListToShow(tabToShow); + + return cResult; + } + + //@author A0143378Y + /* + * Clears all main window text + */ + public static void displayClear() { + detailedView = null; + } + + //@author A0143378Y + /* + * Displays an individual GenericMemory item with all non-null details + */ + public static String displayDetailed(GenericMemory item) { + displayClear(); + detailedView = item; + + assert detailedView != null; + + return formatItem(item); + } + + //@author A0143378Y + /* + * Formats the item display + */ + private static String formatItem(GenericMemory item) { + String result = ITEM_VIEW + "\n" + LINE + "\n" + item + "\n" + LINE2; + return result; + } + + //@author A0143378Y + /* + * Takes an ArrayList of GenericMemoryitems, and display them in a list form, with name as the heading + */ + public static String displayList(ArrayList list, String name) { + displayClear(); + detailedView = null; + listName = name; + + assert listName != null; + + return formatList(list); + } + + //@author A0143378Y + /* + * Formats the list display + */ + private static String formatList(ArrayList list) { + String result = listName + "\n" + LINE; + for (int i=0; i < list.size(); i++) { + String line = i + 1 + BRACKET_OPEN; + line += BRACKET_CLOSE + list.get(i).getName(); + + result = result + "\n" + line; + } + + result = result + "\n" + LINE2; + return result; + } + + //@author A0143378Y + /* + * Displays list of item with the corresponding type + */ + public static void displayType(ArrayList list, String type) { + assert type.length() != 0; + + if (list.size() > 1) { // if list contains multiple items, show as list + displayList(list, DISPLAY_ITEM_TYPE + type); + } else if (list.size() == 1){ // if only one item, show item + displayDetailed(list.get(0)); + } + } +} +``` +###### /java/harmony/mastermind/logic/commands/UpcomingCommand.java +``` java +/** + * Lists all upcoming tasks in the task manager to the user. + */ +public class UpcomingCommand extends Command { + + public static final String COMMAND_WORD = "upcoming"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": List upcoming deadlines and events.\n" + + "Parameters: TASKTYPE (Optional)\n" + + "Example: \n" + + COMMAND_WORD + "\n" + + COMMAND_WORD + " events\n" + + COMMAND_WORD + " deadlines"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + "[tab_name]"; + public static final String COMMAND_DESCRIPTION = "List tasks due in the week"; + + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile("(?deadlines|events)"); + + public final String TIME_TODAY = "today 2359"; + + public static final String MESSAGE_SUCCESS_UPCOMING = "Listed all upcoming tasks"; + public static final String MESSAGE_SUCCESS_UPCOMING_DEADLINE = "Listed all upcoming deadlines"; + public static final String MESSAGE_SUCCESS_UPCOMING_EVENT = "Listed all upcoming events"; + public static final String COMMAND_SUMMARY = "Upcoming tasks: \n" + COMMAND_WORD; + + private String taskType; + + public UpcomingCommand() {} + + public UpcomingCommand(String type) { + this.taskType = type; + } + + @Override + public CommandResult execute() { + model.updateFilteredListToShowUpcoming(prettyTimeParser.parse(TIME_TODAY).get(0).getTime(), taskType); + + switch (taskType) { + case "deadlines" : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING_DEADLINE); + case "events" : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING_EVENT); + default : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING); + } + } + + +} +``` +###### /java/harmony/mastermind/logic/commands/ImportCommand.java +``` java + public ImportCommand(String filePath, String extension) { + this.fileToImport = filePath.trim(); + this.extension = extension; + lstOfCmd = new ArrayList(); + } + + @Override + public CommandResult execute() { + assert fileToImport != null; + assert lstOfCmd != null; + + CommandResult result = null; + + switch (extension) { + case EXT_ICS: + result = importIcsFile(); + break; + case EXT_CSV: + result = importCsvFile(); + break; + } + return result; + + } + + /** gets the list of commands for adding tasks */ + public ArrayList getTaskToAdd() { + return this.lstOfCmd; + } + + private CommandResult importCsvFile() { + int currLine = 0; + int errCount = 0; + String errLines = ""; + try { + Iterable records = CSVFormat.RFC4180 + .withHeader(HEADER_NAME, HEADER_START_DATE, HEADER_START_TIME, HEADER_END_DATE, HEADER_END_TIME) + .parse(new FileReader(fileToImport)); + + boolean isTask = false; + for (CSVRecord record : records) { + if (isTask) { + currLine++; + try { + Optional taskToAdd = parseCsvRecord(record); + if (taskToAdd.isPresent()) { + model.addTask(taskToAdd.get()); + } else { + errCount++; + errLines += Integer.toString(currLine) + ","; + } + } catch (IllegalValueException | InvalidEventDateException | IllegalArgumentException e) { + errCount++; + errLines += Integer.toString(currLine) + ","; + } + + } else { + currLine++; + isTask = true; + if (!record.get(INDEX_NAME).equals(HEADER_NAME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_START_DATE).equals(HEADER_START_DATE)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_START_TIME).equals(HEADER_START_TIME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_END_DATE).equals(HEADER_END_DATE)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_END_TIME).equals(HEADER_END_TIME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + } + } + + } catch (IOException ioe) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_READ_FAILURE, fileToImport)); + } + + int addCount = currLine - errCount - HEADER_LINE; + if (errLines.isEmpty()) { + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(MESSAGE_IMPORT_TXT_SUCCESS, addCount)); + } else { + errLines = errLines.substring(0,errLines.length()-1); + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(MESSAGE_IMPORT_TXT_FAILURE, addCount, errLines)); + } + } + + /** + * reads a csv record and return a Task if its valid else an empty Optional + * @return Parsed Task object + * @throws IllegalValueException + * @throws InvalidEventDateException + */ + private Optional parseCsvRecord(CSVRecord record) throws IllegalValueException, InvalidEventDateException { + Optional name; + Optional startDate; + Optional endDate; + + if (record.get(HEADER_NAME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + name = Optional.ofNullable(record.get(HEADER_NAME)); + } + + if (record.get(HEADER_START_DATE).equals(EMPTY_ARG)) { + startDate = Optional.empty(); + } else if (record.get(HEADER_START_TIME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + startDate = Optional.ofNullable(record.get(HEADER_START_DATE) + " " + record.get(HEADER_START_TIME)); + } + if (record.get(HEADER_END_DATE).equals(EMPTY_ARG)) { + endDate = Optional.empty(); + } else if (record.get(HEADER_END_TIME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + endDate = Optional.ofNullable(record.get(HEADER_END_DATE) + " " + record.get(HEADER_END_TIME)); + } + if (startDate.isPresent() && !endDate.isPresent()) { + return Optional.empty(); + } + + + Set tags = new HashSet(); + tags.add("CSVIMPORT"); + + TaskBuilder taskBuilder = new TaskBuilder(name.get()); + taskBuilder.withTags(tags); + + if (startDate.isPresent() && endDate.isPresent()) { + taskBuilder.asEvent(prettyTimeParser.parse(startDate.get()).get(0), prettyTimeParser.parse(endDate.get()).get(0)); + return Optional.ofNullable(taskBuilder.build()); + } else if (endDate.isPresent()) { + taskBuilder.asDeadline(prettyTimeParser.parse(endDate.get()).get(0)); + return Optional.ofNullable(taskBuilder.build()); + } else if (!endDate.isPresent()) { + taskBuilder.asFloating(); + return Optional.ofNullable(taskBuilder.build()); + } else { + return Optional.empty(); + } + + } + +``` +###### /java/harmony/mastermind/logic/commands/UnmarkCommand.java +``` java +public class UnmarkCommand extends Command implements Undoable, Redoable{ + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": undo marking of task as done" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + + COMMAND_WORD + + " 1"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + public static final String COMMAND_DESCRIPTION = "Unmarking a task as done"; + + public static final String MESSAGE_UNMARK_SUCCESS = "%1$s has been unmarked"; + public static final String MESSAGE_DUPLICATE_UNMARK_TASK = "%1$s already exist in not completed list"; + public static final String MESSAGE_UNMARK_FAILURE = "Tasks in current tab has not been marked"; + + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Unmark Command] %1$s has been archived"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Unmark Command] %1$s has been unmarked"; + + private final int targetIndex; + private Task taskToUnmark; + + public UnmarkCommand(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() { + try { + if (!this.model.getCurrentTab().equals(ModelManager.TAB_ARCHIVES)) { + return new CommandResult(COMMAND_WORD, UnmarkCommand.MESSAGE_UNMARK_FAILURE); + } + + executeUnmark(); + + model.pushToUndoHistory(this); + + model.clearRedoHistory(); + + requestHighlightLastActionedRow(taskToUnmark); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNMARK_SUCCESS, taskToUnmark)); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_DUPLICATE_UNMARK_TASK, taskToUnmark)); + } catch (TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/UnmarkCommand.java +``` java + private void executeUnmark() throws IndexOutOfBoundsException, TaskNotFoundException, + DuplicateTaskException { + UnmodifiableObservableList lastShownList = model.getFilteredArchiveList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new TaskNotFoundException(); + } + + taskToUnmark = (Task) lastShownList.get(targetIndex - 1); + + model.unmarkTask(taskToUnmark); + } + +} +``` +###### /java/harmony/mastermind/ui/CommandBox.java +``` java + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandField.setText(""); +} + + /** + * Handles any KeyPress in the commandField + */ + @FXML + private void handleKeyPressed(KeyEvent event) { + KeyCode key = event.getCode(); + switch (key) { + case UP: + restorePrevCommandText(); + return; + case DOWN: + restoreNextCommandText(); + return; + case ENTER: + learnWord(commandField.getText()); + return; + } + + if (CTRL_ONE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS)); + } else if (CTRL_TWO.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_TASKS)); + } else if (CTRL_THREE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_EVENTS)); + } else if (CTRL_FOUR.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_DEADLINES)); + } else if (CTRL_FIVE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_ARCHIVES)); + } + } + +``` +###### /java/harmony/mastermind/ui/CommandBox.java +``` java + private void restoreCommandText() { + commandField.setText(currCommandText); + } + + private void restorePrevCommandText() { + String prevCommand = getPrevCommandHistory(); + if (prevCommand != null) { + // need to wrap in runLater due to concurrency threading in JavaFX + Platform.runLater(new Runnable() { + public void run() { + commandField.setText(prevCommand); + commandField.positionCaret(prevCommand.length()); + } + }); + } // else ignore + } + + private void restoreNextCommandText() { + String nextCommand = getNextCommandHistory(); + // need to wrap in runLater due to concurrency threading in JavaFX + Platform.runLater(new Runnable() { + public void run() { + if (nextCommand != null) { + commandField.setText(nextCommand); + commandField.positionCaret(nextCommand.length()); + } else { + commandField.setText(""); + } + } + }); +} + + /** + * Adds recent input into stack + */ + private void updateCommandHistory(String command) { + commandHistory.push(command); + commandIndex = commandHistory.size(); + } + + private String getPrevCommandHistory() { + if (commandHistory.empty()) { + return null; + } else if (commandIndex == 0) { + return null; + } else { + commandIndex--; + return commandHistory.get(commandIndex); + } + } + + private String getNextCommandHistory() { + if (commandHistory.empty()) { + return null; + } else if (commandIndex >= commandHistory.size() - 1) { + commandIndex = commandHistory.size(); + return null; + } else { + commandIndex++; + return commandHistory.get(commandIndex); + } + } + + @Subscribe + private void handleIncorrectCommand(IncorrectCommandAttemptedEvent event) { + restoreCommandText(); + } +} +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.setSortable(false); + recurColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView("file:src/main/resources/images/recur_white.png")); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } + +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + /** + * update the number of task in each tab in the tab title + */ + protected void updateTabTitle() { + tabLst.get(INDEX_HOME).setText(NAME_TABS[INDEX_HOME] + + "(" + + logic.getFilteredTaskList().size() + + ")"); + tabLst.get(INDEX_TASKS).setText(NAME_TABS[INDEX_TASKS] + + "(" + + logic.getFilteredFloatingTaskList().size() + + ")"); + tabLst.get(INDEX_EVENTS).setText(NAME_TABS[INDEX_EVENTS] + + "(" + + logic.getFilteredEventList().size() + + ")"); + tabLst.get(INDEX_DEADLINES).setText(NAME_TABS[INDEX_DEADLINES] + + "(" + + logic.getFilteredDeadlineList().size() + + ")"); + tabLst.get(INDEX_ARCHIVES).setText(NAME_TABS[INDEX_ARCHIVES] + + "(" + + logic.getFilteredArchiveList().size() + + ")"); + } + +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + /** + * handle the switching of tabs when list/upcoming is used + */ + private void updateTab(String result) { + switch (result) { + case ListCommand.MESSAGE_SUCCESS: + tabPane.getSelectionModel().select(INDEX_HOME); + break; + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING: + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING_DEADLINE: + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING_EVENT: + tabPane.getSelectionModel().select(INDEX_HOME); + break; + case ListCommand.MESSAGE_SUCCESS_TASKS: + tabPane.getSelectionModel().select(INDEX_TASKS); + break; + case ListCommand.MESSAGE_SUCCESS_EVENTS: + tabPane.getSelectionModel().select(INDEX_EVENTS); + break; + case ListCommand.MESSAGE_SUCCESS_DEADLINES: + tabPane.getSelectionModel().select(INDEX_DEADLINES); + break; + case ListCommand.MESSAGE_SUCCESS_ARCHIVES: + tabPane.getSelectionModel().select(INDEX_ARCHIVES); + break; + } + } + +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + @Subscribe + private void handleTaskManagerChanged(TaskManagerChangedEvent event) { + updateTabTitle(); + logger.info("Update tab title."); + } + +} +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderRecurCell() { + return new TableCell() { + + @Override + public void updateItem(Boolean isRecur, boolean isEmpty) { + super.updateItem(isRecur, isEmpty); + if (!isEmpty()) { + CheckBox box = new CheckBox(); + box.setSelected(isRecur); + box.setDisable(true); + box.setStyle("-fx-opacity: 1"); + + this.setAlignment(Pos.CENTER); + this.setGraphic(box); + } else { + this.setGraphic(null); + ; + } + } + }; + } + +``` diff --git a/collated/main/A0124797Runused.md b/collated/main/A0124797Runused.md new file mode 100644 index 000000000000..730cac052a54 --- /dev/null +++ b/collated/main/A0124797Runused.md @@ -0,0 +1,101 @@ +# A0124797Runused +###### /java/harmony/mastermind/model/ModelManager.java +``` java + //remove the use of importing txt file + @Override + public synchronized BufferedReader importFile(String fileToImport) throws FileNotFoundException { + BufferedReader br = new BufferedReader(new FileReader(fileToImport)); + + return br; + } + +``` +###### /java/harmony/mastermind/logic/LogicManager.java +``` java + // disable the use of importing txt file + /** + * parse the result of commands and handle ImportCommand separately + */ + /* + private CommandResult parseResult(Command cmd) { + CommandResult result = cmd.execute(); + if (result.feedbackToUser.equals(ImportCommand.MESSAGE_READ_SUCCESS)) { + result = handleImport((ImportCommand) cmd); + } + + return result; + } + + /** + * handle the inputs from the reading of file from ImportCommand + */ + /* + public CommandResult handleImport(ImportCommand command) { + ArrayList lstOfCmd = command.getTaskToAdd(); + String errLines = ""; + int errCount = 0; + int lineCounter = 0; + + for (String cmd: lstOfCmd) { + lineCounter += 1; + Command cmdResult = parser.parseCommand(cmd); + cmdResult.setData(model, storage); + CommandResult addResult = cmdResult.execute(); + boolean isFailure = isAddFailure(addResult); + + if (isFailure) { + errCount += 1; + errLines += Integer.toString(lineCounter) + ","; + } + } + + int addCount = lineCounter - errCount; + if (errLines.isEmpty()) { + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(ImportCommand.MESSAGE_IMPORT_TXT_SUCCESS, addCount)); + } else { + errLines = errLines.substring(0,errLines.length()-1); + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(ImportCommand.MESSAGE_IMPORT_TXT_FAILURE, addCount, errLines)); + } + + } + */ + + + /** + * Checks if adding of tasks from ImportCommand is valid + */ + /* + private boolean isAddFailure(CommandResult cmdResult) { + if (cmdResult.toString().substring(MESSAGE_START_INDEX, MESSAGE_END_INDEX).equals(AddCommand.MESSAGE_SUCCESS.substring(MESSAGE_START_INDEX, MESSAGE_END_INDEX))) { + return false; + } + + return true; + } + */ +} +``` +###### /java/harmony/mastermind/logic/commands/ImportCommand.java +``` java + // find that it does not make sense to import a txt file of commands, + // instead user could have + /* + private CommandResult importTxtFile() { + try { + BufferedReader br = model.importFile(fileToImport); + + String line; + + while ((line = br.readLine()) != null) { + lstOfCmd.add(line); + } + + } catch (IOException e) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_READ_FAILURE, fileToImport)); + } + + return new CommandResult(COMMAND_WORD, MESSAGE_READ_SUCCESS); + }*/ + + +``` diff --git a/collated/main/A0124797Rused.md b/collated/main/A0124797Rused.md new file mode 100644 index 000000000000..9a409f053325 --- /dev/null +++ b/collated/main/A0124797Rused.md @@ -0,0 +1,36 @@ +# A0124797Rused +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** adds an event */ + /** + * @deprecated + */ + /* + public AddCommand(String name, String startDate, String endDate, Set tags, String recurVal, Memory mem) throws IllegalValueException, InvalidEventDateException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + Date createdDate = new Date(); + Date startTime = prettyTimeParser.parse(startDate).get(0); + Date endTime = prettyTimeParser.parse(endDate).get(0); + + if (startTime.after(endTime)) { + throw new InvalidEventDateException(); + } + + this.toAdd = new Task(name, startTime, endTime, new UniqueTagList(tagSet), recurVal, createdDate); + + //Converting Date start to Calendar start + Calendar start = dateToCalendar(startTime); + + //Converting Date end to Calendar end + Calendar end = dateToCalendar(endTime); + + event = new GenericMemory(tags.toString(), name, "", start, end, 0); + mem.add(event); + } + */ + + // deadline +``` diff --git a/collated/main/A0138862W.md b/collated/main/A0138862W.md new file mode 100644 index 000000000000..55c8cd1f68c8 --- /dev/null +++ b/collated/main/A0138862W.md @@ -0,0 +1,2692 @@ +# A0138862W +###### /resources/view/MainWindow.css +``` css +* { + -fx-primary: #66D9EF; /* lightblue */ + -fx-secondary: #FD971F; /* orange */ + -fx-tertiary: #A6E22E; /* green */ + -fx-quaternary: #F92672; /* pink */ + -fx-quinary: #272822; /* black */ + -fx-quinary-alt: #1f201b; + -fx-senary: #AE81FF; /* purple */ +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: -fx-quinary; + -fx-control-inner-background-alt: -fx-quinary-alt; + -fx-background-color: -fx-quinary; + -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-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 18px; + -fx-font-family: "Verdana"; + -fx-text-fill: -fx-primary; + -fx-alignment: center; + -fx-opacity: 1; +} + +.table-row-cell { + -fx-cell-size: 55px; +} + +.table-row-cell:selected { + -fx-background-color: black; + -fx-table-cell-border-color: black; + -fx-border-width: 2px 2px 2px 2px; + -fx-border-color: -fx-primary; + -fx-border-style: dashed; +} + +.text-field{ + -fx-background-color: #3f3f3f; + -fx-font-size: 12pt; + -fx-font-family: "Lucida Console"; + -fx-text-fill: -fx-primary; + -fx-border-radius: 0 0 0 0; + -fx-background-radius: 0 0 0 0; +} + +.tab-pane .tab-header-area .headers-region, +.tab-pane .tab-header-area .tab-header-background{ + -fx-background-color: -fx-quinary; +} + +.tab-pane .tab-header-area .tab{ + -fx-background-color: -fx-quinary; +} + +.tab-pane .tab-header-area .tab-label{ + -fx-text-fill: lightGray; + -fx-font-weight: bold; + -fx-font-style: oblique; +} + +.tab-pane .tab-header-area .tab:selected .tab-label{ + -fx-text-fill: -fx-primary; + -fx-font-weight: bold; +} + +.text-area, +.text-area .content{ + -fx-background-color: #1d1d1d; + -fx-font-size: 12pt; + -fx-font-family: "Lucida Console"; + -fx-text-fill: -fx-primary; +} + +.list-view{ + -fx-background-color: #1d1d1d; +} + +.label{ + -fx-text-fill: white; + -fx-font-family: "Verdana"; + -fx-font-size: 12pt; + -fx-font-weight: bold; + -fx-padding: 15px; + -fx-font-style: oblique; +} + +.list-cell{ + -fx-cell-size: 50; + -fx-padding: 10 5 10 15; + -fx-background-color: -fx-quinary; + -fx-text-fill: white; +} + +.list-cell:filled:selected:focused{ + -fx-background-color: #1a1a1a; +} + +.scroll-bar, +.scroll-bar .decrement-button, +.scroll-bar .increment-button{ + -fx-background-color: -fx-quinary; +} + +.scroll-bar .thumb, +.scroll-bar .decrement-button > .decrement-arrow, +.scroll-bar .increment-button > .increment-arrow{ + -fx-background-color: -fx-primary; +} + +.action-history-entry .title{ + -fx-text-fill: -fx-primary; + -fx-font-weight: bold; + -fx-font-size: 14px; + -fx-font-style: normal; + -fx-padding: 0px; +} + +.action-history-entry .date{ + -fx-text-fill: -fx-primary; + -fx-font-weight: normal; + -fx-font-size: 10px; + -fx-font-style: normal; + -fx-padding: 0px; +} + +.action-history-entry .description{ + -fx-text-fill: white; + -fx-font-weight: normal; + -fx-font-size: 12px; + -fx-font-style: normal; + -fx-padding: 0px; +} + +.task-name-column{ + -fx-font-weight: bold; + -fx-font-size: 18px; +} + +.index-column{ + -fx-font-weight: bold; + -fx-font-size: 16px; + -fx-fill: white; + -fx-font-style: oblique; +} + +.pretty-date{ + -fx-font-weight:bold; +} + +.ugly-date{ + -fx-font-size:10px; +} + +.tag{ + -fx-background-color: -fx-primary; + -fx-background-radius: 30; + -fx-background-insets: 0; + -fx-text-fill: -fx-quinary; + -fx-font-size: 10px; + -fx-font-weight: bold; +} + +.tag-overdue{ + -fx-background-color: -fx-quaternary; + -fx-background-radius: 30; + -fx-background-insets: 0; + -fx-text-fill: white; + -fx-font-size: 8px; + -fx-font-weight: bold; +} + +.tag-happening{ + -fx-background-color: -fx-secondary; + -fx-background-radius: 30; + -fx-background-insets: 0; + -fx-text-fill: black; + -fx-font-size: 8px; + -fx-font-weight: bold; +} + +.tag-due-duration, +.tag-event-duration{ + -fx-background-color: darkGray; + -fx-background-radius: 30; + -fx-background-insets: 0; + -fx-text-fill: black; + -fx-font-size: 8px; + -fx-font-weight: bold; +} + +.normal{ + -fx-fill: -fx-tertiary; +} + +.overdue{ + -fx-fill: -fx-quaternary; +} + +.happening{ + -fx-fill: -fx-secondary; +} + +.completed{ + -fx-fill: -fx-senary; +} + +.titled-pane, +.titled-pane .title, +.titled-pane .content{ + -fx-background-color: #1d1d1d; + -fx-border-color: #1d1d1d; +} + +.titled-pane .title{ + -fx-border-width: 2px 0px 0px 0px; + -fx-border-color: #3f3f3f; + -fx-border-style: dashed; +} + +.titled-pane > .title .text{ + -fx-fill: -fx-primary; + -fx-font-family: "Lucida Console"; + -fx-font-style: oblique; + -fx-font-weight: bold; + +} + +.titled-pane .title .arrow-button .arrow{ + -fx-background-color: -fx-primary; +} + +.titled-pane .list-cell{ + -fx-background-color: #1d1d1d; +} +``` +###### /resources/view/ActionHistoryEntry.fxml +``` fxml + + + + + + + + + + + + +``` +###### /resources/view/MainWindow.fxml +``` fxml + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+
+
+
+``` +###### /java/harmony/mastermind/commons/exceptions/TaskAlreadyMarkedException.java +``` java +/* + * This exception is thrown when attempt to execute MarkCOmmand on a task that's already marked + */ +public class TaskAlreadyMarkedException extends Exception { + + + public TaskAlreadyMarkedException(){ + super(); + } +} +``` +###### /java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java +``` java +public class ExecuteCommandEvent extends BaseEvent{ + + public Date dateExecuted; + public String title; + public String description; + +``` +###### /java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java +``` java + public ExecuteCommandEvent(String title, String description){ + dateExecuted = new Date(); + + this.title = title; + this.description = description; + } + +``` +###### /java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java +``` java + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + + +} +``` +###### /java/harmony/mastermind/commons/events/ui/ToggleActionHistoryEvent.java +``` java +/** + * + * This event is raised when the ActionHistoryCommand request to toggle the UI. + * + */ +public class ToggleActionHistoryEvent extends BaseEvent{ + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### /java/harmony/mastermind/commons/events/ui/TabChangedEvent.java +``` java +public class TabChangedEvent extends BaseEvent { + + public final String fromTabId; + public final String toTabId; + + public TabChangedEvent(String fromTabId, String toTabId){ + this.fromTabId = fromTabId; + this.toTabId = toTabId; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### /java/harmony/mastermind/commons/events/ui/HighlightLastActionedRowRequestEvent.java +``` java +/* + * This event is raise when a command request UI to highlight the action row in the tableview + * + */ +public class HighlightLastActionedRowRequestEvent extends BaseEvent { + + public Task task; + + public HighlightLastActionedRowRequestEvent(Task task){ + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### /java/harmony/mastermind/model/tag/UniqueTagList.java +``` java + public String toString(){ + // functional way to transform list of items into concatenated string joining with commas (JAVA 8) + return this.getInternalList().stream().map(Object::toString).collect(Collectors.joining(",")); + } +} +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + /* + * push the command to the undo history. + * @see harmony.mastermind.model.Model#pushToUndoHistory(harmony.mastermind.logic.commands.Undoable) + */ + public void pushToUndoHistory(Undoable command) { + undoHistory.push(command); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + /** undo last action performed **/ + public CommandResult undo() throws EmptyStackException { + CommandResult commandResult = undoHistory.pop().undo(); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + return commandResult; + } + + @Override +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + /* + * push the command to the redo history. Should only be called after an undo operation + * @see harmony.mastermind.model.Model#pushToRedoHistory(harmony.mastermind.logic.commands.Redoable) + */ + public void pushToRedoHistory(Redoable command) { + redoHistory.push(command); + } + + @Override +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + /** redo the action that being undone function **/ + public CommandResult redo() throws EmptyStackException { + CommandResult commandResult = redoHistory.pop().redo(); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + return commandResult; + } + + @Override + /** + * This method should only be called when the user entered a new command + * other than redo/undo + **/ +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + public void clearRedoHistory() { + redoHistory.clear(); + } + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** undo last action performed, throws EmptyStackException is there's no more action can be undone **/ + CommandResult undo() throws EmptyStackException; + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** push the command to redo history */ + void pushToRedoHistory(Redoable command); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** undo last action performed, throws EmptyStackException is there's no more action can be undone **/ + CommandResult redo() throws EmptyStackException; + + /** empty redoHistory **/ + // required when a new command is entered, model should throw away all remaining commands in the redo history + void clearRedoHistory(); + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Returns filtered task list as an {@code ObervableList} */ + ObservableList getListToMark(); + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Search */ + void searchTask(String input); + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /* + * Initialize through taskBuilder (preferred way) + */ + protected Task(TaskBuilder taskBuilder){ + this.name = taskBuilder.getName(); + this.startDate = taskBuilder.getStartDate(); + this.endDate = taskBuilder.getEndDate(); + this.createdDate = taskBuilder.getCreatedDate(); + this.tags = taskBuilder.getTags(); + this.recur = taskBuilder.getRecur(); + this.isMarked = taskBuilder.isMarked(); + } + + // event +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Task(String name, Date startDate, Date endDate, UniqueTagList tags, String recur, Date createdDate) { + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + this.tags = tags; + this.isMarked = false; + this.recur = recur; + this.createdDate = createdDate; + } + + // deadline +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Task(String name, Date endDate, UniqueTagList tags, String recur, Date createdDate) { + this(name, null, endDate, tags, recur, createdDate); + } + + // floating +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Task(String name, UniqueTagList tags, Date createdDate) { + this(name, null, null, tags, null, createdDate); + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Task(ReadOnlyTask source) { + this(source.getName(), source.getStartDate(), source.getEndDate(), source.getTags(), source.getRecur(), source.getCreatedDate()); + this.isMarked = source.isMarked(); + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public boolean isFloating() { + return startDate == null && endDate == null; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public boolean isDeadline() { + return startDate == null && endDate != null; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public boolean isEvent() { + return startDate != null && endDate != null; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Date getCreatedDate() { + return createdDate; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /* + * + * Check if the current task is due. Applies to only deadline & event + * - deadline: true if and only if current date is after end date + * - event: true if and only if current date is after end date & start date + * + * @see harmony.mastermind.model.task.ReadOnlyTask#isDue() + */ + public boolean isDue(){ + Date now = new Date(); + if (isDeadline() && now.after(endDate)) { + return true; + } else if (isEvent() && now.after(startDate) && now.after(endDate)){ + return true; + } else { + return false; + } + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /* + * + * Check if current task is happening at the moment. + * Only applies to event, where the current date falls between start & end date + * + * @see harmony.mastermind.model.task.ReadOnlyTask#isHappening() + */ + public boolean isHappening(){ + Date now = new Date(); + if (isEvent() && now.after(startDate) && now.before(endDate)) { + return true; + } else { + return false; + } + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /* + * + * Calculate the duration of event. + * Applies to only event, return null otherwise. + * + * @see harmony.mastermind.model.task.ReadOnlyTask#getEventDuration() + */ + public Duration getEventDuration(){ + if(isEvent()){ + long differencel = endDate.getTime() - startDate.getTime(); + + return Duration.of(differencel, ChronoUnit.MILLIS); + }else{ + return null; + } + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + /* + * calculate the duration until due date + * Applies to only deadlines, return null otherwise. + * + */ + public Duration getDueDuration(){ + if(endDate != null){ + long nowl = System.currentTimeMillis(); + long endDatel = endDate.getTime(); + + long differencel = endDatel - nowl; + + return Duration.of(differencel, ChronoUnit.MILLIS); + }else{ + return null; + } + } +} +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java +import java.util.Date; +import java.util.Set; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.tag.UniqueTagList.DuplicateTagException; + +/** + * The TaskBuilder is a safe way to create a task object instead of relying on constructors. + * The Task has numerous attributes which depends on the nature of the task (event, floating, deadline), + * it must be build independently. + *
+ * The task builder provide a systematic way to build a task object and is future proof. + * Task Builder mitigates the problem of having too many parameters in Task constructor. + * + */ +public class TaskBuilder { + + private final String name; + private Date startDate; + private Date endDate; + private Date createdDate; + private UniqueTagList tags; + private String recur; + private boolean isMarked; + + /** + * creation date will automatically assigned upon initializing. + * + * @param name is mandatory field. The task name. + * + */ + public TaskBuilder(String name){ + this.name = name; + this.createdDate = new Date(); + } + + /** + * build a floating task with both start and end dates are null + * + */ + public TaskBuilder asFloating(){ + startDate = null; + endDate = null; + return this; + } + + /** + * build an event with both valid start and end dates + * + * @param startDate is the start date of the event + * @param endDate is the end date of the event + * + * @throws InvalidEventDateException if start date is after end date + * + */ + public TaskBuilder asEvent(Date startDate, Date endDate) throws InvalidEventDateException{ + if (startDate.after(endDate)) { + throw new InvalidEventDateException(); + } + + this.startDate = startDate; + this.endDate = endDate; + + return this; + } + + /** + * build a deadline task with only end date + * + * @param endDate is the due date. + * + */ + public TaskBuilder asDeadline(Date endDate){ + this.startDate = null; + this.endDate = endDate; + return this; + } + + public TaskBuilder withTags(Set tagSet) throws IllegalValueException { + tags = new UniqueTagList(); + for (String tag: tagSet) { + this.tags.add(new Tag(tag)); + } + return this; + } + + public TaskBuilder withTags(UniqueTagList tags) throws IllegalValueException { + this.tags = tags; + return this; + } + + /** + * build a recurring task. + * + * @param recur can be daily, weekly, monthly, yearly + * + */ + public TaskBuilder asRecurring(String recur){ + this.recur = recur; + return this; + } + + /** + * Set a custom creation date. This will overwrite the auto assigned date. + * + * @param createdDate is the customized creation date + * + */ + public TaskBuilder withCreationDate(Date createdDate){ + this.createdDate = createdDate; + return this; + } + + /** + * build a task as marked task + * + */ + public TaskBuilder asMarked(){ + this.isMarked = true; + return this; + } + + + /** + * finalize build and return a Task object + * + */ + public Task build(){ + return new Task(this); + } + +``` +###### /java/harmony/mastermind/model/task/TaskListComparator.java +``` java +public class TaskListComparator implements Comparator { + + /* + * By default, compare by creation dates. + * This comparator use for sorting table view so edit/delete will not add to the bottom of the list + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(ReadOnlyTask o1, ReadOnlyTask o2) { + if(o1.getCreatedDate() == null || o2.getCreatedDate() == null){ + return 0; + } + + if (o1.getCreatedDate().after(o2.getCreatedDate())) { + return 1; + } else if (o1.getCreatedDate().before(o2.getCreatedDate())) { + return -1; + } else { + return 0; + } + } +} +``` +###### /java/harmony/mastermind/storage/XmlAdaptedTask.java +``` java + public XmlAdaptedTask(ReadOnlyTask source) { + name = source.getName(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + private Command prepareAdd(String args) { + try { + + final Matcher matcher = AddCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate user command input + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_EXAMPLES)); + } + + // mandatory field + // catch escaped name group if exists, otherwise use unescaped name group + final String nameEscaped = matcher.group("nameEscaped"); + final String name = (nameEscaped != null)? nameEscaped: matcher.group("name"); + + // at this point name variable should never be null because the regex only capture full match of mandatory components + // check for bug in regex expression if the following throws assertion error + assert name != null; + + // optionals + final Optional dates = Optional.ofNullable(matcher.group("dates")); + final Optional tags = Optional.ofNullable(matcher.group("tags")); + final Optional recur = Optional.ofNullable(matcher.group("recur")); + + // return internal value if present. else, return empty string + final Set tagSet = getTagsFromArgs(tags.map(val -> val).orElse("")); + + // after init every capturing groups, we start to build the command + AddCommand addCommand = buildAddCommand(name, dates, recur, tagSet); + + return addCommand; + } catch (IllegalValueException | InvalidEventDateException e) { + return new IncorrectCommand(e.getMessage()); + } + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Build the AddCommand + * + * @param name is mandatory field + * @param dates contain user input date string that has to be parsed by natural language processing library. Optional + * @param recur contain recur input contain the keyword such as daily, weekly, monthly. Optional + * @param tagSet unique set of tag string associated to the task + * + * @throws IllegalValueException if tags contain non-alphanumeric value + * @throws InvalidEventDateException if event start date is after end date + */ + private AddCommand buildAddCommand(final String name, final Optional dates, final Optional recur, final Set tagSet) throws IllegalValueException, InvalidEventDateException { + AddCommandBuilder addCommandBuilder = new AddCommandBuilder(name); + addCommandBuilder.withTags(tagSet); + + if(dates.isPresent()){ + PrettyTimeParser ptp = new PrettyTimeParser(); + List parsedDates = ptp.parseSyntax(dates.get()); + + if(!parsedDates.isEmpty()){ + recur.ifPresent(recurVal -> addCommandBuilder.asRecurring(recurVal)); + List startEndDates = parsedDates.get(0).getDates(); + + /* + * We assume two conditions after parsing nlp dates: + * 1. Found only 1 date, then we assume it is a deadline + * 2. Found 2 dates, then we assume it is an event + */ + if(shouldParseAsDeadline(startEndDates)){ + addCommandBuilder.asDeadline(startEndDates.get(0)); + }else if(shouldParseAsEvent(startEndDates)){ + addCommandBuilder.asEvent(startEndDates.get(0), startEndDates.get(1)); + } + } + }; + return addCommandBuilder.build(); + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /* + * Determine the date should be parse as deadline task + */ + private boolean shouldParseAsDeadline(List dates){ + return dates.size() == 1; + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /* + * Determine the date should be parse as event task + */ + private boolean shouldParseAsEvent(List dates){ + return dates.size() == 2; + } + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /* + * Extract the source destination string and prepare the Import ICS command. + * + */ + private Command prepareImport(String args){ + final Matcher matcher = ImportCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } + + final String source = matcher.group("source"); + final String extension = matcher.group("extension"); + + assert source != null; + assert extension != null; + + return new ImportCommand(source, extension); + } + + /** + * Parses arguments in the context of the edit task command. + * + * @param args + * full command args string + * @return the prepared command + */ +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + private Command prepareEdit(String args) { + final Matcher matcher = EditCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + try { + + // mandatory + // regex accept only numbers in index field, encountering NumberFormatException is impossible + final int index = Integer.parseInt(matcher.group("index")); + + //optional + final Optional recur = Optional.ofNullable(matcher.group("recur")); + final Optional name = Optional.ofNullable(matcher.group("name")); + final Optional startDate = Optional.ofNullable(matcher.group("startDate")); + final Optional endDate = Optional.ofNullable(matcher.group("endDate")); + final Optional tags = Optional.ofNullable(matcher.group("tags")); + + Optional> tagSet = Optional.empty(); + if(tags.isPresent()){ + tagSet = Optional.ofNullable(getTagsFromArgs(tags.get())); + }; + + return new EditCommand(index, name, startDate, endDate, tagSet, recur); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (ParseException pe) { + return new IncorrectCommand(pe.getMessage()); + } + + } + + private Command prepareExport(String args){ + final Matcher matcher = ExportCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_EXAMPLE)); + } + + try { + // mandatory + final String destination = matcher.group("destination"); + + // at this point destination variable should never be null because the regex only capture full match of mandatory components + // check for bug in regex expression if the following throws assertion error + assert destination != null; + + // capture all matched string if present + final Optional tasks = Optional.ofNullable(matcher.group("tasks")); + final Optional deadlines = Optional.ofNullable(matcher.group("deadlines")); + final Optional events = Optional.ofNullable(matcher.group("events")); + final Optional archives = Optional.ofNullable(matcher.group("archives")); + + if(isExportingAll(tasks, deadlines, events, archives)){ + return new ExportCommand(destination, true, true, true, true); + }else{ + boolean isExportingTasks = tasks.isPresent(); + boolean isExportingDeadlines = deadlines.isPresent(); + boolean isExportingEvents = events.isPresent(); + boolean isExportingArchives = archives.isPresent(); + + return new ExportCommand(destination, isExportingTasks, isExportingDeadlines, isExportingEvents, isExportingArchives); + } + } catch (IOException e) { + return new IncorrectCommand(e.getMessage()); + } + } + + /* + * This method return true if user did not specify any categories to export, + * then we assume user wants to export all categories. + * eg: export to C:\User\Jim\Workspace\mastermind.csv + */ + private boolean isExportingAll(Optional tasks, Optional deadlines, Optional events, Optional archives){ + return tasks.isPresent() == false && + deadlines.isPresent() == false && + events.isPresent() == false && + archives.isPresent() == false; + } + +``` +###### /java/harmony/mastermind/logic/commands/Command.java +``` java + /** + * Raises an event to highlight the last action row in table + * This event should be subscribed by UiManager to update the table + * + * @param the row that contain the task that needs to be highlighted + * + */ + protected void requestHighlightLastActionedRow(Task task){ + EventsCenter.getInstance().post(new HighlightLastActionedRowRequestEvent(task)); + } + +} +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java +public class AddCommand extends Command implements Undoable, Redoable { + private static final Logger logger = LogsCenter.getLogger(AddCommand.class); + + public static final String COMMAND_KEYWORD_ADD = "add"; + public static final String COMMAND_KEYWORD_DO = "do"; + + // Better regex, support better NLP: + // general form: add some task name from tomorrow 8pm to next friday 8pm daily #recurring,awesome + // https://regex101.com/r/M2A3tB/8 + public static final String COMMAND_ARGUMENTS_REGEX = "(?='(?.+)'|(?(?:(?:.(?!by|from|#)))+))" + + "(?:(?=.*(?:by|from)\\s(?(?:.(?!#|.*'))+)?))?" + + "(?:(?=.*(?daily|weekly|monthly|yearly)))?" + + "(?:(?=.*#(?.+)))?" + + ".*"; + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_DESCRIPTION = "Adding a task"; + + public static final String COMMAND_FORMAT = "Floating Task: (add|do) #[]\n" + + "Deadline: (add|do) by [daily|weekly|monthly|yearly] #[comma_separated_tags]\n" + + "Event: (add|do) from to [daily|weekly|monthly|yearly] #[comma_separated_tags]"; + + public static final String MESSAGE_EXAMPLE_EVENT = "add attend workshop from today 7pm to next monday 1pm #programming,java"; + public static final String MESSAGE_EXAMPLE_DEADLINE = "add submit homework by next sunday 11pm #math,physics"; + public static final String MESSAGE_EXAMPLE_FLOATING = "do chores #cleaning"; + public static final String MESSAGE_EXAMPLE_RECUR_DEADLINE = "add submit homework by next sunday 11pm weekly #math,physics"; + public static final String MESSAGE_EXAMPLE_RECUR_EVENT = "add attend workshop from today 7pm to next monday 1pm monthly #programming,java"; + + public static final String MESSAGE_EXAMPLES = new StringBuilder() + .append("[Format]\n") + .append(COMMAND_FORMAT+ "\n\n") + .append("[Examples]:\n") + .append("Event: "+ MESSAGE_EXAMPLE_EVENT+"\n") + .append("Deadline: "+MESSAGE_EXAMPLE_DEADLINE+"\n") + .append("Floating: "+MESSAGE_EXAMPLE_FLOATING+"\n") + .append("Recurring Deadline: "+MESSAGE_EXAMPLE_RECUR_DEADLINE+"\n") + .append("Recurring Event: "+MESSAGE_EXAMPLE_RECUR_EVENT+"\n") + .toString(); + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Add Command] Task deleted: %1$s"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Add Command] Task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in Mastermind"; + + private final Task toAdd; + + + private static final String TASK = "Task"; + private static final String DEADLINE = "Deadline"; + private static final String EVENT = "Event"; + + static GenericMemory task; + static GenericMemory deadline; + static GenericMemory event; + +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** + * Build the AddCommand using addCommandBuilder. + * Depending on the builder attributes, the taskBuilder will return the appropriate event/floating/deadline task. + * + * @param addCommandBuilder to build the command safely + * + */ + protected AddCommand(AddCommandBuilder addCommandBuilder) throws IllegalValueException, InvalidEventDateException{ + TaskBuilder taskBuilder = new TaskBuilder(addCommandBuilder.getName()); + taskBuilder.withTags(addCommandBuilder.getTags()); + + if(addCommandBuilder.isDeadline()){ + taskBuilder.asDeadline(addCommandBuilder.getEndDate()); + }else if(addCommandBuilder.isEvent()){ + taskBuilder.asEvent(addCommandBuilder.getStartDate(), addCommandBuilder.getEndDate()); + } + + if(addCommandBuilder.isRecurring()){ + taskBuilder.asRecurring(addCommandBuilder.getRecur()); + } + + toAdd = taskBuilder.build(); + + Parser.mem.add(new GenericMemory(toAdd.getTags().toString(), toAdd.getName(), "")); + } + + @Override +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + public CommandResult execute() { + assert model != null; + try { + executeAdd(); + + model.pushToUndoHistory(this); + + // this is a new command entered by user (not undo/redo) + // need to clear the redoHistory Stack + model.clearRedoHistory(); + + requestHighlightLastActionedRow(toAdd); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_SUCCESS, toAdd)); + } catch (UniqueTaskList.DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_ADD,MESSAGE_DUPLICATE_TASK); + } + + } + +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** action to perform when ModelManager requested to undo this command **/ + @Override + public CommandResult undo() { + try { + model.deleteTask(toAdd); + + model.pushToRedoHistory(this); + + logger.info("Task undo" + toAdd.getName()); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_UNDO_SUCCESS, toAdd)); + } catch (UniqueTaskList.TaskNotFoundException pne) { + return new CommandResult(COMMAND_KEYWORD_ADD,Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** action to perform when ModelManager requested to redo this command**/ + @Override + public CommandResult redo() { + assert model != null; + try { + executeAdd(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(toAdd); + + logger.info("Task redo" + toAdd.getName()); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_REDO_SUCCESS, toAdd)); + } catch (UniqueTaskList.DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_ADD,MESSAGE_DUPLICATE_TASK); + } + } + + +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** extract method since it's reusable for execute() and redo()**/ + private void executeAdd() throws DuplicateTaskException { + model.addTask(toAdd); + } + +``` +###### /java/harmony/mastermind/logic/commands/RedoCommand.java +``` java + public CommandResult execute() { + + try{ + // All Command supports undo operation must implement Redoable interface + + // execute the redo strategy implemented by the underlying command + CommandResult redoResult = model.redo(); + + // display successful message and the details of the undo operations + return new CommandResult(COMMAND_WORD, + MESSAGE_SUCCESS + "\n" + + "=====Redo Details=====\n" + + redoResult.feedbackToUser + "\n"+ + "=================="); + }catch(EmptyStackException ex){ + return new CommandResult(COMMAND_WORD, MESSAGE_EMPTY_COMMAND_HISTORY); + } + } + +} +``` +###### /java/harmony/mastermind/logic/commands/Undoable.java +``` java +public interface Undoable { + + /** + * Specify the undo strategy according to the nature of the corresponding class. + * For example, to implement an undo operation on AddCommand, a delete operation should be implemented. + * Similarly, a DeleteCommand should implement a add operation to restore the record. + * + * @return CommandResult, the Object contains details & feedback about the undo operation. + */ +``` +###### /java/harmony/mastermind/logic/commands/Undoable.java +``` java + public CommandResult undo(); + +} +``` +###### /java/harmony/mastermind/logic/commands/MarkCommand.java +``` java + @Override + /* + * Strategy to undo mark command + * + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + try { + + for (Task t:tasksToMark) { + model.unmarkTask(t); + + requestHighlightLastActionedRow(t); + } + model.pushToRedoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, tasksToMark.get(0))); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(UnmarkCommand.MESSAGE_DUPLICATE_UNMARK_TASK, tasksToMark.get(0))); + } catch (ArchiveTaskList.TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/MarkCommand.java +``` java + @Override + /* + * Strategy to redo mark command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + try { + executeMark(); + + model.pushToUndoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_REDO_SUCCESS, tasksToMark.get(0))); + + } catch (TaskNotFoundException pnfe) { + return new CommandResult(COMMAND_WORD,Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } catch (DuplicateTaskException | NotRecurringTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java +public class ExportCommand extends Command { + + public static final String COMMAND_KEYWORD_EXPORT = "export"; + + public static final String COMMAND_ARGUMENTS_REGEX = "(?:(?=.*(?tasks)))?" + + "(?:(?=.*(?deadlines)))?" + + "(?:(?=.*(?events)))?" + + "(?:(?=.*(?archives)))?" + + ".*to " + + "(?.+)"; + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_FORMAT = "export [tasks] [deadlines] [events] [archives] to "; + + public static final String COMMAND_DESCRIPTION = "Export to desired format"; + + public static final String MESSAGE_EXAMPLE = "export tasks deadlines to C:\\Desktop\\mastermind.csv"; + + public static final String MESSAGE_SUCCESS = "CSV exported."; + + public static final String MESSAGE_FAILURE = "Failed to export CSV."; + + private static final String NEWLINE_CHARACTER = "\n"; + + private static final Object[] GOOGLE_CALENDAR_HEADER = { "Subject", "Start Date", "Start Time", "End Date", "End Time", "All Day Event", "Description", "Location", "Private" }; + + private static final SimpleDateFormat GOOGLE_CALENDAR_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy"); + + private static final SimpleDateFormat GOOGLE_CALENDAR_TIME_FORMAT = new SimpleDateFormat("HH:mm"); + + private final String destination; + + private final boolean isExportingTasks; + + private final boolean isExportingDeadlines; + + private final boolean isExportingEvents; + + private final boolean isExportingArchives; + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + /** + * Create a ExportCommand + * + * @param destination the file output destination + * @param isExportingTasks whether to export all floating tasks + * @param isExportingDeadlines whether to export all deadlines + * @param isExportingEvents whether to export all events + * @param isExportingArchives whether to export all archives + * @throws IOException if destination file path is invalid + */ + public ExportCommand(String destination, boolean isExportingTasks, boolean isExportingDeadlines, boolean isExportingEvents, boolean isExportingArchives) throws IOException { + this.destination = destination; + this.isExportingTasks = isExportingTasks; + this.isExportingDeadlines = isExportingDeadlines; + this.isExportingEvents = isExportingEvents; + this.isExportingArchives = isExportingArchives; + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + @Override + public CommandResult execute() { + + CSVFormat csvFormat = CSVFormat.EXCEL; + + try (FileWriter fileWriter = new FileWriter(destination); CSVPrinter csvPrinter = new CSVPrinter(fileWriter, csvFormat);) { + printHeader(csvPrinter); + printTasks(csvPrinter); + printDeadlines(csvPrinter); + printEvents(csvPrinter); + printArchives(csvPrinter); + return new CommandResult(COMMAND_KEYWORD_EXPORT, MESSAGE_SUCCESS); + } catch (IOException e) { + return new CommandResult(COMMAND_KEYWORD_EXPORT, MESSAGE_FAILURE); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + private void printHeader(CSVPrinter csvPrinter) throws IOException { + csvPrinter.printRecord(GOOGLE_CALENDAR_HEADER); + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + private void printTasks(CSVPrinter csvPrinter) throws IOException { + if (isExportingTasks) { + UnmodifiableObservableList tasks = model.getFilteredFloatingTaskList(); + printDataBody(csvPrinter, tasks); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + private void printDeadlines(CSVPrinter csvPrinter) throws IOException{ + if(isExportingDeadlines){ + UnmodifiableObservableList deadlines = model.getFilteredDeadlineList(); + printDataBody(csvPrinter, deadlines); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + private void printEvents(CSVPrinter csvPrinter) throws IOException{ + if(isExportingEvents){ + UnmodifiableObservableList events = model.getFilteredEventList(); + printDataBody(csvPrinter, events); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + private void printArchives(CSVPrinter csvPrinter) throws IOException{ + if(isExportingArchives){ + UnmodifiableObservableList archives = model.getFilteredArchiveList(); + printDataBody(csvPrinter, archives); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/ExportCommand.java +``` java + /** + * A helper method to print list of tasks into csv. + * Due to the limitation of Google Calendar (must specify start and end date): + * + * - Floating tasks are assigned with dummy date (exported date) as their start and end date, in addition set the all day event as true. + * - Deadlines will share the same start and end dates + * - Event will and distinct start and end dates + * + * All tags are exported under descriptions + * + * @param csvPrinter CSVPrinter object + * @param tasks list of tasks to print + * @throws IOException if file is not writable + */ + private void printDataBody(CSVPrinter csvPrinter, UnmodifiableObservableList tasks) throws IOException { + for (ReadOnlyTask task : tasks) { + List data = new ArrayList<>(); + data.add(task.getName()); + if(task.isFloating()){ + Date dummyDate = new Date(); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(dummyDate)); + } else if (task.isDeadline()){ + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + } else if (task.isEvent()){ + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getStartDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getStartDate())); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + } + data.add((task.isFloating())? "TRUE": "FALSE"); + data.add(task.getTags().toString().replaceAll(",", " ")); + data.add(null); + data.add("TRUE"); + csvPrinter.printRecord(data); + } + } + +} +``` +###### /java/harmony/mastermind/logic/commands/HistoryCommand.java +``` java +/** + * + * Command to toggle ActionHistory in UI + * + * This command takes no parameter. + * @author kfwong + * + */ +public class HistoryCommand extends Command { + + public static final String COMMAND_KEYWORD_ACTIONHISTORY = "history"; + public static final String COMMAND_DESCRIPTION = "Toggles action history bar"; + + public static final String MESSAGE_SUCCESS = "Action history toggled."; + + @Override + public CommandResult execute() { + + requestToggleActionHistory(); + return new CommandResult(COMMAND_KEYWORD_ACTIONHISTORY, MESSAGE_SUCCESS); + + } + + private void requestToggleActionHistory() { + EventsCenter.getInstance().post(new ToggleActionHistoryEvent()); + } + +} +``` +###### /java/harmony/mastermind/logic/commands/EditCommand.java +``` java + public static final String COMMAND_ARGUMENTS_REGEX = "(?=(?\\d+))" + + "(?:(?=.*name to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*start date to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*end date to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*tags to #(?:(?.+?)(?:\\s|,\\s|$|,$|\\R))?))?" + + "(?:(?=.*recur (?daily|weekly|monthly|yearly)(?:,|$|\\R)))?" + + ".+"; + + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_FORMAT = "(edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #,]"; + + public static final String MESSAGE_USAGE = COMMAND_FORMAT + + "\n" + + "Edits the task identified by the index number used in the last task listing.\n" + + "Example: \n" + + "edit 2 name to parents with dinner, end date to tomorrow 7pm, recur daily, tags to #meal,family"; + + public static final String MESSAGE_EDIT_TASK_PROMPT = "Edit the following task: %1$s"; + + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Edit Command] Task reverted: %1$s"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Edit Command] Edit the following task: %1$s"; + + public static final String COMMAND_DESCRIPTION = "Editing a task"; + + // private MainWindow window; + private ReadOnlyTask originalTask; + private Task editedTask; + + private final int targetIndex; + private Optional name; + private Optional startDate; + private Optional endDate; + private Optional recur; + private Optional> tags; +``` +###### /java/harmony/mastermind/logic/commands/EditCommand.java +``` java + /* + * Strategy implementation to undo the edit command + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + + try { + model.deleteTask(editedTask); + + // add back the original task + model.addTask((Task) originalTask); + + model.pushToRedoHistory(this); + + requestHighlightLastActionedRow((Task)originalTask); + + return new CommandResult(COMMAND_KEYWORD_EDIT, String.format(MESSAGE_UNDO_SUCCESS, originalTask)); + } catch (UniqueTaskList.TaskNotFoundException pne) { + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_EDIT, AddCommand.MESSAGE_DUPLICATE_TASK); + } + } + + @Override +``` +###### /java/harmony/mastermind/logic/commands/EditCommand.java +``` java + /* + * Strategy implementation to redo the edit command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + + try { + executeEdit(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(editedTask); + + return new CommandResult(COMMAND_KEYWORD_EDIT, String.format(MESSAGE_REDO_SUCCESS, originalTask)); + } catch (TaskNotFoundException | IndexOutOfBoundsException ie) { + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } catch (IllegalValueException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_COMMAND_FORMAT); + } catch (InvalidEventDateException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_DATE); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/EditCommand.java +``` java + private void executeEdit() throws TaskNotFoundException, IndexOutOfBoundsException, InvalidEventDateException, IllegalValueException { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new IndexOutOfBoundsException(); + } + + if (originalTask == null) { + originalTask = lastShownList.get(targetIndex - 1); + } + + // parsing inputs + // if user provides explicit field and value, we change them + // otherwise, all omitted field are taken from the original + String toEditName = name.map(val -> val).orElse(originalTask.getName()); + Date toEditStartDate = startDate.map(val -> prettyTimeParser.parse(val).get(0)).orElse(originalTask.getStartDate()); + Date toEditEndDate = endDate.map(val -> prettyTimeParser.parse(val).get(0)).orElse(originalTask.getEndDate()); + String toEditRecur = recur.map(val -> val).orElse(originalTask.getRecur()); + UniqueTagList toEditTags = new UniqueTagList(tags.map(val -> { + final Set tagSet = new HashSet<>(); + for (String tagName : val) { + try { + tagSet.add(new Tag(tagName)); + } catch (IllegalValueException e) { + e.printStackTrace(); + } + } + return tagSet; + }).orElse(originalTask.getTags().toSet())); + Date toEditCreatedDate = originalTask.getCreatedDate(); + + + // initialize the new task with edited values + editedTask = buildEditedTask(toEditName, toEditStartDate, toEditEndDate, toEditRecur, toEditTags, toEditCreatedDate); + + model.deleteTask(originalTask); + model.addTask(editedTask); + } + +``` +###### /java/harmony/mastermind/logic/commands/EditCommand.java +``` java + /** + * Attempt to build a task based on edited value. + * If this command has build it before, it'll simply return the previous instance. + * + * @param toEditName The edited name + * @param toEditStartDate the edited start date + * @param toEditEndDate edited end date + * @param toEditRecur recurring keyword + * @param toEditTags tags + * @param toEditCreatedDate custom creation date + * @return + * @throws IllegalValueException if tags are not alphanumeric + * @throws InvalidEventDateException if start date is after end date + */ + private Task buildEditedTask(String toEditName, Date toEditStartDate, Date toEditEndDate, String toEditRecur, UniqueTagList toEditTags, Date toEditCreatedDate) throws IllegalValueException, InvalidEventDateException { + if (editedTask == null) { + TaskBuilder taskBuilder = new TaskBuilder(toEditName); + taskBuilder.withCreationDate(toEditCreatedDate); + taskBuilder.withTags(toEditTags); + taskBuilder.asRecurring(toEditRecur); + + if (isEvent(toEditStartDate, toEditEndDate)){ + taskBuilder.asEvent(toEditStartDate,toEditEndDate); + } else if (isDeadline(toEditStartDate, toEditEndDate)){ + taskBuilder.asDeadline(toEditEndDate); + } else if (isFloating(toEditStartDate, toEditEndDate)){ + taskBuilder.asFloating(); + } + editedTask = taskBuilder.build(); + } + + return editedTask; + } +``` +###### /java/harmony/mastermind/logic/commands/DeleteCommand.java +``` java + /** action to perform when ModelManager requested to undo this delete command **/ + public CommandResult undo() { + try { + model.addTask((Task) toDelete); + + model.pushToRedoHistory(this); + + requestHighlightLastActionedRow((Task) toDelete); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, toDelete)); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_WORD, AddCommand.MESSAGE_DUPLICATE_TASK); + } + } + + @Override +``` +###### /java/harmony/mastermind/logic/commands/DeleteCommand.java +``` java + /** action to perform when ModelManager requested to redo this delete command **/ + public CommandResult redo() { + try { + executeDelete(); + + model.pushToUndoHistory(this); + + } catch (TaskNotFoundException | IndexOutOfBoundsException | ArchiveTaskList.TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_REDO_SUCCESS, toDelete)); + } + + private void executeDelete() throws TaskNotFoundException, IndexOutOfBoundsException, ArchiveTaskList.TaskNotFoundException { + UnmodifiableObservableList lastShownList = model.getCurrentList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new IndexOutOfBoundsException(); + } + + if (toDelete == null) { + toDelete = lastShownList.get(targetIndex - 1); + deleteDirectly(toDelete.toString(), memory); + } + + if (toDelete.isMarked()) { + model.deleteArchive(toDelete); + }else { + model.deleteTask(toDelete); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/Redoable.java +``` java +public interface Redoable { + /** + * Specify the redo strategy according to the nature of the corresponding class. + * + * @return CommandResult, the Object contains details & feedback about the redo operation. + */ + public CommandResult redo(); +} +``` +###### /java/harmony/mastermind/logic/commands/UndoCommand.java +``` java +public class UndoCommand extends Command{ + + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_SUCCESS = "Undo successfully."; + public static final String MESSAGE_EMPTY_COMMAND_HISTORY = "There's no more action available to undo."; + public static final String MESSAGE_COMMAND_NOT_UNDOABLE = "This command is not undoable"; + public static final String COMMAND_DESCRIPTION = "Undo an action"; + + @Override +``` +###### /java/harmony/mastermind/logic/commands/UndoCommand.java +``` java + public CommandResult execute() { + + try{ + // All Command supports undo operation must implement Undoable interface + + // execute the undo strategy implemented by the underlying command + CommandResult undoResult = model.undo(); + + // display successful message and the details of the undo operations + return new CommandResult(COMMAND_WORD, + MESSAGE_SUCCESS + "\n" + + "=====Undo Details=====\n" + + undoResult.feedbackToUser + "\n"+ + "=================="); + }catch(EmptyStackException ex){ + return new CommandResult(COMMAND_WORD, MESSAGE_EMPTY_COMMAND_HISTORY); + } + } + + + +} + +``` +###### /java/harmony/mastermind/logic/commands/AddCommandBuilder.java +``` java +/* + * AddCommandBuilder provides a safe way to create an AddCommand + * + */ +public class AddCommandBuilder { + public String name; + public Date startDate; + public Date endDate; + public Set tags; + public String recur; + + public AddCommandBuilder(String name) { + this.name = name; + } + + public AddCommandBuilder asEvent(Date startDate, Date endDate) throws InvalidEventDateException{ + if (startDate.after(endDate)) { + throw new InvalidEventDateException(); + } + + this.startDate = startDate; + this.endDate = endDate; + return this; + } + + public AddCommandBuilder asDeadline(Date endDate) { + this.startDate = null; + this.endDate = endDate; + return this; + } + + public AddCommandBuilder withTags(Set tags) throws IllegalValueException { + this.tags = tags; + return this; + } + + public AddCommandBuilder asRecurring(String recur) { + this.recur = recur; + return this; + } + + public AddCommand build() throws IllegalValueException, InvalidEventDateException { + return new AddCommand(this); + } + + public boolean isFloating() { + return startDate == null + && endDate == null; + } + + public boolean isDeadline() { + return startDate == null + && endDate != null; + } + + public boolean isEvent() { + return startDate != null + && endDate != null; + } + + public boolean isRecurring() { + return this.recur != null; + } + + public String getName() { + return name; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Set getTags() { + return tags; + } + + public String getRecur() { + return recur; + } + +} +``` +###### /java/harmony/mastermind/logic/commands/ImportCommand.java +``` java +/** + * Reads either ics/csv file and import the tasks into Mastermind + */ +public class ImportCommand extends Command { + private static final int HEADER_LINE = 1; + private static final int INDEX_NAME = 0; + private static final int INDEX_START_DATE = 1; + private static final int INDEX_START_TIME = 2; + private static final int INDEX_END_DATE = 3; + private static final int INDEX_END_TIME = 4; + + public static final String COMMAND_WORD = "import"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reads file and add all task from file into Mastermind\n" + + "Parameters: File location\n" + + "Example:\n" + + "for Mac:\n" + + COMMAND_WORD + "from" + + " Users/Jim/Desktop/jim@gmail.com.ics\n" + + "for Windows:\n" + + COMMAND_WORD + "from" + + " C:\\Users\\Jim\\jim@gmail.com.ics"; + + public static final String COMMAND_ARGUMENTS_REGEX = "from (?.+)(?<=(?txt|csv|ics))"; + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String MESSAGE_READ_SUCCESS = "Read success on imported file"; + public static final String MESSAGE_READ_FAILURE = "Invalid file path: %1$s"; + public static final String MESSAGE_CSV_READ_FAILURE = "Header in csv File is invalid\n" + + "First row of your csv file should include headers " + + "like Subject, Start Date, Start Time, End Date, End Time"; + public static final String MESSAGE_IMPORT_TXT_SUCCESS = "Import success: %1$s tasks added"; + public static final String MESSAGE_IMPORT_TXT_FAILURE = "Import failure: %1$s tasks added \nInvalid lines: %2$s"; + public static final String MESSAGE_IMPORT_ICS_SUCCESS = "Import ics success."; + public static final String MESSAGE_IMPORT_ICS_FAILURE = "Failed to import ics."; + + public static final String MESSAGE_FAILURE_DUPLICATE_TASK = "Failed to import ics. Duplicate task detected when importing."; + + public static final String COMMAND_FORMAT = "import "; + public static final String COMMAND_DESCRIPTION = "Reads file and add all task from file into Mastermind"; + + public static final String HEADER_NAME = "Subject"; + public static final String HEADER_START_DATE = "Start Date"; + public static final String HEADER_START_TIME = "Start Time"; + public static final String HEADER_END_DATE = "End Date"; + public static final String HEADER_END_TIME = "End Time"; + + + public static final String EXT_CSV = "csv"; + public static final String EXT_ICS = "ics"; + + public static final String REGEX_COMMA = ","; + + public static final String EMPTY_ARG = ""; + + private String fileToImport; + private String extension; + private ArrayList lstOfCmd; + +``` +###### /java/harmony/mastermind/logic/commands/ImportCommand.java +``` java + private CommandResult importIcsFile() { + + try (FileInputStream fis = new FileInputStream(fileToImport)){ + ICalendar ical = Biweekly.parse(fis).first(); + + for (VEvent event : ical.getEvents()) { + Task task = parseTask(event); + model.addTask(task); + } + + return new CommandResult(COMMAND_WORD, MESSAGE_IMPORT_ICS_SUCCESS); + } catch (DuplicateTaskException e){ + return new CommandResult(COMMAND_WORD, MESSAGE_FAILURE_DUPLICATE_TASK); + } catch (InvalidEventDateException | IOException | IllegalValueException e) { + return new CommandResult(COMMAND_WORD, MESSAGE_IMPORT_ICS_FAILURE); + } + } + + /** + * This method will attempt to parse a ical's VEvent to a Mastermind Task Object + * + * @param event The ical VEvent Object to parse + * @return the parsed Task object + * @throws InvalidEventDateException if start date is after end date + * @throws IllegalValueException if tags contains non-alphanumeric characters + */ + private Task parseTask(VEvent event) throws InvalidEventDateException, IllegalValueException { + Set tags = new HashSet(); + tags.add("ICALIMPORT"); + + TaskBuilder taskBuilder = new TaskBuilder(event.getSummary().getValue()); + taskBuilder.asEvent(event.getDateStart().getValue(), event.getDateEnd().getValue()); + taskBuilder.withTags(tags); + + return taskBuilder.build(); + } + + +} +``` +###### /java/harmony/mastermind/logic/commands/UnmarkCommand.java +``` java + @Override + /* + * Strategy to undo unmark command + * + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + try { + // remove the task that's previously added. + model.markTask(taskToUnmark); + + model.pushToRedoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, taskToUnmark)); + } catch (UniqueTaskList.TaskNotFoundException e) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } + } + + @Override +``` +###### /java/harmony/mastermind/logic/commands/UnmarkCommand.java +``` java + /* + * + * Strategy to redo unmark command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + try { + executeUnmark(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(taskToUnmark); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNMARK_SUCCESS, taskToUnmark)); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_DUPLICATE_UNMARK_TASK, taskToUnmark)); + } catch (TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + +``` +###### /java/harmony/mastermind/ui/ActionHistoryEntry.java +``` java +public class ActionHistoryEntry extends UiPart{ + + private static final String FXML = "ActionHistoryEntry.fxml"; + + @FXML + private HBox actionHistoryEntry; + + @FXML + private Label title; + + @FXML + private Label date; + + public void setTitle(String title){ + this.title.setText(title); + } + + public void setDate(String date){ + this.date.setText(date); + } + + public Node getNode(){ + return actionHistoryEntry; + } + + public void setTypeFail(){ + this.title.setStyle("-fx-text-fill: -fx-quaternary;"); + this.date.setStyle("-fx-text-fill: -fx-quaternary;"); + } + + public void setTypeSuccess(){ + this.title.setStyle("-fx-text-fill: -fx-primary;"); + this.date.setStyle("-fx-text-fill: -fx-primary;"); + } + + @Override + public void setNode(Node node) { + actionHistoryEntry = (HBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + + +} + +``` +###### /java/harmony/mastermind/ui/ActionHistoryEntry.java +``` java +class ActionHistory { + private final String title; + private final String description; + private final Date dateActioned; + + public ActionHistory(String title, String description){ + this.title = title; + this.description = description; + this.dateActioned = new Date(); + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public Date getDateActioned() { + return dateActioned; + } +} +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java +public class HomeTableView extends DefaultTableView { + + private static final String FXML = "HomeTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView homeTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static HomeTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + HomeTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new HomeTableView()); + ui.configure(logic); + return ui; + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.setSortable(false); + indexColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.setSortable(false); + nameColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.setSortable(false); + startDateColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.setSortable(false); + endDateColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.setSortable(false); + tagsColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + +``` +###### /java/harmony/mastermind/ui/HomeTableView.java +``` java + @Subscribe + public void highlightLastActionedRow(HighlightLastActionedRowRequestEvent event){ + homeTableView.getSelectionModel().select(event.task); + homeTableView.scrollTo(event.task); + } +} + +``` +###### /java/harmony/mastermind/ui/ActionHistoryPane.java +``` java + protected void initActionHistory() { + + actionHistory.setOnMouseClicked(value -> { + consoleOutput.setText(actionHistory.getSelectionModel().getSelectedItem().getDescription()); + }); + + actionHistory.setCellFactory(listView -> { + ListCell actionCell = new ListCell() { + + @Override + protected void updateItem(ActionHistory item, boolean isEmpty) { + super.updateItem(item, isEmpty); + + if (!isEmpty) { + + ActionHistoryEntry actionHistoryEntry = UiPartLoader.loadUiPart(new ActionHistoryEntry()); + + actionHistoryEntry.setTitle(item.getTitle().toUpperCase()); + actionHistoryEntry.setDate(item.getDateActioned().toString().toUpperCase()); + + if (item.getTitle().toUpperCase().equals("INVALID COMMAND")) { + actionHistoryEntry.setTypeFail(); + } else { + actionHistoryEntry.setTypeSuccess(); + } + + this.setGraphic(actionHistoryEntry.getNode()); + + this.setPrefHeight(50); + this.setPrefWidth(250); + + this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + } else { + this.setGraphic(null); + } + } + }; + + return actionCell; + }); + + + } +``` +###### /java/harmony/mastermind/ui/ActionHistoryPane.java +``` java + public void toggleActionHistory(){ + actionHistoryPane.setExpanded(!actionHistoryPane.isExpanded()); +} + + // @@A0138862W + protected void pushToActionHistory(String title, String description) { + ActionHistory aHistory = new ActionHistory(title, description); + + actionHistory.getItems().add(aHistory); + actionHistory.scrollTo(actionHistory.getItems().size()- 1); + } +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + // init only one parser for all parsing, save memory and computation time + private static final PrettyTime prettyTime = new PrettyTime(); +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { + + MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); + mainWindow.fillInnerParts(logic); + mainWindow.configure(config.getAppTitle(), config.getTaskManagerName(), config, prefs, logic); + return mainWindow; + } +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + /** + * Configure Tables and Tabs + * @param logic Logic Manager instance + */ + private void configureComponents(Logic logic) { + + // Configure sorting algorithm for tables + SortedList sortedTasks = logic.getFilteredTaskList().sorted(); + Comparator comparator = new TaskListComparator(); + sortedTasks.setComparator(comparator); + sortedTasks.comparatorProperty().bind(homeTableView.getTableView().comparatorProperty()); + + // define placeholder label for empty table + Label placeholder = new Label("What's on your mind?\nTry adding a new task by executing \"add\" command!"); + placeholder.setAlignment(Pos.CENTER); + placeholder.setTextAlignment(TextAlignment.CENTER); + + homeTableView.getTableView().setPlaceholder(placeholder); + homeTableView.getTableView().setItems(sortedTasks); + + tasksTableView.getTableView().setPlaceholder(placeholder); + tasksTableView.getTableView().setItems(logic.getFilteredFloatingTaskList()); + + eventsTableView.getTableView().setPlaceholder(placeholder); + eventsTableView.getTableView().setItems(logic.getFilteredEventList()); + + deadlinesTableView.getTableView().setPlaceholder(placeholder); + deadlinesTableView.getTableView().setItems(logic.getFilteredDeadlineList()); + + archivesTableView.getTableView().setPlaceholder(placeholder); + archivesTableView.getTableView().setItems(logic.getFilteredArchiveList()); + + tabPane.getSelectionModel().selectedItemProperty().addListener((tabList, fromTab, toTab)->{ + this.raise(new TabChangedEvent(fromTab.getId(), toTab.getId())); + }); + } + + public void hide() { + primaryStage.hide(); + } + + private void setTitle(String appTitle) { + primaryStage.setTitle(appTitle); + } + + /** + * Sets the default size based on user preferences. + */ + protected void setWindowDefaultSize(UserPrefs prefs) { + primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); + primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); + if (prefs.getGuiSettings().getWindowCoordinates() != null) { + primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); + primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); + } + } + + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + private void fillInnerParts(Logic logic) { + commandBox = CommandBox.load(primaryStage, commandBoxPlaceholder, logic); + actionHistoryPane = ActionHistoryPane.load(primaryStage, actionHistoryPanePlaceholder, logic); + homeTableView = HomeTableView.load(primaryStage, homeTableViewPlaceholder, logic); + + tasksTableView = TasksTableView.load(primaryStage, tasksTableViewPlaceholder, logic); + + eventsTableView = EventsTableView.load(primaryStage, eventsTableViewPlaceholder, logic); + + deadlinesTableView = DeadlinesTableView.load(primaryStage, deadlinesTableViewPlaceholder, logic); + + archivesTableView = ArchivesTableView.load(primaryStage, archivesTableViewPlaceholder, logic); + } +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + @Subscribe + private void handleNewResultAvailableEvent(NewResultAvailableEvent event){ + // updates the tab when a list command is called + this.updateTab(event.message); + logger.info("Update tab."); + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderIndexCell() { + return new TableCell() { + @Override + public void updateIndex(int index) { + super.updateIndex(index); + + Text indexText = new Text(Integer.toString(index + 1)+ "."); + indexText.getStyleClass().add("index-column"); + + if (isEmpty() || index < 0) { + this.setGraphic(null); + } else { + this.setGraphic(indexText); + } + } + + }; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderNameCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + + if (!isEmpty()) { + + VBox vBox = new VBox(3); + + Text taskName = generateStyledText(readOnlyTask, readOnlyTask.getName()); + taskName.getStyleClass().add("task-name-column"); + vBox.getChildren().add(taskName); + + generateSpecialTag(readOnlyTask, vBox); + + this.setGraphic(vBox); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + + }; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderStartDateCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getStartDate() != null) { + + TextFlow textFlow = generateStyledDate(readOnlyTask, readOnlyTask.getStartDate()); + + this.setGraphic(textFlow); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + + }; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderEndDateCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getEndDate() != null) { + + TextFlow textFlow = generateStyledDate(readOnlyTask, readOnlyTask.getEndDate()); + + this.setGraphic(textFlow); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + }; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + protected TableCell renderTagsCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getTags() != null) { + + HBox tags = new HBox(5); + + for (Tag tag : readOnlyTask.getTags()) { + Button tagBubble = new Button(); + tagBubble.setText(tag.tagName); + tagBubble.getStyleClass().add("tag"); + tags.getChildren().add(tagBubble); + } + + this.setGraphic(tags); + } else { + this.setGraphic(null); + } + } + }; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + /* + * Generate styled row base on the task status: due(red), happening(orange), + * normal(blue) + * + */ + protected Text generateStyledText(ReadOnlyTask readOnlyTask, String text) { + Text taskName = new Text(text); + + if (readOnlyTask.isHappening()) { + taskName.getStyleClass().add("happening"); + } else if (readOnlyTask.isDue()) { + taskName.getStyleClass().add("overdue"); + } else { + taskName.getStyleClass().add("normal"); + } + return taskName; + } + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + /** + * THis method generate Due, Happening, and duration depend on the nature of the task + * + * @param readOnlyTask the task object + * @param vBox The container + */ + protected void generateSpecialTag(ReadOnlyTask readOnlyTask, VBox vBox) { + HBox hBox = new HBox(5); + + Button status = new Button(); + if (readOnlyTask.isHappening()) { + status.setText("HAPPENING"); + status.getStyleClass().add("tag-happening"); + hBox.getChildren().add(status); + } else if (readOnlyTask.isDue()) { + status.setText("DUE"); + status.getStyleClass().add("tag-overdue"); + hBox.getChildren().add(status); + } + + if (readOnlyTask.isEvent()) { + Button eventDuration = new Button(); + eventDuration.setText("DURATION: " + DurationFormatUtils.formatDurationWords(readOnlyTask.getEventDuration().toMillis(), true, true).toUpperCase()); + eventDuration.getStyleClass().add("tag-event-duration"); + hBox.getChildren().add(eventDuration); + } else if (readOnlyTask.isDeadline() + && !readOnlyTask.isDue()) { + Button dueDuration = new Button(); + dueDuration.setText("DUE IN " + DurationFormatUtils.formatDurationWords(readOnlyTask.getDueDuration().toMillis(), true, true)); + dueDuration.getStyleClass().add("tag-due-duration"); + hBox.getChildren().add(dueDuration); + } + + vBox.getChildren().add(hBox); + } + + +``` +###### /java/harmony/mastermind/ui/DefaultTableView.java +``` java + /** + * + * This method generate the double-line text for date column + * + * @param readOnlyTask the task object + * @return TextFlow the styled text + */ + protected TextFlow generateStyledDate(ReadOnlyTask readOnlyTask, Date date) { + TextFlow textFlow = new TextFlow(); + + Text prettyDate = generateStyledText(readOnlyTask, prettyTime.format(date)); + prettyDate.getStyleClass().add("pretty-date"); + + Text lineBreak = new Text("\n\n"); + lineBreak.setStyle("-fx-font-size:2px;"); + + Text uglyDate = generateStyledText(readOnlyTask, readOnlyTask.parse(date)); + uglyDate.getStyleClass().add("ugly-date"); + + textFlow.getChildren().add(prettyDate); + textFlow.getChildren().add(lineBreak); + textFlow.getChildren().add(uglyDate); + return textFlow; + } + +} + +``` diff --git a/collated/main/A0138862Wunused.md b/collated/main/A0138862Wunused.md new file mode 100644 index 000000000000..6fc1ccc475d1 --- /dev/null +++ b/collated/main/A0138862Wunused.md @@ -0,0 +1,58 @@ +# A0138862Wunused +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** + * + * The builder constructor has taken care of all the construction of event, floating and deadline + * @see AddCommand(AddCommandBuilder) + * @deprecated + * + */ + /* + public AddCommand(String name, String endDateStr, Set tags, String recur, Memory mem) throws IllegalValueException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + Date createdDate = new Date(); + Date endDate = prettyTimeParser.parse(endDateStr).get(0); + + + this.toAdd = new Task(name, endDate, new UniqueTagList(tagSet), recur, createdDate); + + //Converting Date end to Calendar end + Calendar end = dateToCalendar(endDate); + + deadline = new GenericMemory(tags.toString(), name, "", end); + mem.add(deadline); + + } + */ + + // floating +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + /** + * The builder constructor has taken care of all the construction of event, floating and deadline + * @see AddCommand(AddCommandBuilder) + * @deprecated + * + */ + /* + public AddCommand(String name, Set tags, Memory mem) throws IllegalValueException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + + Date createdDate = new Date(); + + this.toAdd = new Task(name, new UniqueTagList(tagSet), createdDate); + + task = new GenericMemory(tags.toString(), name, ""); + mem.add(task); + } + */ + +``` diff --git a/collated/main/A0139194X.md b/collated/main/A0139194X.md new file mode 100644 index 000000000000..74d06578463e --- /dev/null +++ b/collated/main/A0139194X.md @@ -0,0 +1,660 @@ +# A0139194X +###### /java/harmony/mastermind/commons/util/FileUtil.java +``` java + /** + * Checks if directory is writable + * @param newFilePath + * @throws UnwrittableFolderException + */ + + public static void checkWrittableDirectory(String newFilePath) throws UnwrittableFolderException { + assert newFilePath != null; + File newFile = new File(newFilePath); + if (!(newFile.isDirectory() && newFile.canWrite())) { + throw new UnwrittableFolderException(newFilePath + " is not writtable."); + } + } + +``` +###### /java/harmony/mastermind/commons/util/FileUtil.java +``` java + /** + * Checks if file path string exists + * @param newFilePath + * @throws FolderDoesNotExistException + */ + public static void checkSaveLocation(String newFilePath) throws FolderDoesNotExistException { + assert newFilePath != null; + Path filePath = Paths.get(newFilePath); + if (!Files.exists(filePath)) { + throw new FolderDoesNotExistException(newFilePath + " does not exist"); + } + } +} +``` +###### /java/harmony/mastermind/commons/exceptions/UnwrittableFolderException.java +``` java +/* + * An exception say that a filepath has no write permission + */ +public class UnwrittableFolderException extends Exception { + + public UnwrittableFolderException(String message) { + super(message); + } + +} +``` +###### /java/harmony/mastermind/commons/events/storage/RelocateFilePathEvent.java +``` java +/* + * Event that holds the new file path the user wants to relocate to + */ +public class RelocateFilePathEvent extends BaseEvent { + + private final String newFilePath; + + public RelocateFilePathEvent(String newFilePath) { + this.newFilePath = newFilePath; + } + + @Override + public String toString() { + return "Change save location file path to: " + newFilePath; + } + + public String getFilePath() { + return this.newFilePath; + } +} +``` +###### /java/harmony/mastermind/commons/events/ui/ShowHelpRequestEvent.java +``` java + * An event requesting to view the help page. + */ +public class ShowHelpRequestEvent extends BaseEvent { + + private final ArrayList helpEntries; + + public ShowHelpRequestEvent(ArrayList helpEntries) { + this.helpEntries = helpEntries; + } + + public ArrayList getHelpEntries() { + return this.helpEntries; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### /java/harmony/mastermind/commons/core/Config.java +``` java + public void setAppTitle(String appTitle) { + assert appTitle != null; + this.appTitle = appTitle; + } + +``` +###### /java/harmony/mastermind/commons/core/Config.java +``` java + public void setUserPrefsFilePath(String userPrefsFilePath) { + assert userPrefsFilePath != null; + this.userPrefsFilePath = userPrefsFilePath; + } + +``` +###### /java/harmony/mastermind/commons/core/Config.java +``` java + public void setTaskManagerFilePath(String taskManagerFilePath) { + assert taskManagerFilePath != null; + this.taskManagerFilePath = taskManagerFilePath; + } + + public String getTaskManagerName() { + return taskManagerName; + } + +``` +###### /java/harmony/mastermind/commons/core/Config.java +``` java + public void setTaskManagerName(String taskManagerName) { + assert taskManagerName != null; + this.taskManagerName = taskManagerName; + } + + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + /** + * This method should only be called when the user entered a new command + * other than redo/undo + **/ + public void clearUndoHistory() { + undoHistory.clear(); + } + + @Override + public synchronized void deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + taskManager.removeTask(target); + indicateTaskManagerChanged(); + } + +``` +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public synchronized void relocateSaveLocation(String newFilePath) throws FolderDoesNotExistException { + raise(new RelocateFilePathEvent(newFilePath)); + indicateTaskManagerChanged(); + } + + + // =========== Methods for Recurring Tasks============================= + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + /** Relocates save location to given directory */ + void relocateSaveLocation(String directory) throws FolderDoesNotExistException; + + + /** push the command to undo history */ + void pushToUndoHistory(Undoable command); + +``` +###### /java/harmony/mastermind/storage/StorageManager.java +``` java + /** + * Handles RelocateFilePathEvent by first changing taskManger's file path, then moving + * the file over and deleting the old one + */ + @Subscribe + public void handleRelocateEvent(RelocateFilePathEvent event) { + assert event != null; + assert event.getFilePath() != null; + String oldPath = taskManagerStorage.getTaskManagerFilePath(); + String newPath = correctFilePathFormat(event.getFilePath()); + taskManagerStorage.setTaskManagerFilePath(newPath); + updateConfig(newPath); + try { + logger.info("Trying to move into new file path."); + taskManagerStorage.migrateIntoNewFolder(oldPath, newPath); + } catch (IOException e) { + logger.warning("Error occured while handling relocate event."); + logger.warning("Reverting save location back to " + oldPath); + taskManagerStorage.setTaskManagerFilePath(oldPath); + updateConfig(oldPath); + } + } + +``` +###### /java/harmony/mastermind/storage/StorageManager.java +``` java + //Appends the '/' if it is not that for a valid file path + public String correctFilePathFormat(String newPath) { + assert newPath != null; + if (newPath.endsWith("/")) { + newPath = newPath + "mastermind.xml"; + } else { + newPath = newPath + "/mastermind.xml"; + } + return newPath; + } + +``` +###### /java/harmony/mastermind/storage/StorageManager.java +``` java + /** + * Updates the config.json file so that upon startup, the correct xml file will be + * loaded. + * @param newPath + */ + public void updateConfig(String newPath) { + assert newPath != null; + Config config; + String defaultConfigLocation = Config.DEFAULT_CONFIG_FILE; + + try { + Optional configOptional = ConfigUtil.readConfig(defaultConfigLocation); + config = configOptional.orElse(new Config()); + } catch (DataConversionException e) { + logger.warning("Config file at " + defaultConfigLocation + " is not in the correct format. " + + "Using default config properties"); + config = new Config(); + } + config.setTaskManagerFilePath(newPath); + logger.fine("Updated config's data save location."); + + //Update config file in case it was missing to begin with or there are new/unused fields + try { + ConfigUtil.saveConfig(config, defaultConfigLocation); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } +} +``` +###### /java/harmony/mastermind/storage/JsonUserPrefStorage.java +``` java + public void setFilePath(String filePath) { + assert filePath != null; + logger.fine(String.format(SUCCESSFULLY_CHANGED_FILEPATH, filePath)); + this.filePath = filePath; + } +} +``` +###### /java/harmony/mastermind/logic/HelpPopupEntry.java +``` java + * Class to store entries into the Help Popup table. + */ +public class HelpPopupEntry { + private String commandWord; + private String format; + private String description; + + public HelpPopupEntry(String commandWord, String format, String description) { + this.commandWord = commandWord; + this.format = format; + this.description = description; + } + + public String getFormat() { + return format; + } + + public String getCommandWord() { + return commandWord; + } + + public String getDescription() { + return description; + } +} +``` +###### /java/harmony/mastermind/logic/commands/RelocateCommand.java +``` java + * Relocates save location to another file path + */ +public class RelocateCommand extends Command { + + public static final String COMMAND_WORD = "relocate"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes save location in MasterMind. " + + "Parameters: FILE_PATH\n" + + "Example: " + COMMAND_WORD + + "Desktop"; + + public static final String COMMAND_DESCRIPTION = "Change your data's save location"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + + public static final String MESSAGE_SUCCESS = "Relocated save location to %1$s"; + public static final String MESSAGE_INVALID_INPUT = "%1$s is not valid."; + public static final String MESSAGE_UNWRITTABLE_FOLDER = "%1$s is not writtable."; + + private final String newFilePath; + + /** +``` +###### /java/harmony/mastermind/logic/commands/RelocateCommand.java +``` java + * Convenience constructor using raw values. + */ + public RelocateCommand(String newFilePath) { + this.newFilePath = newFilePath.trim(); + } + +``` +###### /java/harmony/mastermind/logic/commands/RelocateCommand.java +``` java + @Override + public CommandResult execute() { + assert model != null; + assert newFilePath != null; + try { + FileUtil.checkSaveLocation(newFilePath); + FileUtil.checkWrittableDirectory(newFilePath); + model.relocateSaveLocation(newFilePath); + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_SUCCESS, newFilePath)); + } catch (FolderDoesNotExistException fdnee) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_INVALID_INPUT, newFilePath)); + } catch (UnwrittableFolderException ufe) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNWRITTABLE_FOLDER, newFilePath)); + } + } +} +``` +###### /java/harmony/mastermind/logic/commands/ClearCommand.java +``` java +/** + * Clears the task manager by reseting the data and clearing both undo and redo history + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Mastermind has been cleared!"; + public static final String COMMAND_DESCRIPTION = "Clearing all of Mastermind's data"; + + public ClearCommand() {} + + @Override + public CommandResult execute() { + assert model != null; + model.resetData(TaskManager.getEmptyTaskManager()); + + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS); + } +} +``` +###### /java/harmony/mastermind/logic/commands/HelpCommand.java +``` java + * Formats full help instructions for every command for display. + */ +public class HelpCommand extends Command { + + public static final String COMMAND_WORD = "help"; + + public static final String COMMAND_DESCRIPTION = "Shows program usage instructions"; + + public static final String COMMAND_SUMMARY = "Getting help:" + + "\n" + COMMAND_WORD; + + private ArrayList commandList; + private ArrayList formatList; + private ArrayList descriptionList; + private ArrayList helpEntries; + + public static final String SUCCESSFULLY_SHOWN = "Command summary displayed."; + + public HelpCommand() { + initInfo(); + } + + private void initInfo() { + //if its already initialised, don't redo it + if (helpEntries == null) { + initCommandWords(); + initFormat(); + initDescription(); + initEntries(); + } + } + + /** + * Initialise the Entries that will be sent to the UI component + */ + private void initEntries() { + helpEntries = new ArrayList(); + for (int i = 0; i < commandList.size(); i++) { + helpEntries.add(new HelpPopupEntry(commandList.get(i), formatList.get(i), descriptionList.get(i))); + } + } + + /** + * Consolidate all the command words + */ + private void initCommandWords() { + commandList = new ArrayList(); + commandList.add(HelpCommand.COMMAND_WORD); + commandList.add(AddCommand.COMMAND_KEYWORD_ADD + ", " + AddCommand.COMMAND_KEYWORD_DO); + commandList.add(EditCommand.COMMAND_KEYWORD_EDIT + ", " + + EditCommand.COMMAND_KEYWORD_UPDATE + ", " + + EditCommand.COMMAND_KEYWORD_CHANGE); + commandList.add(MarkCommand.COMMAND_WORD); + commandList.add(UnmarkCommand.COMMAND_WORD); + commandList.add(DeleteCommand.COMMAND_WORD); + commandList.add(UndoCommand.COMMAND_WORD); + commandList.add(RedoCommand.COMMAND_WORD); + commandList.add(ListCommand.COMMAND_WORD); + commandList.add(FindCommand.COMMAND_WORD); + commandList.add(FindTagCommand.COMMAND_WORD); + commandList.add(UpcomingCommand.COMMAND_WORD); + commandList.add(RelocateCommand.COMMAND_WORD); + commandList.add(ImportCommand.COMMAND_WORD); + commandList.add(ExportCommand.COMMAND_KEYWORD_EXPORT); + commandList.add(HistoryCommand.COMMAND_KEYWORD_ACTIONHISTORY); + commandList.add(ClearCommand.COMMAND_WORD); + commandList.add(ExitCommand.COMMAND_WORD); + } + + /** + * Consolidate all the formats for help + */ + private void initFormat() { + formatList = new ArrayList(); + formatList.add(HelpCommand.COMMAND_WORD); + formatList.add(AddCommand.COMMAND_FORMAT); + formatList.add(EditCommand.COMMAND_FORMAT); + formatList.add(MarkCommand.COMMAND_FORMAT); + formatList.add(UnmarkCommand.COMMAND_FORMAT); + formatList.add(DeleteCommand.COMMAND_FORMAT); + formatList.add(UndoCommand.COMMAND_WORD); + formatList.add(RedoCommand.COMMAND_WORD); + formatList.add(ListCommand.COMMAND_FORMAT); + formatList.add(FindCommand.COMMAND_FORMAT); + formatList.add(FindTagCommand.COMMAND_FORMAT); + formatList.add(UpcomingCommand.COMMAND_FORMAT); + formatList.add(RelocateCommand.COMMAND_FORMAT); + formatList.add(ImportCommand.COMMAND_FORMAT); + formatList.add(ExportCommand.COMMAND_FORMAT); + formatList.add(HistoryCommand.COMMAND_KEYWORD_ACTIONHISTORY); + formatList.add(ClearCommand.COMMAND_WORD); + formatList.add(ExitCommand.COMMAND_WORD); + } + + /** + * Consolidate all the descriptions for help + */ + private void initDescription() { + descriptionList = new ArrayList(); + descriptionList.add(HelpCommand.COMMAND_DESCRIPTION); + descriptionList.add(AddCommand.COMMAND_DESCRIPTION); + descriptionList.add(EditCommand.COMMAND_DESCRIPTION); + descriptionList.add(MarkCommand.COMMAND_DESCRIPTION); + descriptionList.add(UnmarkCommand.COMMAND_DESCRIPTION); + descriptionList.add(DeleteCommand.COMMAND_DESCRIPTION); + descriptionList.add(UndoCommand.COMMAND_DESCRIPTION); + descriptionList.add(RedoCommand.COMMAND_DESCRIPTION); + descriptionList.add(ListCommand.COMMAND_DESCRIPTION); + descriptionList.add(FindCommand.COMMAND_DESCRIPTION); + descriptionList.add(FindTagCommand.COMMAND_DESCRIPTION); + descriptionList.add(UpcomingCommand.COMMAND_DESCRIPTION); + descriptionList.add(RelocateCommand.COMMAND_DESCRIPTION); + descriptionList.add(ImportCommand.COMMAND_DESCRIPTION); + descriptionList.add(ExportCommand.COMMAND_DESCRIPTION); + descriptionList.add(HistoryCommand.COMMAND_DESCRIPTION); + descriptionList.add(ClearCommand.COMMAND_DESCRIPTION); + descriptionList.add(ExitCommand.COMMAND_DESCRIPTION); + } + +``` +###### /java/harmony/mastermind/logic/commands/HelpCommand.java +``` java + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ShowHelpRequestEvent(getEntries())); + return new CommandResult(COMMAND_WORD, SUCCESSFULLY_SHOWN); + } + +``` +###### /java/harmony/mastermind/logic/commands/HelpCommand.java +``` java + public ArrayList getEntries() { + return helpEntries; + } + +} +``` +###### /java/harmony/mastermind/ui/UiManager.java +``` java + @Subscribe + private void handleShowHelpEvent(ShowHelpRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + helpPopup.injectData(event.getHelpEntries()); + helpPopup.show(mainWindow.getNode()); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java +/** + * Class to initialise the Help Popup containing a table of information. + */ +public class HelpPopup extends UiPart { + + private static final String FXML = "HelpPopup.fxml"; + private static final Logger logger = LogsCenter.getLogger(HelpPopup.class); + + private final String COMMAND_COL_HEADER = "Command"; + private final String FORMAT_COL_HEADER = "Format"; + private final String DESCRIPTION_COL_HEADER = "Description"; + + private final int COMMAND_COL_MIN_WIDTH = 150; + private final int FORMAT_COL_MIN_WIDTH = 300; + private final int DESCRIPTION_COL_MIN_WIDTH = 400; + + private final int DEFAULT_X_POS = 200; + private final int DEFAULT_Y_POS = 100; + + private Popup popup; + private boolean isFirstKey; + private TableView table; + + TableColumn commandCol; + TableColumn formatCol; + TableColumn descriptionCol; + + ObservableList entries; + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + public HelpPopup() { + initTable(); + initPopup(); + isFirstKey = true; + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + public void show(Node node) { + assert node != null; + table.setItems(entries); + logger.fine("Displaying help Popup"); + popup.show(node, DEFAULT_X_POS, DEFAULT_Y_POS); + popup.centerOnScreen(); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + @Override + public String getFxmlPath() { + return FXML; + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + @FXML + private void initPopup() { + popup = new Popup(); + + popup.getContent().add(table); + + popup.addEventHandler(KeyEvent.KEY_RELEASED, keyEventHandler); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + * Initialise the table + */ + @FXML + private void initTable() { + logger.info("Initialising help popup's table"); + table = new TableView(); + table.setEditable(false); + + initCommandCol(); + initFormatCol(); + initDescriptionCol(); + + table.getColumns().addAll(commandCol, formatCol, descriptionCol); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + * Initialise the Command word Column + */ + private void initCommandCol() { + commandCol = new TableColumn(COMMAND_COL_HEADER); + commandCol.setMinWidth(COMMAND_COL_MIN_WIDTH); + commandCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getCommandWord())); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + * Initialise the format Column + */ + private void initFormatCol() { + formatCol = new TableColumn(FORMAT_COL_HEADER); + formatCol.setMinWidth(FORMAT_COL_MIN_WIDTH); + formatCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getFormat())); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + * Initialise the description Column + */ + private void initDescriptionCol() { + descriptionCol = new TableColumn(DESCRIPTION_COL_HEADER); + descriptionCol.setMinWidth(DESCRIPTION_COL_MIN_WIDTH); + descriptionCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getDescription())); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + //Handles the closing of the popup + @FXML + EventHandler keyEventHandler = new EventHandler() { + public void handle(KeyEvent event) { + if (!isFirstKey && event.getCode() != null) { + popup.hide(); + } + isFirstKey = !isFirstKey; + } + }; + + + /** +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + //Sets the data to display + public void injectData(ArrayList helpEntries) { + entries = FXCollections.observableArrayList(); + for (int i = 0; i < helpEntries.size(); i++) { + entries.add(helpEntries.get(i)); + } + logger.fine("Help Popup table entries injected and initialised succesfully"); + } +} +``` +###### /java/harmony/mastermind/ui/MainWindow.java +``` java + public Node getNode() { + return rootLayout; + } + +``` diff --git a/collated/main/A0143378.md b/collated/main/A0143378.md new file mode 100644 index 000000000000..1342ad30aa8d --- /dev/null +++ b/collated/main/A0143378.md @@ -0,0 +1,14 @@ +# A0143378 +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Setting up tasks + */ + public GenericMemory(String type, String name, String description) { + this.type = type; + this.name = name; + this.description = description; + this.state = 0; + } + +``` diff --git a/collated/main/A0143378Y.md b/collated/main/A0143378Y.md new file mode 100644 index 000000000000..87b0ee6ee03b --- /dev/null +++ b/collated/main/A0143378Y.md @@ -0,0 +1,1594 @@ +# A0143378Y +###### /java/harmony/mastermind/storage/StorageMemory.java +``` java + public static void saveToStorage(Memory memory) { + try { + PrintWriter pw = new PrintWriter(SAVE_FILE); + for (int i=0; i11); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * returns true is day is 0 or greater than 31 + */ + private static boolean invalidDay(int day){ + return (day<=0||day>31); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * returns true if time is set properly + */ + private static boolean setTimeIfHHMMSS(int hour, int minute, Calendar setEvent){ + setEvent.set(Calendar.HOUR_OF_DAY, hour); + setEvent.set(Calendar.MINUTE, minute); + if(hour == 23 && minute == 59){ + setEvent.set(Calendar.SECOND, 59); + }else{ + setEvent.set(Calendar.SECOND, 0); + } + return true; + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * returns true if minute is negative or more than 59 + */ + private static boolean invalidMinute(int minute){ + return (minute<0||minute>=60); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * returns true if hour is negative or more than 23 + */ + private static boolean invalidHour(int hour){ + return (hour<0||hour>=24); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * Check that the word is a command word for date + */ + protected static int isCommandWord(String word){ + switch(word){ + case STRING_DEADLINE: + return DEADLINE; + case STRING_BY: + return BY; + case STRING_UNTIL: + return UNTIL; + case STRING_TILL: + return TILL; + case STRING_BEFORE: + return BEFORE; + case STRING_BETWEEN: + return BETWEEN; + case STRING_TO: + return TO; + case STRING_FROM: + return FROM; + case STRING_ON: + return ON; + case STRING_AND: + return AND; + default: + return INVALID_STRING; + } + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * remove additional space between each word in case of typo + */ + protected static void removeAdditionalSpacesInCommand(){ + String[] temp = command.split(" "); + String newCommand = ""; + for (int i = 0; i < temp.length; i++){ + if(temp[i].length() != 0){ + newCommand = newCommand + temp[i] + " "; + } + } + setCommand(newCommand.trim()); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * Returns true if the command word is a command for start date + */ + protected static boolean isStartCommand(String word){ + return (isCommandWord(word)>= FROM && + isCommandWord(word)<= BETWEEN); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + /* + * Returns true if the command word is a command for end date + */ + protected static boolean isEndCommand(String word){ + return (isCommandWord(word)>= BY && + isCommandWord(word)<= AND); + } + + //author A0143378Y + private static void setDDMMYY(String[] details){ + day = Integer.parseInt(details[0]); + month = Integer.parseInt(details[1]); + year = Integer.parseInt(details[2]); + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + private static void initialiseDate(){ + day = INT_INVALID; + month = INT_INVALID; + year = INT_INVALID; + } + +``` +###### /java/harmony/mastermind/logic/parser/ParserMemoryMain.java +``` java + private static boolean checkIfDateIsNumeric(String[] details){ + for(int i = 0; i < 3; i++ ){ + if(!isNumeric(details[i])){ + return false; + } + } + + return true; + } +} +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + private Memory initializeMemory() { + Memory memory = new Memory(); + mem = memory; + memory.loadFromFile(memory); + return memory; + } +} +``` +###### /java/harmony/mastermind/logic/commands/AddCommand.java +``` java + private Calendar dateToCalendar(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal; + } + + +} +``` +###### /java/harmony/mastermind/logic/commands/DeleteCommand.java +``` java + /* + * Perform a delete by name operation + */ + public static void deleteDirectly(String name, Memory memory) { + ArrayList searchResult = FindCommand.searchExact(name, memory); + if (searchResult.size() == 1){ + deleteItem(searchResult.get(0), memory); + History.advance(memory); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/DeleteCommand.java +``` java + /* + * Delete given GenericMemory item from memory + */ + private static void deleteItem(GenericMemory item, Memory memory) { + assert item != null; + memory.remove(item); + } +} +``` +###### /java/harmony/mastermind/logic/commands/Sort.java +``` java + /* Sort all items in memory by urgency. + * Items arranged by Event, Deadline, Task + * Event sorted by earliest start date followed by end date + * Deadline sorted by earliest due date + * Task sorted by alphabet + */ + public static void sort(ArrayList list) { + task = new ArrayList(); + deadline = new ArrayList(); + event = new ArrayList(); + splitMemory(list); + + sortAllList(); + + joinList(list); + } + +``` +###### /java/harmony/mastermind/logic/commands/Sort.java +``` java + /* + * Reform list by adding Event, Deadline, Task items in sorted order + */ + private static void joinList(ArrayList list) { + list.removeAll(list); + assert list.size() == 0; + for (int i = 0; i < event.size(); i++) { + list.add(event.get(i)); + } + + for (int i = 0; i < deadline.size(); i++) { + list.add(deadline.get(i)); + } + + for (int i = 0; i < task.size(); i++) { + list.add(task.get(i)); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/Sort.java +``` java + /* + * Sorts the 3 lists + */ + private static void sortAllList() { + Collections.sort(task); + Collections.sort(deadline); + Collections.sort(event); + } + +``` +###### /java/harmony/mastermind/logic/commands/Sort.java +``` java + /* + * Split all items into their own list for sorting + */ + public static void splitMemory(ArrayList list) { + for (int i = 0; i < list.size(); i++) { + switch (list.get(i).getType()) { + case "Task": + task.add(list.get(i)); + break; + + case "Deadline": + deadline.add(list.get(i)); + break; + + case "Event": + event.add(list.get(i)); + break; + } + } + } +} +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + /* + * Creates a snapshot of changes in the memory and moves previous state into the back stack. forward stack is reinitialized + */ + public static void advance(Memory memory) { + forward = new Stack>(); + back.push(current); + current = new ArrayList(); + duplicateMemory(memory); + } + +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + /* + * Duplicates memory into the current ArrayList + */ + private static void duplicateMemory(Memory memory) { + for (int i = 0; i< memory.getSize(); i++) { // Duplicating of memory into snapshot + current.add(new GenericMemory(memory.get(i).getType(), + memory.get(i).getName(), + memory.get(i).getDescription(), + memory.get(i).getStart(), + memory.get(i).getEnd(), + memory.get(i).getState())); + } + } + + /* + * Unused code: + * Reason: For future implementations of Memory +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + // Swaps memory used for redo + private static void redoMemorySwap(Memory memory) { + assert forward.size() > 0; + back.push(current); + current = forward.pop(); + memory.setList(current); + } + +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + // Retrieves most recent snapshot in the back stack and swap it with current memory. Current memory gets pushed into forward stack + public static void undo(Memory memory) { + if (back.isEmpty() || (back.peek() == null)) { + System.out.println("Nothing to undo!"); + return; + } + + ArrayList temp = duplicateTemp(memory); + undoMemorySwap(memory, temp); + StorageMemory.saveToStorage(memory); + System.out.println("Undo successful."); + } + +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + // Swaps memory used for undo + private static void undoMemorySwap(Memory memory, ArrayList temp) { + forward.push(temp); + current = back.pop(); + assert current != null; + memory.setList(current); + } + +``` +###### /java/harmony/mastermind/logic/commands/History.java +``` java + // Duplicate current memory into temp ArrayList + private static ArrayList duplicateTemp(Memory memory) { + ArrayList temp = new ArrayList(); // Duplicating of memory into forward stack + for (int i = 0; i < memory.getSize(); i++) { + temp.add(new GenericMemory(memory.get(i).getType(), + memory.get(i).getName(), + memory.get(i).getDescription(), + memory.get(i).getStart(), + memory.get(i).getEnd(), + memory.get(i).getState())); + } + return temp; + } + */ + +} +``` +###### /java/harmony/mastermind/logic/commands/FindCommand.java +``` java + /* + * Checks if memory item contains the keyword. + * If contains, add to exactResult + */ + private static void containsKeyword(String keyword, Memory memory, ArrayList exactResult, int i) { + if (memory.get(i).getName().equals(keyword)) { // found exact matching name item + exactResult.add(memory.get(i)); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/FindCommand.java +``` java + /* + * Only for delete and update direct commands to search for exact matching names + * Should only return list of 1 item ideally + */ + public static ArrayList searchExact(String keyword, Memory memory) { + assert keyword.length() != 0; + ArrayList exactResult = new ArrayList(); + for (int i=0; i searchTerms(String[] keywords, Memory memory) { + ArrayList result = new ArrayList(memory.getList()); + findResult = recursiveSearchTerms(keywords, 0, result); // calls recursive search to narrow down results + if (findResult.size() >= 1) { + assert findResult.size() > 1; + String title = formatSearchTitle(keywords); + ListCommand.displayList(findResult, title); + } + + return findResult; + } + +``` +###### /java/harmony/mastermind/logic/commands/FindCommand.java +``` java + /* + * Formats search title with keywords used to perform the search + */ + private static String formatSearchTitle(String[] keywords) { + String title = "Search Keywords Result: " + keywords[0]; + for (int i=1; i recursiveSearchTerms(String[] keywords, int index, ArrayList result) { + ArrayList newResult = new ArrayList(); + if (index >= keywords.length) { // Base case: no more terms to search + return result; + } else { // Narrowing down results + assert index < keywords.length; + for (int i=0; i < result.size(); i++) { + if (result.get(i).getName().contains(keywords[index])) { // Found term in name + newResult.add(result.get(i)); + } else if ((result.get(i).getDescription()!= null) && result.get(i).getDescription().contains(keywords[index])) { // has description and found term in description + newResult.add(result.get(i)); + } + } + return recursiveSearchTerms(keywords, index+1, newResult); + } + } + +``` +###### /java/harmony/mastermind/logic/commands/FindCommand.java +``` java + public static ArrayList findDate(Calendar date, Memory memory) { + assert date != null; + findResult = new ArrayList(); + for (int i=0; i < memory.getSize(); i++) { + Calendar start = memory.get(i).getStart(); + Calendar end = memory.get(i).getEnd(); + + if (testTwoCalendar(date, start) || // Checks if given date is equals or between item's start and end date + testTwoCalendar(date, end) || + (date.after(start) && date.before(end)) ) { + findResult.add(memory.get(i)); + } + } + + return findResult; + } + +``` +###### /java/harmony/mastermind/logic/commands/FindCommand.java +``` java + /* + * Returns true if two dates are the same + */ + private static boolean testTwoCalendar(Calendar a, Calendar b) { + if (b == null) { + return false; + } else { + assert (a != null) && (b != null); + return (a.get(Calendar.YEAR)== b.get(Calendar.YEAR))&& + (a.get(Calendar.MONTH) == b.get(Calendar.MONTH))&& + (a.get(Calendar.DATE)== b.get(Calendar.DATE)); + } + } + +} +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Adds a given GenericMemory item into the memory ArrayList + * Updates quick view and saves to file + */ + public void add(GenericMemory item){ + assert item != null; + + String type = item.getType(); + + memory.add(item); + StorageMemory.saveToStorage(this); + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Returns GenericMemory object at index + */ + public GenericMemory get(int index){ + return memory.get(index); + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Returns ArrayList of GenericMemory used by memory + */ + public ArrayList getList(){ + return memory; + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Returns number of GenericMemory in memory (size) + */ + public int getSize(){ + return memory.size(); + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Loads GenericMemory from save file into memory + */ + public void loadFromFile(Memory memory){ + assert memory != null; + StorageMemory.checkForFileExists(memory); + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Removes given item from memory + */ + public void remove(GenericMemory item){ + assert item != null; + + String type = item.getType(); + + memory.remove(item); + StorageMemory.saveToStorage(this); + } + +``` +###### /java/harmony/mastermind/memory/Memory.java +``` java + /* + * Swap out ArrayList used by memory with new given list + */ + public void setList(ArrayList list){ + memory = list; + } + +} +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Setting up deadlines + */ + public GenericMemory(String type, String name, String description, Calendar end) { + this.type = type; + this.name = name; + this.description = description; + this.end = end; + this.state = 0; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Events and constructor used to load from storage + */ + public GenericMemory(String type, String name, String description, Calendar startDate, Calendar end, int state) { + this.type = type; + this.name = name; + this.description = description; + this.start = startDate; + this.end = end; + this.state = state; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns type of the to do item + */ + public String getType() { + return type; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns name of the to do item + */ + public String getName() { + return name; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns description of the to do item + */ + public String getDescription() { + return description; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns Calendar start of the to do item + */ + public Calendar getStart() { + return start; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns Calendar end of the to do item + */ + public Calendar getEnd() { + return end; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns the state of the to do item + */ + public int getState() { + return state; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Initializes start calendar - having a real calendar instead of hard coding everything + */ + public void initStart(){ + start = new GregorianCalendar(); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Initializes end calendar + */ + public void initEnd(){ + end = new GregorianCalendar(); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set type of to do item + */ + public void setType(String type) { + this.type = type; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set name of to do item + */ + public void setName(String name) { + this.name = name; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set description of to do item + */ + public void setDescription(String description) { + this.description = description; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set start time of to do item using hours and minutes + */ + public void setStartTime(int hourOfDay, int minute) { + start.set(Calendar.HOUR_OF_DAY, hourOfDay); + start.set(Calendar.MINUTE, minute); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set start time of to do item using hours, minutes and seconds + */ + public void setStartTime(int hourOfDay, int minute, int second) { + start.set(Calendar.HOUR_OF_DAY, hourOfDay); + start.set(Calendar.MINUTE, minute); + start.set(Calendar.SECOND, second); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set end time of to do item using hours and minutes + */ + public void setEndTime(int hourOfDay, int minute) { + end.set(Calendar.HOUR_OF_DAY, hourOfDay); + end.set(Calendar.MINUTE, minute); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set end time of to do item using hours, minutes and seconds + */ + public void setEndTime(int hourOfDay, int minute, int second) { + end.set(Calendar.HOUR_OF_DAY, hourOfDay); + end.set(Calendar.MINUTE, minute); + end.set(Calendar.SECOND, second); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set start date of to do item + */ + public void setStartDate(int year, int month, int date) { + start.set(Calendar.YEAR, year); + start.set(Calendar.MONTH, month); + start.set(Calendar.DATE, date); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set end date of to do item + */ + public void setEndDate(int year, int month, int date) { + end.set(Calendar.YEAR, year); + end.set(Calendar.MONTH, month); + end.set(Calendar.DATE, date); + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Set state of to do item + */ + public void setState(int newstate) { + this.state = newstate; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* Converts GenericEvents object into string representation + * Outputs in the format + * Name + * Description + * Start + * End + * State + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String output = TYPE_STRING + getType() + + NAME_STRING + getName(); + output = descriptionToString(output); + + if (getType().equals(TASK)) { // Task + output = taskDeadlineStateToString(output); + return output; + } + + if (getType().equals(DEADLINE)) { // Deadline + output = deadlineDateToString(output); + output = taskDeadlineStateToString(output); + return output; + } + + if (getType().equals(EVENT)) { // Event + output = eventDatesToString(output); + output = eventStateToString(output); + return output; + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Converts description into string representation + */ + public String descriptionToString(String output) { + if (description != null) { // If description exists + output += DESCRIPTION_STRING + getDescription(); + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Converts due date into string representation + */ + public String deadlineDateToString(String output) { + if (end != null) { + output += DUE_BY + getDate(end) + " " + getTime(end); + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Converts event start and end dates into string representation + */ + public String eventDatesToString(String output) { + if (start != null) { + output += START_STRING + getDate(start) + " " + getTime(start); + } + if (end != null) { + output += END_STRING + getDate(end) + " " + getTime(end); + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Converts event state into string representation + */ + public String eventStateToString(String output) { + if (getState() == 0) { // Printing of state into string + output+= STATUS_UPCOMING; + } else if (getState() == 1) { + output+= STATUS_OVER; + } else { + output+= STATUS_ONGOING; + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Converts task or deadline state into string representation + */ + public String taskDeadlineStateToString(String output) { + if (getState() == 0) { // Printing of state into string + output+= STATUS_INCOMPLETE; + } else if (getState() == 1) { + output+= STATUS_COMPLETED; + } else { + output+= STATUS_OVERDUE; + } + return output; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns string representation of date as DD/MM/YY + */ + public static String getDate(Calendar item){ + if(item != null){ + return item.get(Calendar.DATE) + "/" + + (item.get(Calendar.MONTH) + 1) + "/" + + (item.get(Calendar.YEAR)%100) + " " + + dayOfTheWeek(item); + }else{ + return null; + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Returns string representation of time as HH:MM AM/PM + */ + public static String getTime(Calendar item){ + if(item != null){ + return hour(item) + ":" + + min(item) + " " + + AM_PM(item); + }else{ + return null; + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Return string representation of day of the week of calendar object + */ + public static String dayOfTheWeek(Calendar item){ + int dayOfTheWeek = item.get(Calendar.DAY_OF_WEEK); + + switch(dayOfTheWeek){ + case Calendar.MONDAY: + return MON; + + case Calendar.TUESDAY: + return TUES; + + case Calendar.WEDNESDAY: + return WED; + + case Calendar.THURSDAY: + return THURS; + + case Calendar.FRIDAY: + return FRI; + + case Calendar.SATURDAY: + return SAT; + + case Calendar.SUNDAY: + return SUN; + } + return null; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Check item for AM/PM and return the correct time period + */ + public static String AM_PM(Calendar item){ + if(item.get(Calendar.AM_PM) == Calendar.AM){ + return AM; + }else{ + return PM; + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Return the hour of time to form of HH + */ + public static String hour(Calendar item){ + if(item.get(Calendar.HOUR_OF_DAY)==12){ + return "12"; + }else if( item.get(Calendar.HOUR)<10){ + return "0" + item.get(Calendar.HOUR); + }else{ + return Integer.toString(item.get(Calendar.HOUR)); + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Return minute of time to form of MM + */ + public static String min(Calendar item){ + if( item.get(Calendar.MINUTE)<10){ + return "0" + item.get(Calendar.MINUTE); + }else{ + return Integer.toString(item.get(Calendar.MINUTE)); + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* Comparator between to do item and o + * Only valid when comparing items of the same type + * eg. Task vs Task, Deadline vs Deadline, Event vs Event + * Task: Compare names lexicographically, ignoring case differences -> alphabetical order + * Deadline: Compare due dates + * Event: Compare start dates, else end dates + */ + public int compareTo(GenericMemory o) { + if (this.start == null && this.end == null && o.start == null && o.end == null){ //both task + return this.name.compareToIgnoreCase(o.name); + } + + if (this.start == null && this.end != null && o.start == null && o.end != null){ //both deadline + return this.end.compareTo(o.end); + } + + if (this.start != null && this.end != null && o.start != null && o.end != null){ //both event + return eventCompare(o); + } + return 0; + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Compare's start date followed by end dates + */ + int eventCompare(GenericMemory o) { + if (this.start.compareTo(o.start) != 0) { + return this.start.compareTo(o.start); + } else { + return this.end.compareTo(o.end); + } + } + +``` +###### /java/harmony/mastermind/memory/GenericMemory.java +``` java + /* + * Return state of the item in the form of a string + */ + public String getStateType(){ + + if(type.equals(DEADLINE)|| type.equals(TASK)){ + if(state == INT_INCOMPLETE){ + return INCOMPLETE; + }else if (state == INT_COMPLETED){ + return COMPLETED; + }else if (state == INT_OVERDUE){ + return OVERDUE; + } + } + if(type.equals(EVENT)){ + if(state == INT_UPCOMING){ + return UPCOMING; + }else if(state == INT_OVER){ + return OVER; + }else if (state == INT_ONGOING){ + return ONGOING; + } + } + return null; + } +} +``` +###### /java/harmony/mastermind/ui/CommandBox.java +``` java + @FXML + private void initAutoComplete() { + // Autocomplete function + Collections.addAll(listOfWords, words); + autoCompletionBinding = TextFields.bindAutoCompletion(commandField, listOfWords); + autoCompletionBinding.setPrefWidth(500); + autoCompletionBinding.setVisibleRowCount(5); + autoCompletionBinding.setHideOnEscape(true); + + } + +``` +###### /java/harmony/mastermind/ui/CommandBox.java +``` java + // This function takes in whatever the user has "ENTER"-ed, and save in a + // dictionary of words + // These words will be in the autocomplete list of words + private void learnWord(String text) { + listOfWords.add(text); + + if (autoCompletionBinding != null) { + autoCompletionBinding.dispose(); + } + + autoCompletionBinding = TextFields.bindAutoCompletion(commandField, listOfWords); + } + +``` diff --git a/collated/main/A0143378Yunused.md b/collated/main/A0143378Yunused.md new file mode 100644 index 000000000000..a7790eb84ec1 --- /dev/null +++ b/collated/main/A0143378Yunused.md @@ -0,0 +1,42 @@ +# A0143378Yunused +###### /java/harmony/mastermind/logic/commands/History.java +``` java + // Retrieves most recent snapshot in the forward stack and swap it with current memory. Current memory gets pushed into back stack + public static void redo(Memory memory) { + if (forward.isEmpty() || (forward.peek() == null)) { + System.out.println("Nothing to redo!"); + return; + } + + redoMemorySwap(memory); + StorageMemory.saveToStorage(memory); + System.out.println("Redo successful."); + } + +``` +###### /java/harmony/mastermind/ui/HelpPopup.java +``` java + * Styling for TextArea. Unused because we initially use a TextArea contained in a popup. + * But we changed it to a TableView instead. + */ + /* + public void properties() { + //Setting up the width and height + content.setPrefHeight(DEFAULT_HEIGHT); + content.setPrefWidth(DEFAULT_WIDTH); + + //Setting up wrapping of text in the content box + content.setWrapText(true); + + //Setting up the background, font and borders + content.setStyle("-fx-background-color: #00BFFF;" + + "-fx-padding:10px;" + + "-fx-text-fill: #000080;" + + "-fx-font-family: Fantasy;" + + "-fx-alignment: center" + + "-fx-font-size: 20px" + ); + } + */ + +``` diff --git a/collated/main/generated.md b/collated/main/generated.md new file mode 100644 index 000000000000..3dae99d17747 --- /dev/null +++ b/collated/main/generated.md @@ -0,0 +1,191 @@ +# generated +###### /java/harmony/mastermind/model/ModelManager.java +``` java + @Override + public void updateFilteredList(Set keywords) { + updateFilteredList(new PredicateExpression(new NameQualifier(keywords))); + indicateTaskManagerChanged(); + } + +``` +###### /java/harmony/mastermind/model/Model.java +``` java + UnmodifiableObservableList getFilteredTaskList(); + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public String getName() { + return name; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public void setName(String name) { + this.name = name; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public UniqueTagList getTags() { + return tags; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Date getStartDate() { + return startDate; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public Date getEndDate() { + return endDate; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + +``` +###### /java/harmony/mastermind/model/task/Task.java +``` java + public void setTags(UniqueTagList tags) { + this.tags = tags; + } + + @Override +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public String getName() { + return name; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public Date getStartDate() { + return startDate; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public Date getEndDate() { + return endDate; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public Date getCreatedDate() { + return createdDate; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public UniqueTagList getTags() { + return tags; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public String getRecur() { + return recur; + } +``` +###### /java/harmony/mastermind/model/task/TaskBuilder.java +``` java + public boolean isMarked() { + return isMarked; + } + + + +} +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + public void setTasks(List tasks) { + this.tasks.getInternalList().sort(comparator); + this.tasks.getInternalList().setAll(tasks); + } + +``` +###### /java/harmony/mastermind/model/TaskManager.java +``` java + public void setTags(Collection tags) { + this.tags.getInternalList().setAll(tags); + } + +``` +###### /java/harmony/mastermind/storage/XmlSerializableTaskManager.java +``` java + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + +} +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Returns the specified index in the {@code command} IF a positive unsigned + * integer is given as the index. Returns an {@code Optional.empty()} + * otherwise. + */ + private Optional parseIndex(String command) { + final Matcher matcher = TASK_INDEX_ARGS_FORMAT.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String index = matcher.group("targetIndex"); + if (!StringUtil.isUnsignedInteger(index)) { + return Optional.empty(); + } + return Optional.of(Integer.parseInt(index)); + + } + + +``` +###### /java/harmony/mastermind/logic/parser/Parser.java +``` java + /** + * Parses arguments in the context of the find task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareFind(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new FindCommand(keywordSet, mem); + } + + +``` diff --git a/collated/test/A0124797R.md b/collated/test/A0124797R.md new file mode 100644 index 000000000000..875509f1ce42 --- /dev/null +++ b/collated/test/A0124797R.md @@ -0,0 +1,1057 @@ +# A0124797R +###### /java/harmony/mastermind/testutil/TaskBuilder.java +``` java + public TaskBuilder withName(String name) { + this.task.setName(name); + return this; + } + +``` +###### /java/harmony/mastermind/testutil/TaskBuilder.java +``` java + public TaskBuilder withStartDate(String date) { + this.task.setStartDate(date); + return this; + } + +``` +###### /java/harmony/mastermind/testutil/TaskBuilder.java +``` java + public TaskBuilder withEndDate(String date) { + this.task.setEndDate(date); + return this; + } + +``` +###### /java/harmony/mastermind/testutil/TaskBuilder.java +``` java + public TaskBuilder withRecur(String recur) { + this.task.setRecur(recur); + return this; + } + + public TaskBuilder withTags(String... tags) throws IllegalValueException{ + for (String tagName : tags) { + task.getTags().add(new Tag(tagName)); + } + return this; + } + + public TaskBuilder withTags(Set tags) throws IllegalValueException{ + for (String tagName : tags) { + task.getTags().add(new Tag(tagName)); + } + return this; + } + + public TaskBuilder mark() { + return this.mark(); + + } + + public TestTask build() { + return this.task; + } + +} +``` +###### /java/harmony/mastermind/testutil/TypicalTestTasks.java +``` java +/** + * to generate tasks for testing purposes + */ +public class TypicalTestTasks { + + public static TestTask task1, task2, task3, task4, task5, task6, task7, task8, task9, task10, task11, task12; + + public TypicalTestTasks() { + + try { + task1 = new TaskBuilder().withName("complete cs2103 lecture quiz") + .withEndDate("25 oct at 2359") + .withTags("homework").build(); + task2 = new TaskBuilder().withName("cs2105 assignment") + .withStartDate("23 oct 1pm").withEndDate("23 oct 5pm") + .withTags("homework").build(); + task3 = new TaskBuilder().withName("laundry") + .withTags("chores").build(); + task4 = new TaskBuilder().withName("finish assignment").build(); + + + //manual inputs + task5 = new TaskBuilder().withName("past year papers") + .withTags("examPrep").build(); + task6 = new TaskBuilder().withName("sweep floor").build(); + + //completed tasks + task7 = new TaskBuilder().withName("lecture").build(); + task8 = new TaskBuilder().withName("submit PR").withEndDate("22 oct at 2359").build(); + + //reurring inputs + task9 = new TaskBuilder().withName("pick up grocery") + .withEndDate("23 Oct 6pm").withRecur("weekly").build(); + task10 = new TaskBuilder().withName("pick up grocery") + .withEndDate("30 Oct 6pm").withRecur("weekly").build(); + + //deadlines within a week + task11 = new TaskBuilder().withName("dinner with parents") + .withEndDate("tomorrow").build(); + + //events within a week + task12 = new TaskBuilder().withName("feed my pet hamster") + .withStartDate("tomorrow 5pm").withEndDate("tomorrow 6pm").build(); + + } catch (IllegalValueException e) { + assert false : "should not reach here"; + } + + } + + public static void loadTaskManagerWithSampleData(TaskManager tm) { + + try { + tm.addTask(new Task(task1)); + tm.addTask(new Task(task2)); + tm.addTask(new Task(task3)); + tm.addTask(new Task(task4)); + tm.addTask(new Task(task7)); + tm.markTask(new Task(task7)); + tm.addTask(new Task(task8)); + tm.markTask(new Task(task8)); + + } catch (UniqueTaskList.DuplicateTaskException e) { + assert false : "Should not reach here"; + } catch (TaskNotFoundException e) { + assert false : "task is added before marking"; + } + } + + public TestTask[] getTypicalTasks() { + return new TestTask[]{task1, task2, task3, task4}; + } + + public TestTask[] getTypicalArchivedTasks() { + return new TestTask[]{task7.mark(),task8.mark()}; + } + +``` +###### /java/harmony/mastermind/testutil/TestTask.java +``` java +public class TestTask implements ReadOnlyTask { + + private String name; + private String startDate; + private String endDate; + private String recur; + private String createdDate; + private UniqueTagList tags; + private boolean isMarked; + + public TestTask() { + tags = new UniqueTagList(); + } + + public void setName(String name) { + this.name = name; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public void setCreateDate(String createdDate) { + this.createdDate = createdDate; + } + + public void setRecur(String recur) { + this.recur = recur; + } + + +``` +###### /java/harmony/mastermind/testutil/TestTask.java +``` java + @Override + public Date getStartDate() { + if (startDate!=null) { + return TaskManagerGuiTest.prettyTimeParser.parse(startDate).get(0); + }else { + return null; + } + } + + @Override + public Date getEndDate() { + if (endDate!=null) { + return TaskManagerGuiTest.prettyTimeParser.parse(endDate).get(0); + }else { + return null; + } + } + + @Override + public Date getCreatedDate() { + if (createdDate==null){ + return null; + } + return TaskManagerGuiTest.prettyTimeParser.parse(createdDate).get(0); + } + + @Override + public String getRecur() { + return recur; + } + + @Override + public boolean isRecur() { + return recur!=null; + } + + @Override + public boolean isFloating() { + return startDate == null && endDate == null; + } + + @Override + public boolean isDeadline() { + return startDate == null && endDate != null; + } + + @Override + public boolean isEvent() { + return startDate != null && endDate != null; + } + + @Override + public boolean isMarked() { + return this.isMarked; + } + + @Override + public UniqueTagList getTags() { + return tags; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Task // instanceof handles nulls + && this.toString().equals(((Task) other).toString())); // state check + + } + + @Override + public boolean isSameTask(ReadOnlyTask task) { + return this.toString().equals(((Task) task).toString()); + } + + @Override + public String toString() { + return getAsText(); + } + + public TestTask mark() { + this.isMarked = true; + return this; + } + +``` +###### /java/harmony/mastermind/testutil/TestUtil.java +``` java + private static Tag[] getSampleTagData() { + try { + return new Tag[]{ + new Tag("homework"), + new Tag("finals") + }; + } catch (IllegalValueException e) { + assert false; + return null; + //not possible + } + } + +``` +###### /java/harmony/mastermind/testutil/TestUtil.java +``` java + public static boolean compareTasks(ReadOnlyTask t1, ReadOnlyTask t2) { + return t1.isSameTask(t2); + + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void readAndSaveTaskManager_allInOrder_success() throws Exception { + String filePath = testFolder.getRoot().getPath() + "TempTaskManager.xml"; + TypicalTestTasks td = new TypicalTestTasks(); + TaskManager original = td.getTypicalTaskManager(); + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(filePath); + + //Save in new file and read back + xmlTaskManagerStorage.saveTaskManager(original, filePath); + ReadOnlyTaskManager readBack = xmlTaskManagerStorage.readTaskManager(filePath).get(); + assertEquals(original, new TaskManager(readBack)); + + //Modify data, overwrite exiting file, and read back + original.addTask(new Task(TypicalTestTasks.task5)); + original.removeTask(new Task(TypicalTestTasks.task3)); + xmlTaskManagerStorage.saveTaskManager(original, filePath); + readBack = xmlTaskManagerStorage.readTaskManager(filePath).get(); + + assertEquals(original, new TaskManager(readBack)); + + //Save and read without specifying file path + original.addTask(new Task(TypicalTestTasks.task6)); + xmlTaskManagerStorage.saveTaskManager(original); //file path not specified + readBack = xmlTaskManagerStorage.readTaskManager().get(); //file path not specified + assertEquals(original, new TaskManager(readBack)); + + } + +``` +###### /java/guitests/guihandles/CommandBoxHandle.java +``` java + public void getPreviousCommand() { + pressUp(); + } + + public void getNextCommand() { + pressDown(); + } +``` +###### /java/guitests/guihandles/TabPaneHandle.java +``` java +public class TabPaneHandle extends GuiHandle { + + private static final String TAB_PANE_ID = "#tabPane"; + + public TabPaneHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public String getCurrentTab() { + return getTabPane().getSelectionModel().getSelectedItem().getId(); + } + + private TabPane getTabPane() { + return (TabPane) getNode(TAB_PANE_ID); + + } + + + +} +``` +###### /java/guitests/guihandles/GuiHandle.java +``` java + public void pressUp() { + guiRobot.type(KeyCode.UP).sleep(50); + } + + public void pressDown() { + guiRobot.type(KeyCode.DOWN).sleep(50); + } + +``` +###### /java/guitests/guihandles/TaskListPanelHandle.java +``` java + public boolean isArchivedListMatching(ReadOnlyTask... tasks) { + return this.isArchivedListMatching(0, tasks); + } + + /** + * Clicks on the ListView. + */ + public void clickOnListView() { + Point2D point= TestUtil.getScreenMidPoint(getTableView()); + guiRobot.clickOn(point.getX(), point.getY()); + } + + /** + * Returns true if the {@code tasks} appear as the sub list (in that order) at position {@code startPosition}. + */ + public boolean containsInOrderHome(int startPosition, ReadOnlyTask... tasks) { + List tasksInList = getTableView().getItems(); + + // Return false if the list in panel is too short to contain the given list + if (startPosition + tasks.length > tasksInList.size()){ + return false; + } + + // Return false if any of the tasks doesn't match + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getName().equals(tasks[i].getName())){ + return false; + } + } + + return true; + } + + /** + * Returns true if the {@code tasks} appear as the sub list (in that order) at position {@code startPosition}. + */ +``` +###### /java/guitests/guihandles/TaskListPanelHandle.java +``` java + public boolean containsInOrderArchive(int startPosition, ReadOnlyTask... tasks) { + List tasksInList = getArchiveTableView().getItems(); + + // Return false if the list in panel is too short to contain the given list + if (startPosition + tasks.length > tasksInList.size()){ + return false; + } + + // Return false if any of the tasks doesn't match + for (int i = 0; i < tasks.length; i++) { + if (!tasksInList.get(startPosition + i).getName().equals(tasks[i].getName())){ + return false; + } + } + + return true; + } + + /** + * Returns true if the list is showing the task details correctly and in correct order. + * @param startPosition The starting position of the sub list. + * @param tasks A list of task in the correct order. + */ + public boolean isListMatching(int startPosition, ReadOnlyTask... tasks) throws IllegalArgumentException { + List table = getTableView().getItems(); + if (tasks.length + startPosition != table.size()) { + throw new IllegalArgumentException("List size mismatched\n" + + "Expected " + (table.size() - 1) + " tasks"); + } + + assertTrue(this.containsInOrderHome(startPosition, tasks)); + + for (int i = 0; i < tasks.length; i++) { + final int scrollTo = i + startPosition; + guiRobot.interact(() -> getTableView().scrollTo(scrollTo)); + guiRobot.sleep(200); + + if (!TestUtil.compareTasks(table.get(i), tasks[i])) { + return false; + } + } + return true; + } + + /** + * Returns true if the list is showing the task details correctly and in correct order. + * @param startPosition The starting position of the sub list. + * @param tasks A list of task in the correct order. + */ +``` +###### /java/guitests/guihandles/TaskListPanelHandle.java +``` java + public boolean isArchivedListMatching(int startPosition, ReadOnlyTask... tasks) throws IllegalArgumentException { + List table = getArchiveTableView().getItems(); + if (tasks.length + startPosition != table.size()) { + throw new IllegalArgumentException("List size mismatched\n" + + "Expected " + (table.size() - 1) + " tasks"); + } + + assertTrue(this.containsInOrderArchive(startPosition, tasks)); + + for (int i = 0; i < tasks.length; i++) { + final int scrollTo = i + startPosition; + guiRobot.interact(() -> getArchiveTableView().scrollTo(scrollTo)); + guiRobot.sleep(200); + + if (!TestUtil.compareTasks(table.get(i), tasks[i])) { + return false; + } + } + return true; + } + + + public void navigateToTask(String name) { + guiRobot.sleep(500); //Allow a bit of time for the list to be updated + final Optional task = getTableView().getItems().stream().filter(p -> p.getName().equals(name)).findAny(); + + + int index = getTaskIndex(task.get()); + + guiRobot.interact(() -> { + getTableView().scrollTo(index); + guiRobot.sleep(150); + getTableView().getSelectionModel().select(index); + }); + guiRobot.sleep(100); + + } + + + /** + * Returns the position of the task given, {@code NOT_FOUND} if not found in the list. + */ + public int getTaskIndex(ReadOnlyTask targetTask) { + List tasksInList = getTableView().getItems(); + + for (int i = 0; i < tasksInList.size(); i++) { + String name = tasksInList.get(i).getName(); + + if(name.equals(targetTask.getName())){ + return i; + } + } + + return NOT_FOUND; + } + + /** + * Gets a task from the list by index + */ + public ReadOnlyTask getTask(int index) { + return getTableView().getItems().get(index); + } + + public int getNumberOfTask() { + return getTableView().getItems().size(); + } +} +``` +###### /java/guitests/FindCommandTest.java +``` java + @Test + public void find_nonEmptyList() { + assertFindResult("find Mark"); //no results + assertFindResult("find assignment", td.task2, td.task4); //multiple results + + //find after deleting one result + commandBox.runCommand("delete 1"); + assertFindResult("find assignment",td.task4); + + assertListSize(1); // should only contain task3 + assertResultMessage("1 tasks listed!"); + assertTrue(taskListPanel.isListMatching(td.task4)); + } + + @Test + public void find_emptyList(){ + commandBox.runCommand("clear"); + assertFindResult("find Marking"); //no results + } + + @Test + public void find_invalidCommand_fail() { + commandBox.runCommand("findassignment"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND+": findassignment"); + } + + + /** + * Checks if find command list the correct number of tasks as the expectedHits + * @param command + * @param expectedHits + */ + private void assertFindResult(String command, TestTask... expectedHits ) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +``` +###### /java/guitests/DeleteCommandTest.java +``` java + @Test + public void delete() { + + //delete the first in the list + TestTask[] currentList = td.getTypicalTasks(); + int targetIndex = 1; + assertDeleteSuccess(targetIndex, currentList); + + //delete the last in the list + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); + targetIndex = currentList.length; + assertDeleteSuccess(targetIndex, currentList); + + //delete from the middle of the list + currentList = TestUtil.removeTaskFromList(currentList, targetIndex); + targetIndex = currentList.length/2; + assertDeleteSuccess(targetIndex, currentList); + + //invalid index + commandBox.runCommand("delete " + (currentList.length + 1)); + assertResultMessage("The task index provided is invalid"); + + } + + /** + * Runs the delete command to delete the task at specified index and confirms the result is correct. + * @param targetIndexOneIndexed e.g. to delete the first task in the list, 1 should be given as the target index. + * @param currentList A copy of the current list of tasks (before deletion). + */ + private void assertDeleteSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToDelete = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); + + commandBox.runCommand("delete " + targetIndexOneIndexed); + + //confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete)); + } + +} +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importcsv_readFailure_malformedHeader(){ + // malformed headers + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/malformHeader.csv"); + this.assertResultMessage(ImportCommand.MESSAGE_CSV_READ_FAILURE); + // should not add any task in + this.assertListSize(0); + + } + +``` +###### /java/guitests/UpcomingCommand.java +``` java + @Test + public void upcoming_dueTasks_emptyList() { + //remove floating tasks + commandBox.runCommand("delete 3"); + commandBox.runCommand("delete 3"); + TestTask[] emptyTask = new TestTask[]{}; + + assertUpcomingSuccess("", emptyTask); + + + } + + @Test + public void upcoming_deadline_listDeadlines() { + TestTask[] expectedList = new TestTask[] {TypicalTestTasks.task11}; + commandBox.runCommand(TypicalTestTasks.task11.getAddCommand()); + + assertUpcomingSuccess(" deadlines", expectedList); + + + } + + @Test + public void upcoming_event_listEvents() { + TestTask[] expectedList = new TestTask[] {TypicalTestTasks.task12}; + commandBox.runCommand(TypicalTestTasks.task12.getAddCommand()); + + assertUpcomingSuccess(" events", expectedList); + + + } + + + private void assertUpcomingSuccess(String args, final TestTask[] expectedList) { + commandBox.runCommand("upcoming" + args); + + assertTrue(taskListPanel.isListMatching(expectedList)); + + } +} +``` +###### /java/guitests/CommandBoxTest.java +``` java + @Test + public void commandBox_commandSucceeds_textCleared() { + commandBox.runCommand(TypicalTestTasks.task4.getAddCommand()); + assertCommandBox(EMPTY_INPUT); + } + + @Test + public void commandBox_commandFail_textNotCleared() { + String invalidCommand = "delete assignment"; + commandBox.runCommand(invalidCommand); + assertCommandBox(invalidCommand); + } + + @Test + public void getPreviousCommand_noPreviousInput_noChangeInCommandBox() { + commandBox.getPreviousCommand(); + assertCommandBox(EMPTY_INPUT); + } + + @Test + public void getPreviousCommand_withOneInput_previousInputLoaded() { + commandBox.runCommand(TypicalTestTasks.task5.getAddCommand()); + assertCommandBox(EMPTY_INPUT); + + commandBox.getPreviousCommand(); + assertCommandBox(TypicalTestTasks.task5.getAddCommand()); + + commandBox.getPreviousCommand(); + assertCommandBox(TypicalTestTasks.task5.getAddCommand()); + } + + @Test + public void previousCommand_withMultipleInput_commandBoxChangeAccordingly() { + commandBox.runCommand(TypicalTestTasks.task5.getAddCommand()); + commandBox.runCommand(TypicalTestTasks.task6.getAddCommand()); + commandBox.runCommand("undo"); + + assertCommandBox(EMPTY_INPUT); + + commandBox.getPreviousCommand(); + assertCommandBox("undo"); + + commandBox.getPreviousCommand(); + assertCommandBox(TypicalTestTasks.task6.getAddCommand()); + + commandBox.getPreviousCommand(); + assertCommandBox(TypicalTestTasks.task5.getAddCommand()); + + // get back first input + commandBox.getNextCommand(); + commandBox.getNextCommand(); + commandBox.getNextCommand(); + assertCommandBox(EMPTY_INPUT); + } + +} +``` +###### /java/guitests/ListCommandTest.java +``` java + @Test + public void list() { + + //start with the Home tab + String targetTab = "Home"; + assertCurrentTab(targetTab); + + //list floating tasks + targetTab = "Tasks"; + assertListSuccess(targetTab); + + //list events + targetTab = "Events"; + assertListSuccess(targetTab); + + //list deadlines + targetTab = "Deadlines"; + assertListSuccess(targetTab); + + //list archives + targetTab = "Archives"; + assertListSuccess(targetTab); + + //ensure the list command is not case sensitive + targetTab = "Events"; + commandBox.runCommand("list events"); + assertCurrentTab(targetTab); + + //list an invalid tab + targetTab = "event"; + commandBox.runCommand("list " + targetTab); + assertResultMessage(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + /** + * Runs the list command to show the specific tab and confirms the current tab is correct. + * param tab e.g. to show the Events tab, events should be given as the target tab. + */ + private void assertListSuccess(String targetTab) { + commandBox.runCommand("list " + targetTab); + + //confirm the current view is in the correct tab + assertCurrentTab(targetTab); + } + +} +``` +###### /java/guitests/ClearCommandTest.java +``` java + @Test + public void clear() { + + //verify a non-empty list can be cleared + assertTrue(taskListPanel.isListMatching(td.getTypicalTasks())); + assertClearCommandSuccess(); + + //verify other commands can work after a clear command + commandBox.runCommand(td.task1.getAddCommand()); + assertTrue(taskListPanel.isListMatching(td.task1)); + commandBox.runCommand("delete 1"); + assertListSize(0); + + //verify clear command works when the list is empty + assertClearCommandSuccess(); + } + + private void assertClearCommandSuccess() { + commandBox.runCommand("clear"); + assertListSize(0); + assertResultMessage("Mastermind has been cleared!"); + } +} +``` +###### /java/guitests/EditCommandTest.java +``` java + @Test + public void editUndoRedo_EditNameTag_Success() { + + //edit the 4th task in the list + TestTask[] currentList = td.getTypicalTasks(); + int targetIndex = 4; + String editArgs = "name to past year papers, tags to #examPrep"; + assertEditSuccess(targetIndex, editArgs, currentList); + + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(currentList)); + + commandBox.runCommand("redo"); + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndex); + expectedRemainder = TestUtil.addTasksToList(expectedRemainder, TypicalTestTasks.task5); + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + } + + + + /** + * Runs the edit command to edit the task at specified index and confirms the result is correct. + * @param targetIndex e.g. to edit the first task in the list, 1 should be given as the target index. + * @param currentList A copy of the current list of tasks (before edit). + */ + private void assertEditSuccess(int targetIndex, String argsToEdit, final TestTask[] currentList) { + TestTask taskToEdit = currentList[targetIndex-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndex); + expectedRemainder = TestUtil.addTasksToList(expectedRemainder, TypicalTestTasks.task5); + + commandBox.runCommand("edit " + targetIndex + argsToEdit); + + //confirm the list now contains the updated task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(EditCommand.MESSAGE_EDIT_TASK_PROMPT,taskToEdit)); + } +} +``` +###### /java/guitests/AddCommandTest.java +``` java + @Test + public void add() { + //add one floating task with tags + TestTask[] currentList = td.getTypicalTasks(); + TestTask taskToAdd = TypicalTestTasks.task5; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + //add another floating task without tags + taskToAdd = TypicalTestTasks.task6; + assertAddSuccess(taskToAdd, currentList); + currentList = TestUtil.addTasksToList(currentList, taskToAdd); + + //add to empty list + commandBox.runCommand("clear"); + assertAddSuccess(TypicalTestTasks.task3); + + //invalid command + commandBox.runCommand("adds Laundry"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND+": adds Laundry"); + } + + private void assertAddSuccess(TestTask taskToAdd, TestTask... currentList) { + commandBox.runCommand(taskToAdd.getAddCommand()); + + //confirm the list now contains all previous tasks plus the new task + TestTask[] expectedList = TestUtil.addTasksToList(currentList, taskToAdd); + assertTrue(taskListPanel.isListMatching(expectedList)); + + } + +} +``` +###### /java/guitests/UnmarkCommandTest.java +``` java + @Test + public void unmark() { + + //Invalid Tab + TestTask[] currentList = td.getTypicalArchivedTasks(); + commandBox.runCommand("unmark 1"); + assertResultMessage(MESSAGE_UNMARK_FAILURE); + + //unmark the first task in the archived list + //ensure that is on correct tab + commandBox.runCommand("list archives"); + + int targetIndex = 2; + assertUnmarkSuccess(targetIndex, currentList); + + //undo Unmark command + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isArchivedListMatching(currentList)); + + commandBox.runCommand("redo"); + TestTask[] expectedList = TestUtil.removeTaskFromList(currentList, targetIndex); + assertTrue(taskListPanel.isArchivedListMatching(expectedList)); + + //invalid index + commandBox.runCommand("unmark " + (currentList.length + 1)); + assertResultMessage(MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + +``` +###### /java/guitests/UnmarkCommandTest.java +``` java + private void assertUnmarkSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToUnmark = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); + + commandBox.runCommand("unmark " + targetIndexOneIndexed); + + //confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isArchivedListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(MESSAGE_UNMARK_SUCCESS, taskToUnmark)); + } + +} + +``` +###### /java/guitests/MarkCommandTest.java +``` java +public class MarkCommandTest extends TaskManagerGuiTest { + + @Test + public void mark_nonRecurringTask_success() { + + //marks first task + TestTask[] currentList = td.getTypicalTasks(); + int targetIndex = 4; + assertMarkSuccess(targetIndex, currentList); + + //undo Mark command + commandBox.runCommand("undo"); + assertTrue(taskListPanel.isListMatching(currentList)); + + //redo Mark command + commandBox.runCommand("redo"); + TestTask[] expectedList = TestUtil.removeTaskFromList(currentList, targetIndex); + assertTrue(taskListPanel.isListMatching(expectedList)); + + //invalid index + commandBox.runCommand("mark " + (currentList.length + 1)); + assertResultMessage(MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void mark_recurringTask_addNextRecurTask() { + //add a recurring task and mark + TestTask[] currentList = td.getTypicalTasks(); + commandBox.runCommand(TypicalTestTasks.task9.getAddCommand()); + currentList = TestUtil.addTasksToList(currentList, TypicalTestTasks.task9); + assertTrue(taskListPanel.isListMatching(currentList)); + + int targetIndex = 5; + assertMarkRecurSuccess(targetIndex, currentList); + + } + + @Test + public void mark_TaskAlreadyMarked_markFailure() { + commandBox.runCommand("list archives"); + commandBox.runCommand("mark 1"); + + assertResultMessage(MESSAGE_MARK_FAILURE); + + } + + @Test + public void markDue_dueTasks_markAllDueTask() { + TestTask[] expectedList = {TypicalTestTasks.task3, TypicalTestTasks.task4}; + + commandBox.runCommand("mark due"); + assertTrue(taskListPanel.isListMatching(expectedList)); + assertResultMessage(MESSAGE_MARK_DUE_SUCCESS); + + } + +``` +###### /java/guitests/MarkCommandTest.java +``` java + private void assertMarkSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToMark = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); + + commandBox.runCommand("mark " + targetIndexOneIndexed); + + //confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(MESSAGE_MARK_SUCCESS, taskToMark)); + } + + private void assertMarkRecurSuccess(int targetIndexOneIndexed, final TestTask[] currentList) { + TestTask taskToMark = currentList[targetIndexOneIndexed-1]; //-1 because array uses zero indexing + TestTask[] expectedRemainder = TestUtil.removeTaskFromList(currentList, targetIndexOneIndexed); + expectedRemainder = TestUtil.addTasksToList(expectedRemainder, td.task10); + + commandBox.runCommand("mark " + targetIndexOneIndexed); + + //confirm the list now contains all previous tasks except the deleted task + assertTrue(taskListPanel.isListMatching(expectedRemainder)); + + //confirm the result message is correct + assertResultMessage(String.format(MESSAGE_MARK_SUCCESS, taskToMark)); + } + +} +``` +###### /java/guitests/FindTagCommandTest.java +``` java + @Test + public void find_nonEmptyList() { + assertFindTagResult("findtag test"); //no results + assertFindTagResult("findtag homework", TypicalTestTasks.task1, TypicalTestTasks.task2); //multiple results + + //find after deleting one result + commandBox.runCommand("delete 1"); + assertFindTagResult("findtag homework", TypicalTestTasks.task2); + + assertListSize(1); // should only contain task1 + assertResultMessage("1 tasks listed!"); + assertTrue(taskListPanel.isListMatching(TypicalTestTasks.task2)); + } + + @Test + public void findTag_emptyList(){ + commandBox.runCommand("clear"); + assertFindTagResult("findtag Marking"); //no results + } + + @Test + public void findTag_invalidCommand_fail() { + commandBox.runCommand("findtagdinner"); + assertResultMessage(Messages.MESSAGE_UNKNOWN_COMMAND+": findtagdinner"); + } + + /** + * Checks if findtag command list the correct number of tasks as the expectedHits + * @param command + * @param expectedHits + */ + private void assertFindTagResult(String command, TestTask... expectedHits ) { + commandBox.runCommand(command); + assertListSize(expectedHits.length); + assertResultMessage(expectedHits.length + " tasks listed!"); + assertTrue(taskListPanel.isListMatching(expectedHits)); + } +} +``` diff --git a/collated/test/A0138862W.md b/collated/test/A0138862W.md new file mode 100644 index 000000000000..ee7e8cef5789 --- /dev/null +++ b/collated/test/A0138862W.md @@ -0,0 +1,513 @@ +# A0138862W +###### /java/harmony/mastermind/testutil/TestTask.java +``` java + public String getAddCommand() { + StringBuffer cmd = new StringBuffer(); + + cmd.append("add"); + + cmd.append(" ").append(this.name); + if(startDate != null && endDate !=null){ + cmd.append(" from ").append(startDate); + cmd.append(" to ").append(endDate); + }else if(endDate!=null){ + cmd.append(" by ").append(endDate); + } + cmd.append(" #"); + + for (Tag t: tags) { + cmd.append(t.tagName); + cmd.append(','); + } + + cmd.deleteCharAt(cmd.length()-1); + + if (recur != null) { + cmd.append(recur); + } + + return cmd.toString(); + } + + @Override + public String getName() { + return name; + } + +``` +###### /java/harmony/mastermind/testutil/TestUtil.java +``` java + /** + * Generate List of Task object given with the number specified + * + * @param numOfTasks: Number of tasks to generate + * @return List of Task + */ + public static List getSampleTaskList(int numOfTasks){ + List sampleTaskList = new ArrayList<>(); + for(int i = 0; i < numOfTasks; i++){ + TaskBuilder taskBuilder = new TaskBuilder(); + sampleTaskList.add(new Task(taskBuilder.withName("Task"+i).build())); + } + return sampleTaskList; + } +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undoAndRedo_add() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task toBeAdded = helper.task(); + String timeCheckEnd = toBeAdded.parseForConsole(endDate); + + logic.execute(helper.generateAddCommand(toBeAdded)); + + assertCommandBehavior("undo", "Undo successfully.\n" + + "=====Undo Details=====\n" + + "[Undo Add Command] Task deleted: task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2]\n" + + "==================", + model.getTaskManager(), + model.getTaskManager().getTaskList()); + + assertCommandBehavior("redo", "Redo successfully.\n" + + "=====Redo Details=====\n" + + "[Redo Add Command] Task added: task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2]\n" + + "==================", + model.getTaskManager(), + model.getTaskManager().getTaskList()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_delete() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task toBeEdited = helper.task(); + String timeCheckEnd = toBeEdited.parseForConsole(endDate); + List oneTask = helper.generateTaskList(toBeEdited); + TaskManager expectedTM = helper.generateTaskManager(oneTask); + ListexpectedList = oneTask; + + helper.addToModel(model, oneTask); + + logic.execute("delete 1"); + + assertCommandBehavior("undo", + "Undo successfully.\n" + + "=====Undo Details=====\n" + + "[Undo Delete Command] Task added: task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2]\n" + + "==================", + expectedTM, + expectedList); + + assertCommandBehavior("redo", + "Redo successfully.\n" + + "=====Redo Details=====\n" + + "[Redo Delete Command] Deleted Task: task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2]\n" + + "==================", + model.getTaskManager(), + model.getListToMark()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_mark() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task toBeEdited = helper.task(); + String timeCheckEnd = toBeEdited.parseForConsole(endDate); + List oneTask = helper.generateTaskList(toBeEdited); + TaskManager expectedTM = helper.generateTaskManager(oneTask); + ListexpectedList = oneTask; + + helper.addToModel(model, oneTask); + + logic.execute("mark 1"); + + assertCommandBehavior("undo", + "Undo successfully.\n" + + "=====Undo Details=====\n" + + "[Undo Mark Command] task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2] has been unmarked\n" + + "==================", + expectedTM, + expectedList); + + assertCommandBehavior("redo", + "Redo successfully.\n" + + "=====Redo Details=====\n" + + "[Redo Mark Command] task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2] has been archived\n" + + "==================", + model.getTaskManager(), + model.getListToMark()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_unmark() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task toBeEdited = helper.task(); + String timeCheckEnd = toBeEdited.parseForConsole(endDate); + List oneTask = helper.generateTaskList(toBeEdited); + TaskManager expectedTM = helper.generateTaskManager(oneTask); + ListexpectedList; + + helper.addToModel(model, oneTask); + + logic.execute("mark 1"); + + logic.execute("unmark 1"); + + assertCommandBehavior("undo", + "Undo successfully.\n" + + "=====Undo Details=====\n" + + "[Undo Mark Command] task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2] has been unmarked\n" + + "==================", + model.getTaskManager(), + model.getListToMark()); + + assertCommandBehavior("redo", + "Redo successfully.\n" + + "=====Redo Details=====\n" + + "[Redo Mark Command] task " + + "end:" + timeCheckEnd + " " + + "Tags: [tag1],[tag2] has been archived\n" + + "==================", + model.getTaskManager(), + model.getListToMark()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_invalidAddTaskNotFound() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task toBeAdded = helper.task(); + + logic.execute(helper.generateAddCommand(toBeAdded)); + + model.deleteTask(toBeAdded); + + assertCommandBehavior("undo", "Undo successfully.\n" + + "=====Undo Details=====\n" + + "Task could not be found in Mastermind\n" + + "==================", + model.getTaskManager(), + model.getTaskManager().getTaskList()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_invalidEditDuplicate() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task task1 = helper.generateTaskWithName("edited2 task name"); + Task task2 = helper.generateTaskWithName("edited2 task name"); + + List twoTasks = helper.generateTaskList(task1); + TaskManager expectedTM = helper.generateTaskManager(twoTasks); + ListexpectedList = twoTasks; + + model.addTask(task1); + + logic.execute(helper.generateEditCommand()); + + model.getTaskManager().getUniqueTaskList().getInternalList().add(task1); + model.getTaskManager().getUniqueTaskList().getInternalList().add(task2); + + assertCommandBehavior("undo", "Undo successfully.\n" + + "=====Undo Details=====\n" + + "This task already exists in Mastermind\n" + + "==================", + model.getTaskManager(), + model.getFilteredTaskList()); + } + + @Test +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + public void execute_undo_invalidDeleteDuplicate() throws Exception{ + TestDataHelper helper = new TestDataHelper(); + Task task1 = helper.generateTaskWithName("edited2 task name"); + Task task2 = helper.generateTaskWithName("edited2 task name"); + + List twoTasks = helper.generateTaskList(task1); + + model.addTask(task1); + + logic.execute("delete 1"); + + model.getTaskManager().getUniqueTaskList().getInternalList().add(task1); + model.getTaskManager().getUniqueTaskList().getInternalList().add(task2); + + assertCommandBehavior("undo", "Undo successfully.\n" + + "=====Undo Details=====\n" + + "This task already exists in Mastermind\n" + + "==================", + model.getTaskManager(), + model.getFilteredTaskList()); + } +``` +###### /java/guitests/ImportCommandTest.java +``` java +public class ImportCommandTest extends TaskManagerGuiTest{ + + + @Before + public void before(){ + this.commandBox.runCommand("clear"); + this.assertListSize(0); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_successWithEmptyTaskList(){ + // run the importics command + // sample.ics contains 3 items + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.ics"); + this.assertResultMessage("Import ics success."); + + // resulting task list in Mastermind should have 3 items + this.assertListSize(3); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_successWithExisitingTaskList(){ + // initilize with an existing tasks + this.commandBox.runCommand("add Exisiting Task"); + this.assertListSize(1); + + // run the importics command + // sample.ics contains 3 items + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.ics"); + this.assertResultMessage("Import ics success."); + + // resulting task list in Mastermind should have 4 items + this.assertListSize(4); + } +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_failure_invalidFilePath(){ + // invalid file path + this.commandBox.runCommand("import from /invalid/file/path"); + this.assertResultMessage("Invalid command format!\n" + ImportCommand.MESSAGE_USAGE); + + // resulting task list in Mastermind should have 0 items still + this.assertListSize(0); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_failure_malformedFileContent(){ + // malformed file content + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/empty.ics"); + this.assertResultMessage("Failed to import ics."); + + // resulting task list in Mastermind should have 0 items still + this.assertListSize(0); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_failure_startDateIsAfterEndDate(){ + + // the malformed date the second task + // expect only the first task entry is successfully imported + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/startDateIsAfterEndDate.ics"); + this.assertResultMessage("Failed to import ics."); + + // resulting task list in Mastermind should have only 1 item + this.assertListSize(1); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importics_failure_importTwiceCauseDuplicateException(){ + + // expect first import successful + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.ics"); + this.assertResultMessage("Import ics success."); + this.assertListSize(3); + + // expect second import failure + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.ics"); + this.assertResultMessage("Failed to import ics. Duplicate task detected when importing."); + this.assertListSize(3); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importcsv_successWithEmptyTaskList(){ + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.csv"); + this.assertResultMessage("Import success: 5 tasks added"); + this.assertListSize(5); + } + + @Test + public void importcsv_successWithExisitingTaskList(){ + // initilize with an existing tasks + this.commandBox.runCommand("add Exisiting Task"); + this.assertListSize(1); + + // run the importcsv command + // sample.ics contains 5 items + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.csv"); + this.assertResultMessage("Import success: 5 tasks added"); + + // resulting task list in Mastermind should have 6 items + this.assertListSize(6); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importcsv_partialSuccess_malformedEntry(){ + // malformed at 1st and 3rd entry + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/malform.csv"); + this.assertResultMessage("Import failure: 4 tasks added \nInvalid lines: 4"); + + // ignore 3rd entry + // resulting task list in Mastermind should have 3 items + this.assertListSize(4); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importcsv_failure_startDateIsAfterEndDate(){ + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/startDateIsAfterEndDate.csv"); + this.assertResultMessage("Import failure: 0 tasks added \nInvalid lines: 2"); + + // resulting task list in Mastermind should have only 0 item + this.assertListSize(0); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importcsv_failure_importTwiceCauseDuplicateException(){ + + // expect first import successful + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.csv"); + this.assertResultMessage("Import success: 5 tasks added"); + this.assertListSize(5); + + // expect second import failure + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.csv"); + this.assertResultMessage("Import failure: 0 tasks added \nInvalid lines: 2,3,4,5,6"); + this.assertListSize(5); + } + +``` +###### /java/guitests/ExportCommandTest.java +``` java +public class ExportCommandTest extends TaskManagerGuiTest{ + +``` +###### /java/guitests/ExportCommandTest.java +``` java + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + +``` +###### /java/guitests/ExportCommandTest.java +``` java + @Before + public void before(){ + this.commandBox.runCommand("clear"); + this.assertListSize(0); + + //add a floating task + this.commandBox.runCommand("add a floating task"); + this.assertListSize(1); + + //add a deadline + this.commandBox.runCommand("add a deadline by next friday 7pm"); + this.assertListSize(2); + + //add an event + this.commandBox.runCommand("add an event from tomorrow 8pm to next monday 7pm"); + this.assertListSize(3); + } + +``` +###### /java/guitests/ExportCommandTest.java +``` java + @Test + public void export_success() throws IOException{ + final long EXPECTED_FILE_LENGTH_ALLCSV = 271; + final long EXPECTED_FILE_LENGTH_TASKSCSV = 155; + final long EXPECTED_FILE_LENGTH_DEADLINESEVENTSCSV = 208; + + // export all tasks + File allCsv = testFolder.newFile("all.csv"); + this.commandBox.runCommand("export to "+allCsv.getAbsolutePath()); + this.assertResultMessage("CSV exported."); + assertTrue(allCsv.exists()); + assertEquals(EXPECTED_FILE_LENGTH_ALLCSV, allCsv.length()); + + // export only floating tasks + File tasksCsv = testFolder.newFile("tasks.csv"); + this.commandBox.runCommand("export tasks to "+tasksCsv.getAbsolutePath()); + this.assertResultMessage("CSV exported."); + assertTrue(tasksCsv.exists()); + assertEquals(EXPECTED_FILE_LENGTH_TASKSCSV, tasksCsv.length()); + + // export deadlines and events + File deadlinesEventsCsv = testFolder.newFile("deadlines-events.csv"); + this.commandBox.runCommand("export deadlines events to "+deadlinesEventsCsv.getAbsolutePath()); + this.assertResultMessage("CSV exported."); + assertTrue(deadlinesEventsCsv.exists()); + assertEquals(EXPECTED_FILE_LENGTH_DEADLINESEVENTSCSV, deadlinesEventsCsv.length()); + } + +``` +###### /java/guitests/ExportCommandTest.java +``` java + @Test + public void export_failure_invalidFilePath(){ + this.commandBox.runCommand("export to /invalid/file/path"); + this.assertResultMessage("Failed to export CSV."); + } + + + +} +``` diff --git a/collated/test/A0138862Wunused.md b/collated/test/A0138862Wunused.md new file mode 100644 index 000000000000..88e982e7fe84 --- /dev/null +++ b/collated/test/A0138862Wunused.md @@ -0,0 +1,52 @@ +# A0138862Wunused +###### /java/guitests/ImportCommandTest.java +``` java + //removed importing of txt file + /* + @Test + public void importtxt_successWithEmptyTaskList(){ + // run the importics command + // sample.ics contains 3 items + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.txt"); + this.assertResultMessage("Import success: 3 tasks added"); + + // resulting task list in Mastermind should have 3 items + this.assertListSize(3); + } + + +``` +###### /java/guitests/ImportCommandTest.java +``` java + @Test + public void importtxt_partialSuccess_containOtherCommand(){ + // run the importics command + // sample.ics contains 3 items + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/containOtherCommand.txt"); + this.assertResultMessage("Import failure: 1 tasks added \nInvalid lines: 2,3"); + + // resulting task list in Mastermind should have 3 items + this.assertListSize(1); + } + +``` +###### /java/guitests/ImportCommandTest.java +``` java + //removed importing of txt file + + @Test + public void importtxt_failure_importTwiceCauseDuplicateException(){ + + // expect first import successful + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.txt"); + this.assertResultMessage("Import success: 3 tasks added"); + this.assertListSize(3); + + // expect second import failure + this.commandBox.runCommand("import from ./src/test/data/ImportCommandTest/sample.txt"); + this.assertResultMessage("Import failure: 0 tasks added \nInvalid lines: 1,2,3"); + this.assertListSize(3); + } + */ +} +``` diff --git a/collated/test/A0139194X.md b/collated/test/A0139194X.md new file mode 100644 index 000000000000..f60ea40eba59 --- /dev/null +++ b/collated/test/A0139194X.md @@ -0,0 +1,479 @@ +# A0139194X +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + private Config config; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Before + public void setup() { + config = new Config(); + } + + @Test + public void toString_defaultObject_stringReturned() { + String defaultConfigAsString = "App title : Mastermind\n" + + "Current log level : INFO\n" + + "Preference file Location : preferences.json\n" + + "Local data file location : data/mastermind.xml\n" + + "TaskManager name : MyTaskManager"; + + assertEquals(defaultConfigAsString, new Config().toString()); + } + + @Test + public void equalsMethod(){ + Config defaultConfig = new Config(); + assertFalse(defaultConfig.equals(null)); + assertTrue(defaultConfig.equals(defaultConfig)); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void getAppTitle_success() { + assertEquals("Mastermind", config.getAppTitle()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setAppTitle_success() { + config.setAppTitle("Test"); + assertEquals("Test", config.getAppTitle()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setAppTitle_nullInput_assertionFailure() { + thrown.expect(AssertionError.class); + config.setAppTitle(null); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void getUserPrefsFilePath_success() { + assertEquals("preferences.json", config.getUserPrefsFilePath()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setUserPrefsFilePath_success() { + config.setUserPrefsFilePath("TestTest"); + assertEquals("TestTest", config.getUserPrefsFilePath()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setUserPrefsFilePath_nullInput_assertionFailure() { + thrown.expect(AssertionError.class); + config.setUserPrefsFilePath(null); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void getTaskManagerFilePath_success() { + assertEquals("data/mastermind.xml", config.getTaskManagerFilePath()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setTaskManagerFilePath_nullInput_assertionFailure() { + thrown.expect(AssertionError.class); + config.setTaskManagerFilePath(null); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setTaskManagerFilePath_success() { + config.setTaskManagerFilePath("TestTestTest"); + assertEquals("TestTestTest", config.getTaskManagerFilePath()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void getTaskManagerName_success() { + assertEquals("MyTaskManager", config.getTaskManagerName()); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setTaskManagerName_nullInput_assertionFailure() { + thrown.expect(AssertionError.class); + config.setTaskManagerName(null); + } + +``` +###### /java/harmony/mastermind/commons/core/ConfigTest.java +``` java + @Test + public void setTaskManagerName_success() { + config.setTaskManagerName("TestTestTestTest"); + assertEquals("TestTestTestTest", config.getTaskManagerName()); + } + +} +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + private final String FILEPATH_ENDING_WITH_SLASH = "TestFile/"; + private final String FILEPATH_NOT_ENDING_WITH_SLASH = "TestFile"; + private final String ORIGINAL_FOLDER = "data/mastermind.xml"; + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setup() { + storageManager = new StorageManager(getTempFilePath("ab"), getTempFilePath("prefs")); + } + + private String getTempFilePath(String fileName) { + return testFolder.getRoot().getPath() + fileName; + } + + /* + * Note: This is an integration test that verifies the StorageManager is + * properly wired to the {@link JsonUserPrefStorage} class. More extensive + * testing of UserPref saving/reading is done in {@link + * JsonUserPrefStorageTest} class. + */ + + @Test + public void prefsReadSave() throws Exception { + UserPrefs original = new UserPrefs(); + original.setGuiSettings(300, 600, 4, 6); + storageManager.saveUserPrefs(original); + UserPrefs retrieved = storageManager.readUserPrefs().get(); + assertEquals(original, retrieved); + } + + @Test + public void taskManagerReadSave() throws Exception { + TaskManager original = new TypicalTestTasks().getTypicalTaskManager(); + storageManager.saveTaskManager(original); + ReadOnlyTaskManager retrieved = storageManager.readTaskManager().get(); + + assertEquals(original, new TaskManager(retrieved)); + // More extensive testing of TaskManager saving/reading is done in + // XmlTaskManagerStorageTest + } + + @Test + public void getTaskManagerFilePath() { + assertNotNull(storageManager.getTaskManagerFilePath()); + } + + @Test + public void handleTaskManagerChangedEvent_exceptionThrown_eventRaised() throws IOException { + // Create a StorageManager while injecting a stub that throws an + // exception when the save method is called + Storage storage = new StorageManager(new XmlTaskManagerStorageExceptionThrowingStub("dummy"), + new JsonUserPrefStorage("dummy")); + EventsCollector eventCollector = new EventsCollector(); + storage.handleTaskManagerChangedEvent(new TaskManagerChangedEvent(new TaskManager())); + assertTrue(eventCollector.get(0) instanceof DataSavingExceptionEvent); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void correctFilePathFormat_nullFilePath_assertionFailure() { + thrown.expect(AssertionError.class); + storageManager.correctFilePathFormat(null); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void correctFilePathFormat_filePathEndingWithSlash_success() { + String result = storageManager.correctFilePathFormat(FILEPATH_ENDING_WITH_SLASH); + assertEquals(result, FILEPATH_ENDING_WITH_SLASH + "mastermind.xml"); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void correctFilePathFormat_filePathNotEndingWithSlash_success() { + String result = storageManager.correctFilePathFormat(FILEPATH_NOT_ENDING_WITH_SLASH); + assertEquals(result, FILEPATH_NOT_ENDING_WITH_SLASH + "/mastermind.xml"); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void handleRelocateEvent_nullEvent_assertionFailure() { + thrown.expect(AssertionError.class); + storageManager.handleRelocateEvent(null); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void handleRelocateEvent_nullEventFilePath_assertionFailure() { + thrown.expect(AssertionError.class); + RelocateFilePathEvent event = new RelocateFilePathEvent(null); + storageManager.handleRelocateEvent(event); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void handleRelocateEvent_unwrittableFilePath_IOExceptionThrown() { + String filePath = storageManager.getTaskManagerFilePath() + "/mastermind.xml"; + RelocateFilePathEvent event = new RelocateFilePathEvent(""); + storageManager.handleRelocateEvent(event); + assertEquals(filePath, storageManager.getTaskManagerFilePath() + "/mastermind.xml"); + reset(); + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + @Test + public void updateConfig_nullInput_assertionFailure() { + thrown.expect(AssertionError.class); + storageManager.updateConfig(null); + } + + //resets the config to the original + public void reset() { + storageManager.updateConfig(ORIGINAL_FOLDER); + } + + /** + * A Stub class to throw an exception when the save method is called + */ + class XmlTaskManagerStorageExceptionThrowingStub extends XmlTaskManagerStorage { + + public XmlTaskManagerStorageExceptionThrowingStub(String filePath) { + super(filePath); + } + + @Override + public void saveTaskManager(ReadOnlyTaskManager addressBook, String filePath) throws IOException { + throw new IOException("dummy exception"); + } + } + +``` +###### /java/harmony/mastermind/storage/StorageManagerTest.java +``` java + * A Stub class to store config.json + */ + class ConfigStub extends Config { + + Config config; + + public ConfigStub(String filePath) { + config = new Config(); + config.setTaskManagerFilePath(filePath); + } + } + +} +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void setTaskManagerFilePath_correctFilePath_assertionSuccess() { + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + xmlTaskManagerStorage.setTaskManagerFilePath(SECOND_TEST_DATA_FOLDER); + assertEquals(xmlTaskManagerStorage.getTaskManagerFilePath(), SECOND_TEST_DATA_FOLDER); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void migrateIntoNewFolder_nullNewFilePath_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + xmlTaskManagerStorage.migrateIntoNewFolder(TEST_DATA_FOLDER, null); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void migrateIntoNewFolder_nullOldFilePath_assertionFailure() throws IOException { + thrown.expect(AssertionError.class); + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + xmlTaskManagerStorage.migrateIntoNewFolder(null, SECOND_TEST_DATA_FOLDER); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void deleteFile_nullFilePath_assertionFailure() { + thrown.expect(AssertionError.class); + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + xmlTaskManagerStorage.deleteFile(null); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void deleteFile_success() throws IOException { + String filePath = testFolder.getRoot().getPath() + "TempTaskManager.xml"; + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + TypicalTestTasks td = new TypicalTestTasks(); + TaskManager original = td.getTypicalTaskManager(); + xmlTaskManagerStorage.saveTaskManager(original, filePath); + assertEquals(true, xmlTaskManagerStorage.deleteFile(filePath)); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void deleteFile_failure() throws IOException { + String filePath = testFolder.getRoot().getPath() + "TempTaskManager.xml"; + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + assertEquals(false, xmlTaskManagerStorage.deleteFile(filePath)); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void migrateNewFolder_allInOrder_success() throws IOException, DataConversionException { + String filePath = testFolder.getRoot().getPath() + "TempTaskManager.xml"; + TypicalTestTasks td = new TypicalTestTasks(); + TaskManager original = td.getTypicalTaskManager(); + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(filePath); + xmlTaskManagerStorage.saveTaskManager(original, filePath); + + String newFilePath = SECOND_TEST_DATA_FOLDER + "TempTaskManager.xml"; + + xmlTaskManagerStorage.migrateIntoNewFolder(filePath, newFilePath); + File toDelete = new File(filePath); + + //Checks if old file has been deleted + assertFalse(toDelete.delete()); + + //Checks if file has been copied over to new location + File newFile = new File(newFilePath); + assertEquals(true, newFile.exists()); + + //reset test folder + clearTestFolder(xmlTaskManagerStorage, newFilePath); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + public void clearTestFolder(XmlTaskManagerStorage storage, String filePath) { + storage.deleteFile(filePath); + } + +``` +###### /java/harmony/mastermind/storage/XmlTaskManagerStorageTest.java +``` java + @Test + public void setTaskManagerFilePath_success() { + XmlTaskManagerStorage xmlTaskManagerStorage = new XmlTaskManagerStorage(TEST_DATA_FOLDER); + xmlTaskManagerStorage.setTaskManagerFilePath(SECOND_TEST_DATA_FOLDER); + assertEquals(SECOND_TEST_DATA_FOLDER, xmlTaskManagerStorage.getTaskManagerFilePath()); + } + +} +``` +###### /java/harmony/mastermind/logic/LogicManagerTest.java +``` java + @Test + public void execute_help() throws Exception { + assertCommandBehavior("help", HelpCommand.SUCCESSFULLY_SHOWN); + assertTrue(helpShown); + } + +``` +###### /java/harmony/mastermind/logic/HelpCommandTest.java +``` java +public class HelpCommandTest { + + //Number of commands is 18 + private final int NUM_ENTRIES = 18; + + @Test + public void initInfo_success() { + HelpCommand help = new HelpCommand(); + assertEquals(help.getEntries().size(), NUM_ENTRIES); + } + + @Test + public void getEntries_success() { + HelpCommand help = new HelpCommand(); + assertTrue(help.getEntries() instanceof ArrayList); + } + +} +``` +###### /java/guitests/RelocateCommandTest.java +``` java +public class RelocateCommandTest extends TaskManagerGuiTest { + + private static String SECOND_TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/MigrationMastermindStorageTest/"); + private static String ORIGINAL_FOLDER = FileUtil.getPath("./data"); + + + @Test + public void relocate_success() { + this.commandBox.runCommand("relocate " + SECOND_TEST_DATA_FOLDER); + this.assertResultMessage("Relocated save location to " + SECOND_TEST_DATA_FOLDER); + + reset(); + } + + //resets save location + private void reset() { + this.commandBox.runCommand("relocate " + ORIGINAL_FOLDER); + } +} +``` +###### /java/guitests/HelpCommandTest.java +``` java +public class HelpCommandTest extends TaskManagerGuiTest { + + @Test + public void execute_sucess() { + commandBox.runCommand("help"); + assertResultMessage("Command summary displayed."); + } +} +``` diff --git a/collated/test/A0143378Y.md b/collated/test/A0143378Y.md new file mode 100644 index 000000000000..32a6d51897a2 --- /dev/null +++ b/collated/test/A0143378Y.md @@ -0,0 +1,889 @@ +# A0143378Y +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + public void test() { + testReadFromFile_IO(); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static void testReadFromFile_IO() { + Memory testMem = new Memory(); + StorageMemory.checkForFileExists(testMem); + + testMem_getTask(testMem); + testMem_getDeadline(testMem); + testMem_getEvent(testMem); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static void testMem_getTask(Memory mem) { + GenericMemory task = new GenericMemory("Task", "I am hungry", "very hungry"); + assertEquals("test if they are the same", testTwoTasks(mem.get(0), task), false); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static void testMem_getDeadline(Memory mem) { + Calendar end = new GregorianCalendar(); + end.set(2014, 1, 27, 23, 59); + + GenericMemory deadline = new GenericMemory("Deadline", "still hungry", "more food needed", end); + assertEquals("test if they are the same", testTwoDeadlines(mem.get(1), deadline), false); + } + + private static void testMem_getEvent(Memory mem) { + Calendar end = new GregorianCalendar(); + Calendar start = new GregorianCalendar(); + start.set(2014, 1, 27, 23, 0); + end.set(2014, 1, 28, 2, 0); + + GenericMemory event = new GenericMemory("Event", "Lunch?", "Sure!", start, end, 0); + assertEquals("test if they are the same", testTwoEvents(mem.get(2), event), false); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static boolean testTwoTasks(GenericMemory a, GenericMemory b) { + return a.getType().equals(b.getType()) && + a.getDescription().equals(b.getDescription()) && + a.getName().equals(b.getName()); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static boolean testTwoDeadlines(GenericMemory a, GenericMemory b) { + return testTwoTasks(a, b) && + testTwoCalendar(a.getEnd(), b.getEnd()); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static boolean testTwoEvents(GenericMemory a, GenericMemory b) { + return testTwoDeadlines(a, b) && + testTwoCalendar(a.getStart(), b.getStart()); + } + +``` +###### /java/harmony/mastermind/storage/TestStorage.java +``` java + private static boolean testTwoCalendar(Calendar a, Calendar b) { + return a.get(Calendar.YEAR) == b.get(Calendar.YEAR) && + a.get(Calendar.MONTH) == b.get(Calendar.MONTH) && + a.get(Calendar.DATE) == b.get(Calendar.DATE); + } +} +``` +###### /java/harmony/mastermind/logic/TestParserMemory.java +``` java + public void test() { + test_set(); + test_setDate(); + test_setTime(); + test_reduce(); + test_isUselessCommand(); + } + +``` +###### /java/harmony/mastermind/logic/TestParserMemory.java +``` java + private void test_set() { + ParserMemoryMain.setCommand("test1"); + assertEquals("Test set command", ParserMemoryMain.getCommand(), "test1"); + + ParserMemoryMain.setTaskName("test2"); + assertEquals("Test set task name", ParserMemoryMain.getTaskName(), "test2"); + + ParserMemoryMain.setDescription("test3"); + assertEquals("Test set description", ParserMemoryMain.getDescription(), "test3"); + + ParserMemoryMain.setLength(4); + assertEquals("Test set length", ParserMemoryMain.getLength(), 4); + + ParserMemoryMain.setType(5); + assertEquals("Test set type", ParserMemoryMain.getType(), 5); + + //Test if setContainsDescription works properly + ParserMemoryMain.setContainsDescription(true); + assertTrue(ParserMemoryMain.containsDescription); + + ParserMemoryMain.setContainsDescription(false); + assertFalse(ParserMemoryMain.containsDescription); + + //Test if setProper works properly + ParserMemoryMain.setProper(true); + assertTrue(ParserMemoryMain.setProper); + + ParserMemoryMain.setProper(false); + assertFalse(ParserMemoryMain.setProper); + } + +``` +###### /java/harmony/mastermind/logic/TestParserMemory.java +``` java + /* + * Accepts only: + * 1. 0 list) { + Sort.sort(list); + + test_Event(list); + test_Deadline(list); + test_Task(list); + } + +``` +###### /java/guitests/TestSortCommand.java +``` java + private void test_Event(ArrayList list) { + //Check if the type is Event + assertEquals("Check if it is Event", list.get(0).getType(), "Event"); + + //Check if the name is correct + assertEquals("Check the name", list.get(0).getName(), "test3"); + + //Check the description + assertEquals("Check description", list.get(0).getDescription(), "do this3"); + + //Check status + assertEquals("Check if it is incomplete", list.get(0).getState(), 0); + } + +``` +###### /java/guitests/TestSortCommand.java +``` java + private void test_Deadline(ArrayList list) { + //Check if the type is Deadline + assertEquals("Check if it is Deadline", list.get(1).getType(), "Deadline"); + + //Check the name + assertEquals("Check the name", list.get(1).getName(), "test2"); + + //Check description + assertEquals("Check description", list.get(1).getDescription(), "do this2"); + + //Check status + assertEquals("Check status", list.get(1).getState(), 0); + } + +``` +###### /java/guitests/TestSortCommand.java +``` java + private void test_Task(ArrayList list) { + //Check if the type is Deadline + assertEquals("Check if it is Task", list.get(2).getType(), "Task"); + + //Check if the type is Deadline + assertEquals("Check the name", list.get(2).getName(), "test1"); + + //Check description + assertEquals("Check description", list.get(2).getDescription(), "do this"); + + //Check status + assertEquals("Check status", list.get(2).getState(), 0); + } + +``` +###### /java/guitests/TestSortCommand.java +``` java + private void initializeList() { + Calendar start = new GregorianCalendar(); + start.set(1990, 10, 1); + + Calendar end = new GregorianCalendar(); + end.set(1990, 10, 9); + + //Adding Task + list.add(new GenericMemory("Task", "test1", "do this")); + + //Adding Deadlines + list.add(new GenericMemory("Deadline", "test2", "do this2", end)); + + //Adding Events + list.add(new GenericMemory("Event", "test3", "do this3", start, end, 0)); + } +} +``` +###### /java/guitests/TestMemoryFind.java +``` java + @Test + public void test() { + Memory testMem = new Memory(); + StorageMemory.setSaveFileAddress("test2.txt"); + StorageMemory.checkForFileExists(testMem); + + testSearchExact(testMem); + testFindDate(testMem); + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testSearchExact(Memory testMem) { + testSearchExact_EmptyMem(); + testSearchExact_NonEmptyMem(testMem); + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testFindDate(Memory testMem){ + Calendar date = new GregorianCalendar(); + testFindDate_NoneExistent(testMem, date); + testFindDate_OnEndDate(testMem, date); + testFindDate_OnStartDate(testMem, date); + testFindDate_Containing(testMem, date); + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testSearchExact_EmptyMem() { + Memory testMem = new Memory(); + + ArrayList testCase1 = FindCommand.searchExact("I want to eat", testMem); + assertEquals("If mem is not empty, test if item cannot be found", testCase1.size(), 0); + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testSearchExact_NonEmptyMem(Memory testMem) { + ArrayList testCase1 = FindCommand.searchExact("I want to eat", testMem); + assertEquals("If mem is not empty, test if item cannot be found", testCase1.size(), 0); + + ArrayList testCase2 = FindCommand.searchExact("I am hungry", testMem); + assertEquals("If mem is not empty, test if item can be found", testCase2.size(), 1); + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + /* + * Completely outside of any range + */ + private void testFindDate_NoneExistent(Memory testMem, Calendar date){ + + date.set(1999, 10, 9); + ArrayList result1 = FindCommand.findDate(date, testMem); + assertEquals("Cannot find date required", result1.size(), 0); + + //A day before earliest startdate + date.set(2013, 7, 26); + result1 = FindCommand.findDate(date, testMem); + assertEquals("Cannot find date required", result1.size(), 0); + + //A day after latest enddate + date.set(2015, 5, 31); + result1 = FindCommand.findDate(date, testMem); + assertEquals("Cannot find date required", result1.size(), 0); + + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testFindDate_OnEndDate(Memory testMem, Calendar date){ + + date.set(2015, 4, 30); + ArrayList result1 = FindCommand.findDate(date, testMem); + assertEquals("Only 1 item has this enddate", result1.size(), 1); + + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testFindDate_OnStartDate(Memory testMem, Calendar date){ + + date.set(2013, 8, 27); + ArrayList result2 = FindCommand.findDate(date, testMem); + assertEquals("Only 1 item has this start date", result2.size(), 1); + + } + +``` +###### /java/guitests/TestMemoryFind.java +``` java + private void testFindDate_Containing(Memory testMem, Calendar date){ + date.set(2014, 1, 27); + ArrayList result3 = FindCommand.findDate(date, testMem); + assertEquals("Date within start and end range", result3.size(), 4); + } +} +``` diff --git a/collated/test/generated.md b/collated/test/generated.md new file mode 100644 index 000000000000..e31c1b0505e1 --- /dev/null +++ b/collated/test/generated.md @@ -0,0 +1,64 @@ +# generated +###### /java/harmony/mastermind/testutil/TypicalTestTasks.java +``` java + public TaskManager getTypicalTaskManager(){ + TaskManager tm = new TaskManager(); + loadTaskManagerWithSampleData(tm); + return tm; + } +} +``` +###### /java/harmony/mastermind/testutil/TestTask.java +``` java + @Override + public boolean isDue() { + return false; + } + + @Override + public boolean isHappening() { + return false; + } + + @Override + public Duration getDueDuration() { + return null; + } + + @Override + public Duration getEventDuration(){ + return null; + } +} +``` +###### /java/harmony/mastermind/testutil/TestUtil.java +``` java + public static List generateSampleTaskData() { + return Arrays.asList(sampleTaskData); + } + +``` +###### /java/harmony/mastermind/testutil/TestUtil.java +``` java + public static Tag[] getTagList(String tags) { + + if (tags.equals("")) { + return new Tag[]{}; + } + + final String[] split = tags.split(", "); + + final List collect = Arrays.asList(split).stream().map(e -> { + try { + return new Tag(e.replaceFirst("Tag: ", "")); + } catch (IllegalValueException e1) { + assert false; + return null; + } + }).collect(Collectors.toList()); + + return collect.toArray(new Tag[split.length]); + } + +} +``` diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 33df65bea583..457e2eb5db8b 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,51 +2,105 @@ We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -## Project Team - -#### [Damith C. Rajapakse](http://www.comp.nus.edu.sg/~damithch)
-
-**Role**: Project Advisor +## Project Mentor +#### [Akshay Narayan](https://github.com/okkhoy) +
----- -#### [Joshua Lee](http://github.com/lejolly) -
-Role: Developer
-Responsibilities: UI ------ +## Project Team + +#### [Dylan Chew Zhi Jiang](https://github.com/zavfel)
+
+* Components in charge of: [Logic](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#logic-component) +* Aspects/tools in charge of: Testing, Code quality +* Features implemented: + * [List](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#listing-all-tasks-of-a-category-list) + * [Mark](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#completing-tasks--mark) + * [Unmark](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#unmarks-tasks--unmark) + * [Previous](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#repeating-a-previous-command-) + * [Upcoming](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#show-upcoming-tasks--upcoming) + * allow adding of recurring task in [Add](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adds-a-recurring-deadline) + + * [Import (csv)](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#importing-file--import) -#### [Leow Yijin](http://github.com/yijinl) -
-Role: Developer
-Responsibilities: Data +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0124797R.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0124797R.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0124797R.md)] +* Other major contributions: + * Did the initial refactoring from AddressBook to ToDoList + * Set up Travis and Coveralls + * Class Diagrams ----- + +#### [Wong Kang Fei](https://github.com/kfwong) +
+* Components in charge of: [UI](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#ui-component) +* Aspects/tools in charge of: Git, Travis, UI, Logic +* Features implemented: + * [Undo](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#undo-a-command--undo) + * [Redo](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#redo-a-command--redo) + * [Add](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adding-a-task-add-do) -#### [Martin Choo](http://github.com/m133225) -
-Role: Developer
-Responsibilities: Dev Ops + * [Import (ics)](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#importing-file--import) + + * [Export (csv)](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#adding-a-task-add-do) + + * [History](https://hackmd.io/AwIwTAbAJgLFIFoCMSDMB2BMkFYAcCAnBAIYkLrp6HrC2oCmS6QA?both#recall-your-action-history-history) + +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0138862W.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0138862W.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0138862W.md)] +* Other major contributions: + * Set up Travis and Coveralls + * Git expert + * UI Revamp + * Action history ----- + +#### [Lim Hui Qi](https://github.com/LuMiN0uSaRc) +
+* Components in charge of: [Memory](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#model-component) +* Aspects/tools in charge of: UI, Scheduling and deadlines, Tracking +* Features implemented: + * [Edit](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#editing-a-task--edit) + * [Find](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#finding-all-tasks-containing-any-keyword-in-their-description-find) +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0143378Y.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0143378Y.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0143378Y.md)] +* Other major contributions: + * Documentation + * Smart learning text field + * Autocomplete + * Issue tracking + * Sorting based on event date and task within Memory + * History for Memory -#### [Thien Nguyen](https://github.com/ndt93) - Role: Developer
- Responsibilities: Threading - - ----- +----- + +#### [Albert Yeoh Ji Bin](https://github.com/bertojo) +
+* Components in charge of: [Data](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/DeveloperGuide.md#storage-component) +* Aspects/tools in charge of: Data, Integration, Documentation, Deliverables +* Features implemented: + * [Relocate Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#changing-save-location--relocate) + * [Help Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#viewing-help--help) + * [Clear Command](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/docs/UserGuide.md#clearing-all-entries-clear) +* Code written: [[functional code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/main/A0139194X.md)][[test code](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/test/A0139194X.md)][[docs](https://github.com/CS2103AUG2016-W11-C3/main/blob/master/collated/docs/A0139194X.md)] +* Other major contributions: + * Documentation + * Proof reading + * Issue tracking + * Sequence Diagrams -#### [You Liang](http://github.com/yl-coder) -
- Role: Developer
- Responsibilities: UI - - ----- +----- # Contributors We welcome contributions. See [Contact Us](ContactUs.md) page for more info. +* [Damith C. Rajapakse](http://www.comp.nus.edu.sg/~damithch/) +* [Joshua Lee](https://github.com/lejolly) +* [Leow YiJin](https://github.com/yijinl) +* [Martin Choo](https://github.com/m133225) +* [Thien Nguyen](https://github.com/ndt93) +* [You Liang](https://github.com/yl-coder) * [Akshay Narayan](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Aokkhoy) -* [Sam Yong](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Amauris) \ No newline at end of file +* [Sam Yong](https://github.com/se-edu/addressbook-level4/pulls?q=is%3Apr+author%3Amauris) diff --git a/docs/ContactUs.md b/docs/ContactUs.md index 866d0de3fddc..552e86d55ff2 100644 --- a/docs/ContactUs.md +++ b/docs/ContactUs.md @@ -1,8 +1,9 @@ + # Contact Us -* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/se-edu/addressbook-level4/issues) +* **Bug reports, Suggestions** : Post in our [issue tracker](https://github.com/CS2103AUG2016-W11-C3/main/issues) if you noticed bugs or have suggestions on how to improve. * **Contributing** : We welcome pull requests. Follow the process described [here](https://github.com/oss-generic/process) -* **Email us** : You can also reach us at `damith [at] comp.nus.edu.sg` \ No newline at end of file +* **Email us** : You can also reach us at `teamharmonyw11c3 [at] gmail.com` diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index acc94d2e1367..8a061f4d37a9 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,5 +1,8 @@ -# Developer Guide + +# Developer Guide +* [Introduction](#introduction) +* [Target Audience](#target-audience) * [Setting Up](#setting-up) * [Design](#design) * [Implementation](#implementation) @@ -11,237 +14,468 @@ * [Appendix B: Use Cases](#appendix-b--use-cases) * [Appendix C: Non Functional Requirements](#appendix-c--non-functional-requirements) * [Appendix D: Glossary](#appendix-d--glossary) -* [Appendix E : Product Survey](#appendix-e-product-survey) +* [Appendix E: Product Survey](#appendix-e--product-survey) + +## Introduction +Welcome to _Mastermind_'s Developer Guide! + +_Mastermind_ is a to-do task manager that helps user keep track of their tasks. We aim to provide our users with an effective and efficient solution to managing their tasks so they are able to better handle their time, as well as completing tasks required. + +This developer guide is for both existing and new developers of the team who are interested in working on _Mastermind_ in the future. + +This guide will show you the Product Architecture, APIs and the details regarding the different components. + +The next segment will show you how to set-up to make sure that you have the necessary tools before getting started. Feel free to approach our team for any clarifications that you may face during the process. Good luck and have fun coding! ## Setting up #### Prerequisites -1. **JDK `1.8.0_60`** or later
- - > Having any Java 8 version is not enough.
+1. **JDK `1.8.0_60`** or later. + > Having any Java 8 version is not enough. This app will not work with earlier versions of Java 8. - -2. **Eclipse** IDE -3. **e(fx)clipse** plugin for Eclipse (Do the steps 2 onwards given in - [this page](http://www.eclipse.org/efxclipse/install.html#for-the-ambitious)) -4. **Buildship Gradle Integration** plugin from the Eclipse Marketplace + +2. **Eclipse** IDE. +3. **e(fx)clipse** plugin for Eclipse (Follow from step 2 onwards given in + [this page](http://www.eclipse.org/efxclipse/install.html#for-the-ambitious)). +4. **Buildship Gradle Integration** plugin from the Eclipse Marketplace. #### Importing the project into Eclipse -0. Fork this repo, and clone the fork to your computer -1. Open Eclipse (Note: Ensure you have installed the **e(fx)clipse** and **buildship** plugins as given - in the prerequisites above) -2. Click `File` > `Import` -3. Click `Gradle` > `Gradle Project` > `Next` > `Next` -4. Click `Browse`, then locate the project's directory -5. Click `Finish` +1. Fork this repo, and clone the fork to your computer. +2. Open Eclipse (Note: Ensure you have installed the **e(fx)clipse** and **buildship** plugins as given in the prerequisites above). +3. Click `File` > `Import`. +4. Click `Gradle` > `Gradle Project` > `Next` > `Next`. +5. Click `Browse`, then locate the project's directory. +6. Click `Finish`. > * If you are asked whether to 'keep' or 'overwrite' config files, choose to 'keep'. > * Depending on your connection speed and server load, it can even take up to 30 minutes for the set up to finish - (This is because Gradle downloads library files from servers during the project set up process) + (This is because Gradle downloads library files from servers during the project set up process). > * If Eclipse auto-changed any settings files during the import process, you can discard those changes. +### Troubleshooting Project Setup +1. **Eclipse reports compile errors after new commits are pulled from Git:** + + >Reason: Eclipse fails to recognize new files that appeared due to the Git pull. + > + >Solution: Refresh the project in Eclipse. Right click on the project (in Eclipse package explorer), choose Gradle -> Refresh Gradle Project. + +2. **Eclipse reports some required libraries missing:** + + >Reason: Required libraries may not have been downloaded during the project import. + > + >Solution: Run tests using Gradle once (to refresh the libraries). + + + ## Design -
+### Software Architecture + +To start off, let us introduce you to the overall structure of _Mastermind_. Do have a basic understanding of _Mastermind_'s different components before focusing on them individually. + +_Mastermind_ is split up into 5 main components, namely the `UI`, `Logic`, `Model`, `Storage` and `Commons`, as shown below, in Figure 1. + + + The **_Architecture Diagram_** given above explains the high-level design of the App. Given below is a quick overview of each component. -`Main` has only one class called [`MainApp`](../src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has only one class called [`MainApp`](../src/main/java/harmony/mastermind/MainApp.java). It is responsible for: * At app launch: Initializes the components in the correct sequence, and connect them up with each other. * At shut down: Shuts down the components and invoke clean up method where necessary. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. Two of those classes play an important role at the architecture level. -* `EventsCentre` : This class (written using [Google's Event Bus library](https://github.com/google/guava/wiki/EventBusExplained)) - is used to by componnents to communicate with other components using events (i.e. a form of _Event Driven_ design) +* `EventsCenter` : This class (written using [Google's Event Bus library](https://github.com/google/guava/wiki/EventBusExplained)) + is used by components to communicate with other components using events (i.e. a form of _Event Driven_ design). * `LogsCenter` : Used by many classes to write log messages to the App's log files. The rest of the App consists four components. -* [**`UI`**](#ui-component) : The UI of tha App. +* [**`UI`**](#ui-component) : The UI of the App. * [**`Logic`**](#logic-component) : The command executor. * [**`Model`**](#model-component) : Holds the data of the App in-memory. * [**`Storage`**](#storage-component) : Reads data from, and writes data to, the hard disk. +* [**`Memory`**](#memory-component) : Reads data from, and writes data to, the hard disk. -Each of the four components -* Defines its _API_ an interface with the same name as the Component. `Logic.java` +Each of the four components: +* Defines its _API_, which is an interface with the same name as the component. `Logic.java` * Exposes its functionality using a `{Component Name}Manager` class e.g. `LogicManager.java` -The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the -command `delete 3`. - +The sections below give more details of each component. ->Note how the `Model` simply raises a `ModelChangedEvent` when the model is changed, - instead of asking the `Storage` to save the updates to the hard disk. +#### UI component -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.
- +`UI` is implemented by JavaFX 8 and consists of the main panel Main Window. This component primarily handles user input such as text input which will be entered via Command Line Input (CLI) as shown in Figure 2. On top of text input, users are also allowed to use keypress or mouse click. Inputs are passed on to the `Logic` component. -> Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having - to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct - coupling between components. +If you are intending to work on the `UI`, you will need to update the application's internal state, which also includes: +1. UiManager.java +2. UiPartLoader.java +3. UiPart.java -The sections below give more details of each component. -### UI component + -
+**API** : [`Ui.java`](../src/main/java/harmony/mastermind/ui/Ui.java) -**API** : [`Ui.java`](../src/main/java/seedu/address/ui/Ui.java) +The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](../src/main/java/harmony/mastermind/ui/MainWindow.java) is specified in [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml). -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, -`StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow` inherits from the abstract `UiPart` class -and they can be loaded using the `UiPartLoader`. +The `UI` component: +* Executes user commands using the `Logic` component. +* Binds itself to data in the `Model` so that the UI can automatically update when data in the `Model` change. +* Responds to some of the events raised from various parts of the App and updates `UI` accordingly. -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files - that are in the `src/main/resources/view` folder.
- For example, the layout of the [`MainWindow`](../src/main/java/seedu/address/ui/MainWindow.java) is specified in - [`MainWindow.fxml`](../src/main/resources/view/MainWindow.fxml) +[insert ui class diagram] -The `UI` component, -* Executes user commands using the `Logic` component. -* Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. -* Responds to events raises from various parts of the App and updates the UI accordingly. +We separated the UI into smaller components. Each components should handles the events raised by `EventCenter` if needed. + +- `MainWindow.java`: handles the Tab name updates, initialize smaller components. + - `CommandBox.java`: handles user input, listen to textfield change event. + - `ActionHistoryPane.java`: display command result. + - `ActionHistoryEntry.java`: renders each entry in the action history list. + - `DefaultTableView.java`: an abstract class that define default table rendering algorithms. It should be extended by the following classes: + - `HomeTableView.java`: display all tasks. + - `TasksTableView.java`: display only floating tasks. + - `EventsTableView.java`: display only events. + - `DeadlinesTableView.java`: display only deadlines. + - `ArchivesTableView.java`: display marked tasks. +- `HelpPopup.java`: display help window. -### Logic component + +#### Logic component +`Logic` is the brain of the application as it controls and manages the overall flow of the application. Upon receiving the user input from `UI`, it will process the input using the `Parser` and return the result of executing the user input back to the `UI`. The inputs `Logic` takes in are command words such as `add`, `edit`, `delete`, etc. `Logic` will then execute them accordingly based on their functionality. If you were to work on this execution of user input, you will need to access `Storage` through the `EventsCenter` to retrieve and update the state of tasks. -
+ -**API** : [`Logic.java`](../src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](../src/main/java/harmony/mastermind/logic/Logic.java) 1. `Logic` uses the `Parser` class to parse the user command. 2. This results in a `Command` object which is executed by the `LogicManager`. 3. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. -4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui` +4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `UI`. + +##### Parser +`Parser` class uses [Regular Expression](https://www.tutorialspoint.com/java/java_regular_expressions.htm) (regex) to extract parameters from raw input. Each `Command` defines the regex in their respective class variable `COMMAND_ARGUMENTS_REGEX`. + +`parseCommand()` method registers all the recognized `Command` under `switch` statement. + +The regular expression can be tested at [regex101](https://regex101.com). + +You can visit the following link to inspect the regex test result for `AddCommand` and `EditCommand`: + +- `AddCommand`: [https://regex101.com/r/M2A3tB/15](https://regex101.com/r/M2A3tB/15) + +- `EditCommand`: [https://regex101.com/r/VcdUmR/4](https://regex101.com/r/VcdUmR/4) + +##### Add +The `add` command allows the user to add tasks, deadlines or events to _Mastermind_. Tasks, deadlines and events are differentiated by which attribute is updated. +> * Floating tasks are tasks without start and end date. +> * Deadlines are tasks with an end date. +> * Events are tasks with both start and end date. +> +All tasks are stored as object attributes such as name, description, end date, start date and type. + +You can refer to Figure 4 and Figure 5 below and the next page for the sequence diagram of `add`. + + + +> Note how the `Model` simply raises a `TaskManagerChangedEvent` when _Mastermind_'s data is changed, instead of directly 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. + + + +> Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is how we reduce direct coupling between 2 components. + + +##### Edit +This command allows the users to update attributes of items they have already added in _Mastermind_. + +The user can update the task by choosing the index of the task they want to change. They will then choose the specific field, such as start date, that they want to change. + +However, the user can only update one item at a time. To update, the item being updated must be found, and removed from the `Storage`. After updating the attribute, the item is re-added back into `Storage`. If the update is successful, the details of the item will be shown to the user at the console output. Otherwise, an error message is generated. + +The details are shown in the following diagrams. + + + +2 events are raised during the execution of `Edit`. + + + + +##### Undo & Redo +The `undo` and `redo` commands allow user to recover from mistakes. The user can execute `undo` or `redo` command multiple times to the earliest entry. + +Note that _Mastermind_ does not support `undo` or `redo` for `import`, `export`, `list`, `upcoming` and `clear` commands. The undo and redo history will be cleared upon executing such commands. + +The `undo` and `redo` commands are session based, meaning that the history will be cleared when the user exit the application. + +[insert undo class diagram] + +The `undo` command makes use of [Strategy Pattern](http://www.tutorialspoint.com/design_pattern/strategy_pattern.htm) to manage the logic flow. All commands that support `undo` operation must implement the `Undoable` interface. Similarly, to support `redo` operation, the command should implement the `Redoable` interface. + +We chose this design pattern because different command has different `undo` behaviour during runtime. Furthermore, it's more intuitive and reasonable to tell the specialized command how to `undo` its own execution. + +It's also aligned with the [Open/Close Principle](http://www.oodesign.com/open-close-principle.html) since the `UndoCommand.java` is close for modification, but open for extension to anticipate more `undo` strategies in the future. + +[insert undo sequence diagram] +We combine this design with the `Stack` data structure to make sure the commands `undo` in proper sequence. The `ModelManager` will manage this stack as `undoHistoryStack`. + +At the end of the command execution, the command should push itself to the `undoHistoryStack`. + +Similarly, `redo` command makes use of the Strategy Pattern as well. `ModelManager` keeps two different stacks to control the undo and redo command flow. + + +##### Mark +The `mark` command allows users to mark their tasks/deadlines/events as completed. This removes the task from the tasks/deadlines/events field, and moves it into the `Archives`. The `mark` command will not delete the task immediately. In the event that users want to unmark the task, users can do so by using the `unmark` Command. Additionally, User can specify `mark due` to bulk mark all tasks that are due. + + +##### Unmark +The `unmark` command allows users to unmark the tasks in the `Archives` tab. _Mastermind_ will add them back to their original position in their orginal tab. + +> This command will only work in the `Archives` tab. + + +##### Delete +This command allows the user to delete any task they have input into the program beforehand. Users have to specify the index to delete after the command word. + +Details are illustrated in the following diagrams. + + + +> Again, note how that `Model` simply raises an event instead of relying on `Storage` directly. + +And `EventsCenter` reacts to the event accordingly. + + + + +##### Clear +`Clear` wipes all tasks(floating tasks, deadlines and events) currently registered in _Mastermind_. + +After inputting the command, the data is cleared from the `Storage` by raising an event to the `EventCenter`. + +> Undo and redo histories are also cleared from _Mastermind_. + + +##### Find +To find an item by name, the user will search through the `Storage` by calling "find ``". It calls `FindCommand` to find the exact terms of the keywords entered by the user. + +##### Findtag +To find an item by tag, the user will search through the `Storage` by calling "findtag ``". It calls `FindTagCommand` to find the exact terms of the keywords entered by the user. + +##### List +Calling `list [tab_name]` will display the items in that category. + + +##### Upcoming +Calling `upcoming [task_type]` will updates the list to show the user task in the upcoming week. If `task_type` is indicated, it will show only tasks of that specified type. + + +##### Relocate +This command allows the user to specify the save location of the data file. The file path the user inputs is contained in an event and passed to the `Storage` component from the `Logic` component via the `EventsCenter`. `Relocate` relies on `FileUtil` to check whether the input file path is valid and writable first. It then raises an event to the `EventsCenter` to inform `Storage` to handle the command. Hence there is no coupling from `Logic` to `Storage`. + + +##### Import +The `import` command allows user to migrate their existing task list from other application to _Mastermind_. It supports `.csv` and `.ics` format currently. This command should append the imported task lists in _Mastermind_ instead of overwriting it. + +Note that the `.csv` format must be compatible with [Google Calendar specification](https://support.google.com/calendar/answer/37118?hl=en) in order for _Mastermind_ succesfully parses the data. + +The `.ics` format must comply with the [iCalendar](http://icalendar.org/) format. Please refer to the [RFC5545](http://icalendar.org/RFC-Specifications/iCalendar-RFC-5545/) for specification details. + +The `import` command makes use of the following 3rd party libraries: +- [BiWeekly](https://github.com/mangstadt/biweekly) for parsing `.ics` format. +- [Apache Commons CSV](https://commons.apache.org/proper/commons-csv/) for parsing `.csv` format. + +##### Export +The `export` command allows user to export their task list from _Mastermind_ to `.csv` format. The exported format should comply with [Google Calendar specification](https://support.google.com/calendar/answer/37118?hl=en). + +Due to the limitation of Google Calendar, it's mandatory to specify a valid start date and end date. In order to export floating and deadline tasks, we have made the following decisions: + +- Floating Task: start & end date are set to exporting date; `All Day Event` field set to `TRUE`. +- Deadline Task: start & end date share the same date; `All Day Event` field set to `FALSE`. +- Event Task: start & end date are exported directly from _Mastermind_; `All Day Event` field set to`FALSE`. + +In addition, the tags defined in _Mastermind_ will be exported in `Description` field. + +##### History +This command allows users to toggle the Action History bar via the command line. The command will raise an event which will be handled by `UI`. + + +##### Help +Calling `help` will show a popup containing a command summary. To close the popup, press any key. + +> An event will be raised in `Logic`, which will be handled by the `UI`. + + + +##### Exit +This `exit` command runs when the user tries to exit the program, allowing the program to close. + +> The undo and redo history will not be kept. + + ### Model component -
+ -**API** : [`Model.java`](../src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](../src/main/java/harmony/mastermind/model/Model.java) -The `Model`, +The `Model`: * Stores a `UserPref` object that represents the user's preferences -* Stores the Address Book data -* Exposes a `UnmodifiableObservableList` 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. +##### Task & TaskBuilder + +The `Task.java` and `TaskBuilder.java` can be found in `harmony.mastermind.model.task` package. + +We identified three different types of Tasks (Floating, Deadlines, Events), which differ only by the existence of start date and end date in their respective attributes. While inheritance seems to be a viable solution, it's not necessary true that a floating task is interchangeable with deadline and event, or vice versa. Moreover, how can we prevent the user from entering a start date without end date? + +Eventually, we resolved this issue by using the [Builder Pattern](https://www.tutorialspoint.com/design_pattern/builder_pattern.htm). This gives us the following benefits: + +- **Avoid long constructor**: `Task` object has many optional attributes, therefore it's impractical to keep multiple constructors with long parameter list. Builder pattern mitigates such problem by using step by step approach to build the `Task` object. + +- **Increase Readability**: The object creation process uses fluent method name that is much more readable than conventional constructor approach. + +- **Enforce mandatory attributes**: The only way to set the start and end date through `TaskBuilder` is by using `asEvent()`, `asDeadline()`, `asFloating()` methods. Ideally, the `TaskBuilder` should return an immutable `ReadOnlyTask` upon building. There's no way that the user can create a `Task` with start date but without an end date. + +_Example:_ +```java +TaskBuilder taskBuilder = new TaskBuilder(taskName); +taskBuilder.withCreationDate(createdDate); +taskBuilder.withTags(tags); +taskBuilder.asRecurring(recur); + +if (isEvent()){ + taskBuilder.asEvent(startDate,endDate); +} else if (isDeadline()){ + taskBuilder.asDeadline(endDate); +} else if (isFloating()){ + taskBuilder.asFloating(); +} + +Task task = taskBuilder.build(); +``` + + ### Storage component -
+ -**API** : [`Storage.java`](../src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](../src/main/java/harmony/mastermind/storage/Storage.java) -The `Storage` component, +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 the _Mastermind_'s data in xml format and read it back. + +`Storage` is completely decoupled from the other components. It functions by taking events from the `EventsCenter`. ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commans` package. +Classes used by multiple components are in the `harmony.mastermind.commons` package. +This component will be maintained by developers working on any of the other components because of its wide scope of application. You can find 4 packages, namely: `core`, `events`, `exceptions` and `utils`. + + +## Memory Component +This is the temporary memory where most changes regarding memory take place. This is before the information to be stored is transferred permanently in the storage. + + ## Implementation ### Logging -We are using `java.util.logging.Logger` as our logger, and `LogsCenter` is used to manage the logging levels -of loggers and handlers (for output of log messages) +We are using `java.util.logging.Logger` as our logger, and `LogsCenter` is used to manage the logging levels of loggers and handlers (for output of log messages). -- The logging level can be controlled using the `logLevel` setting in the configuration file - (See [Configuration](#configuration)) -- The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to - the specified logging level +- The logging level can be controlled using the `logLevel` setting in the configuration file. + > See [Configuration](#configuration). +- The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. -- Currently log messages are output through: `Console` and `.log` +- Currently log messages are output through: `Console` and `.log`. **Logging Levels** - SEVERE - - Critical use case affected, which may possibly cause the termination of the application + - Critical use case affected, which may possibly cause the termination of the application. - WARNING - - Can continue, but with caution + - Can continue, but with caution. - INFO - - Information important for the application's purpose + - Information important for the application's purpose. - e.g. update to local model/request sent to cloud - - Information that the layman user can understand + - Information that the layman user can understand. - FINE - - Used for superficial debugging purposes to pinpoint components that the fault/bug is likely to arise from + - Used for superficial debugging purposes to pinpoint components that the fault/bug is likely to arise from. - Should include more detailed information as compared to `INFO` i.e. log useful information! - - e.g. print the actual list instead of just its size + - e.g. print the actual list instead of just its size. ### Configuration -Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file -(default: `config.json`): +Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file. _Mastermind_ will load the xml data file from the file path specified in `config.json`. +> Stored as: `config.json`. ## Testing -**In Eclipse**: -> If you are not using a recent Eclipse version (i.e. _Neon_ or later), enable assertions in JUnit tests - as described [here](http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option). +**In Eclipse**: +> If you are not using a recent Eclipse version (i.e. _Neon_ or later), enable assertions in JUnit tests as described [here](http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option). + +* To run all tests, right-click on the `src/test/java` folder and choose + `Run as` > `JUnit Test`. +* To run a subset of tests, you can right-click on a test package, test class, or a test and choose to `Run as` a `JUnit Test`. -* To run all tests, right-click on the `src/test/java` folder and choose - `Run as` > `JUnit Test` -* To run a subset of tests, you can right-click on a test package, test class, or a test and choose - to run as a JUnit test. - **Using Gradle**: -* See [UsingGradle.md](UsingGradle.md) for how to run tests using Gradle. +* See [UsingGradle.md](UsingGradle.md) for how to run tests using Gradle. Tests can be found in the `./src/test/java` folder. -1. **GUI Tests** - These are _System Tests_ that test the entire App by simulating user actions on the GUI. - These are in the `guitests` package. - +1. **GUI Tests** - These are _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `guitests` package. + 2. **Non-GUI Tests** - These are tests not involving the GUI. They include, - 1. _Unit tests_ targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.UrlUtilTest` - 2. _Integration tests_ that are checking the integration of multiple code units - (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest` - 3. Hybrids of unit and integration tests. These test are checking multiple code units as well as - how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest` - + 1. _Unit tests_ targeting the lowest level methods/classes. + e.g. `harmony.mastermind` + 2. _Integration tests_ that are checking the integration of multiple code units + (those code units are assumed to be working). + e.g. `harmony.mastermind.commons.core` + 3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how they are connected together. + e.g. `harmony.mastermind.storage` + **Headless GUI Testing** : -Thanks to the ([TestFX](https://github.com/TestFX/TestFX)) library we use, - our GUI tests can be run in the _headless_ mode. +Thanks to the ([TestFX](https://github.com/TestFX/TestFX)) library we use, our GUI tests can be run in the _headless_ mode. In the headless mode, GUI tests do not show up on the screen. - That means the developer can do other things on the Computer while the tests are running.
- See [UsingGradle.md](UsingGradle.md#running-tests) to learn how to run tests in headless mode. - -## Continuous Integration + That means the developer can do other things on the Computer while the tests are running. See [UsingGradle.md](UsingGradle.md#running-tests) to learn how to run tests in headless mode. + +## Dev Ops -We use [Travis CI](https://travis-ci.org/) to perform _Continuous Integration_ on our projects. -See [UsingTravis.md](UsingTravis.md) for more details. +### Build Automation +See [UsingGradle.md](UsingGradle.md) to learn how to use Gradle for build automation. + +### Continuous Integration +We use [Travis CI](https://travis-ci.org/) to perform Continuous Integration on our projects to ensure that every time we merge a new feature into the main branch, automated testing is done to verify that the app is working. See [UsingTravis.md](UsingTravis.md) for more details. ## Making a Release Here are the steps to create a new release. - + 1. Generate a JAR file [using Gradle](UsingGradle.md#creating-the-jar-file). 2. Tag the repo with the version number. e.g. `v0.1` - 2. [Crete a new release using GitHub](https://help.github.com/articles/creating-releases/) - and upload the JAR file your created. - + 3. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/) and upload the JAR file you created. + ## Managing Dependencies -A project often depends on third party libraries. For example, Address Book depends on the -[Jackson library](http://wiki.fasterxml.com/JacksonHome) 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)
+A project often depends on third party libraries. For example, _Mastermind_ depends on the [Jackson library](http://wiki.fasterxml.com/JacksonHome) 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: +* Include those libraries in the repo (this bloats the repo size). +* Require developers to download those libraries manually (this creates extra work for developers). ## Appendix A : User Stories @@ -250,62 +484,781 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (un Priority | As a ... | I want to ... | So that I can... -------- | :-------- | :--------- | :----------- -`* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App -`* * *` | user | add a new person | -`* * *` | user | delete a person | remove entries that I no longer need -`* * *` | user | find a person by name | locate details of persons without having to go through the entire list -`* *` | user | hide [private contact details](#private-contact-detail) 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 +`* * *` | new user | see usage instructions | refer to instructions when I forget how to use the app +`* * *` | user | add a new task | so I can register my things to do +`* * *` | user | add a floating task | have a task without a deadline +`* * *` | user | add a recurring task | add repeating tasks only once +`* * *` | user | delete a task | remove entries that I no longer need +`* * *` | user | edit a task | update entries as needed +`* * *` | user | find a task by name | locate details of the task without having to go through the entire list +`* * *` | user | find a task by deadline | locate tasks that are due soon without having to go through the entire list +`* * *` | user | undo a task entered | undo a mistake easily +`* * *` | user | re-do a task entered | redo a mistake easily +`* * *` | user | sort list by alphabetical order and date | find my tasks easily +`* * *` | user | mark tasks as done | archive my completed tasks +`* * *` | user | specify the location of file storage | choose where to save the to do list +`* *` | user | see my tasks in user interface | have an easier time using the app +`* *` | user | show upcoming tasks | can see tasks that are nearing +`*` | user | specify my own natural language | customise the app +`*` | user | set categories | organise my tasks +`*` | user | block out timings | reserve time slots for tasks +`*` | user | create subtasks | breakdown my tasks into smaller problems +`*` | user | set reminders for tasks | reduce chances of forgetting to do a task +`*` | user | import a list of to do tasks | add in tasks without registering them individually +`*` | user | export a list of tasks | export to another computer + -{More to be added} ## Appendix B : 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 _Mastermind_ and the **Actor** is the _User_, unless specified otherwise) + +| # | Use Case | Descriptions | +|---|---|---| +| [UC1](#uc1-display-help) | Display Help | Display help when requested. | +| [UC2](#uc2-adddo-a-task) | Add/Do a Task | Adding a task. A task can be concrete (have defined date/time) or floating (without date/time). | +| [UC3](#uc3-edit-a-task) | Edit a Task | Edit the details of a single task. The command only update fields specified by the User. Unspecified field remains untouched. | +| [UC4](#uc4-mark-task-as-done) | Mark Task as done | Mark a task as done by index. A marked task should be automatically archived and exclude from display and search. | +| [UC5](#uc5-unmark-a-task) | Unmark a Task | Unmark a task as done by index. The Archived task will add back to the respective tabs.| +| [UC6](#uc6-delete-a-task) | Delete a task | Remove a task entry by index. | +| [UC7](#uc7-undo-action) | Undo Action | Undo last action performed. | +| [UC8](#uc8-redo-action) | Redo Action | Redo an action performed in UC7. | +| [UC9](#uc9-lists-tasks) | Lists Tasks | Display lists of tasks added into the System. | +| [UC10](#uc10-find-tasks) | Find Tasks | Search for task by name with keywords. | +| [UC11](#uc11-find-tasks-by-tag) | Find Tasks by tag | Search for task by tag with keywords. | +| [UC12](#uc12-upcoming-task) | Show upcoming Tasks | Display floating tasks and task that is due in a weeks time. | +| [UC13](#uc13-relocate-storage-location) | Relocate storage location | Change the current storage to other directory specified by the user. | +| [UC14](#uc14-importing-files-to-mastermind) | Import File | Adds tasks identified in file | +| [UC15](#uc15-exporting-files-to-.csv-or-.ical) | Export | Exports data as .csv or .ical | +| [UC16](#uc16-history) | History | Toggles the Action History Bar | +| [UC17](#uc17-clear-everything) | Clears everything | System performs bulk delete on the data (Deadlines, events, tasks). | +| [UC17](#uc17-exit-application) | Exit application | Quit the application | + +--- + +### UC1: Display help + +Display help when requested. + +##### Main Success Scenario: + +1. User requests to display help. + +2. System display the help popup. + +3. User presses any key to close the popup. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC2: Add/Do a Task + +Adding a task. A task can be concrete (have defined date/time), recurring (repeats with defined date/time) or floating (without date/time). + +##### Main Success Scenario: + +1. User requests to add a task. + +2. System accepts the command & parameters, creates the task and displays successful message to User. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +1b. User enters task with date/time. + +* 1b1. System accepts the command as concrete task. + +* 1b2. Use case resume at 2. + +1c. User enters task without date/time. + +* 1c1. System accepts the command as floating task. + +* 1c2. Use case resume at 2. + +1d. User enters task with start date after end date. + +* 1d1. System display error message. + +* 1d2. Use case ends. + +1e. User enters recurring task without date/time. + +* 1e1. System accepts the command as floating task. + +* 1e2. Use case resumes at 2. + +1f. User enters recurring task with date/time. + +* 1f1. System accepts the command as recurring task. + +* 1f2. Use case resume at 2. + + +--- + +### UC3: Edit a Task + +Edit the details of a single task. The command only update fields specified by the User. Unspecified field remains untouched. + +##### Main Success Scenario + +1. User request to edit a task by index. + +2. System find and update the task. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +--- + +### UC4: Mark Task as done + +Mark a task entry by index or keyword 'due'. + +##### Main Success Scenario + +1. User request to mark a task by index. + +2. System find and mark the task and remove from respective tab and add to archives tab. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +2b. User tries to mark a Task that is already marked. + +* 2b1. System check that task associated with the index is already marked. + +* 2b2. System display unsuccessful message. + +* 2b3. Use case ends. + +2c. User wants to mark all due tasks. + +* 2c1. System checks for all tasks that are due + +* 2c2. System removes all tasks that are due + +* 2c3. Use case continues to 3. + +--- + +### UC5: Unmark a Task + +Unmark a task entry by index. + +##### Main Success Scenario + +1. User request to unmark a task by index. + +2. System find and unmark the task and remove from archives tab and add to the respective tab. + +3. System display successful message. + +4. Use case ends. + +##### Extensions -#### Use case: Delete person +1a. User entered an invalid command. -**MSS** +* 1a1. System display unsuccessful message. -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
-Use case ends. +* 1a2. Use case ends. -**Extensions** +2a. Invalid index. -2a. The list is empty +* 2a1. System cannot find the task associated with the index. -> Use case ends +* 2a2. System display unsuccessful message. -3a. The given index is invalid +* 2a3. Use case ends. -> 3a1. AddressBook shows an error message
- Use case resumes at step 2 +2b. User tries to Unmark a Task that not marked yet. -{More to be added} +* 2b1. System check that task associated with the index is not marked yet. + +* 2b2. System display unsuccessful message. + +* 2b3. Use case ends. + +--- + +### UC6: Delete a Task + +Remove a task entry by index. + +##### Main Success Scenario + +1. User request to delete a task by index. + +2. System find and remove the task. + +3. System display successful message. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid index. + +* 2a1. System cannot find the task associated with the index. + +* 2a2. System display unsuccessful message. + +* 2a3. Use case ends. + +--- + +### UC7: Undo Action + +Undo last action performed. + +##### Main Success Scenario + +1. User requests to undo last action performed. + +2. System pop from Undo stack and performs undo on the last action performed. + +3. System put the action into Redo stack. + +3. System display successful message and details of the undo operation. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any previous action in Undo stack. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC8: Redo Action + +Redo an action performed in UC7. + +##### Main Success Scenario + +1. User requests to redo an action performed in UC7. + +2. System pop from Redo stack and performs redo on the last action performed in UC7. + +3. System display successful message and details of the redo operation. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any previous action in Redo stack. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC9: Lists Tasks + +Display lists of tasks added into the System. + +##### Main Success Scenario: + +1. User requests to lists tasks. + +2. System display list of tasks. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC10: Find Tasks by name + +Search for task by name. + +Note: The combination is filtered using **OR** operation. + + +##### Main Success Scenario: + +1. User request to search for a task by name. + +2. System Search for a task that matches the parameters. + +3. System displays the matching results to the User. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC11: Find Tasks by tags. + +Search for tasks by tags. + +Note: The combination is filtered using **OR** operation. + +##### Main Success Scenario: + +1. User request to search for a tasks by tags. + +2. System Search for a task that matches the parameters. + +3. System displays the matching results to the User. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + +--- + +### UC11: Upcoming Tasks + +Shows all floating tasks, deadlines and events that are due in the upcoming week. + +##### Main Success Scenario + +1. User requests to load upcoming command. + +2. System filter and display upcoming tasks. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + + +1b. User requests to show upcoming deadlines. + +* 1b1. System filters only deadlines. + +* 1b2. Use case resumes at 2. + +1c. User requests to show upcoming deadlines. + +* 1c1. System filters only deadlines. + +* 1c2. Use case resumes at 2. + +2a. System cannot find any task. + +* 2a1. System display empty list. + +* 2a2. Use case ends. + + + +--- + +### UC12: Relocate storage location + +Change the current storage to other directory specified by the user. + +##### Main Success Scenario + +1. User requests to relocate the storage directory. + +2. System changes the storage directory according to user input. + +3. System copies current storage to the new location. + +4. System deletes old file at old storage location. + +5. System displays successful message. + +6. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid storage location. + +* 2a1. System displays invalid path message. + +* 2a2. Use case ends. + +2b. Storage location is not accessible/writable. + +* 2b1. System displays unwrittable file message. + +* 2b2. Use case ends. + +--- + +### UC13: Importing files to Mastermind + +Import `.ics` or `.csv` file and add the relevant tasks into Mastermind. + +##### Main Success Scenario + +1. User requests to import file. + +2. System locate the file and attempt to read. + +3. System adds tasks identified from file into Mastermind. + +4. System displays successful message. + +5. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid file location. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +2b. Invalid file type. + +* 2b1. System display unsuccessful message. + +* 2b2. Use case ends. + +--- + +### UC14: Exporting files to .csv +Exports selected data to `.csv`. + +##### Main Success Scenario + + +1. User requests to export file. + +2. System locates the export location. + +3. System attempts to write tasks identified from Mastermind into file + +4. System displays successful message. + +5. Use case ends. + +##### Extensions +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +2a. Invalid file location. + +* 2a1. System display unsuccessful message. + +* 2a2. Use case ends. + +--- + +### UC15: History +Toggles the Action History Bar + +##### Main Success Scenario +1. User requests to toggle Action Histroy Bar + +2. Action History Bar toggled. + +3. Use case ends. + +##### Extensions +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC16: Clear everything + +System performs bulk delete on the data (Deadlines, events, tasks). +##### Main Success Scenario + +1. User requests to clear Mastermind + +2. System proceed to perform bulk action described in UC6 for the specified category. + +3. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System displays unsuccessful message. + +* 1a2. Use case ends. + +--- + +### UC17: Exit application + +Quit the application. + +##### Main Success Scenario + +1. User requests to exit application. + +2. System perform synchronization and save it to the storage. + +3. System exit application. + +4. Use case ends. + +##### Extensions + +1a. User entered an invalid command. + +* 1a1. System display unsuccessful message. + +* 1a2. Use case ends. + +--- ## Appendix C : Non Functional Requirements -1. Should work on any [mainstream OS](#mainstream-os) as long as it has Java `1.8.0_60` or higher installed. -2. Should be able to hold up to 1000 persons. -3. Should come with automated unit tests and open source code. -4. Should favor DOS style commands over Unix-style commands. +1. Should backup tasks list. +2. Should work as a stand alone. +3. Should be able to store 1000 tasks. +4. Should be maintainable and scalable. +5. Should not use relational databases. +6. Should be user friendly for new users. +7. Should be able to be accessed offline. +8. Should come with automated unit testing. +9. Should follow the Object-oriented paradigm. +10. Should work without requiring an installer. +11. Should be able to startup and quit within 1 second. +12. Should display up to date tasks when command is given. +13. Should store data locally and should be in a .xml file. +14. Should work well without any third party framework/library. +15. Should have a Command Line Interface as the primary mode of input. +16. Should be able to display tasks within 1 second when command is given. +17. Should be able to run on all [mainstream OS](#mainstream-os) for desktop. +18. Should have a simple GUI that displays [relevant information](#relevant-information). -{More to be added} ## Appendix D : Glossary ##### Mainstream OS -> Windows, Linux, Unix, OS-X +> Windows, Linux, Unix, OS-X. -##### Private contact detail +##### Relevant Information -> A contact detail that is not meant to be shared with others +> Tasks, due dates, tags. -## Appendix E : Product Survey -{TODO: Add a summary of competing products} +## Appendix E : Product Survey +##### Google Calendar + +> Pros: +> * Able to sync calendars from other people +> * Chrome extension for offline connectivity +> * Multiple viewing options (Calendar/To do list view) +> * Has a Command Line Interface (CLI) + +> Cons: +> * Unable to support floating task +> * Unable to mark tasks as done +> * Unable to block out and free up timings +> * CLI commands only for addition of tasks +> * Bad interface + +##### Wunderlist + +> Pros: +> * Able to set categories +> * Able to mark tasks as done +> * Able to read tasks from e-mails +> * Able to assign tasks to someone +> * Able to search for tasks easily +> * Able to migrate tasks from one category to another easily +> * Web and offline desktop version available + +> Cons: +> * Unable to create subtask +> * Unable to support recurring tasks +> * Unable to block out time slots +> * Unable to set start date for tasks +> * Only has a list view + +##### Todoist + +> Pros: +> * Able to set categories +> * Able to collaborate with others +> * Able to have sub-projects and sub-tasks +> * Able to support recurring tasks +> * Able to sort tasks by priority level +> * Able to integrate from e-mail +> * Able to backup automatically + +> Cons: +> * Unable to block out timings +> * Unable to export out To-do list +> * Minimal CLI +> * Have to do a lot of clicking + +##### Any.Do + +> Pros: +> * Able to set categories by type and day +> * Able to show completed tasks +> * Able to collaborate with others +> * Able to support sub-tasks +> * Able to add attachments +> * Able to support recurring tasks +> * Able to mark task as done +> * Able to notify and remind user +> * Able to have action shortcuts +> * Able to have different types of views + +> Cons: +> * Unable to support floating tasks +> * No CLI + +##### Evernote + +> Pros: +> * Able to quick search +> * Able to support handwriting, embedded images/audio and links +> * Able to work with camera + +> Cons: +> * No CLI +> * No Calendar view + +##### Trello + +> Pros: +> * Able to mark tasks as "in-progress" +> * Able to view as calendar + +> Cons: +> * Unable to import or export +> * Relies on UI interaction +> * No CLI +> * Need to pay for premium use to access 3rd party features +> * No desktop version diff --git a/docs/UserGuide.md b/docs/UserGuide.md index c18832432d1f..b33b66f7d172 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,127 +1,574 @@ + # User Guide * [Quick Start](#quick-start) * [Features](#features) +* [Special Features](#special-features) * [FAQ](#faq) * [Command Summary](#command-summary) +* [Special Command Summary](#special-command-summary) + ## Quick Start -0. Ensure you have Java version `1.8.0_60` or later installed in your Computer.
- > Having any Java 8 version is not enough.
- This app will not work with earlier versions of Java 8. - -1. Download the latest `addressbook.jar` from the 'releases' tab. -2. Copy the file to the folder you want to use as the home folder for your Address Book. -3. Double-click the file to start the app. The GUI should appear in a few seconds. - > - -4. Type the command in the command box and press Enter to execute it.
- e.g. typing **`help`** and pressing Enter will open the help window. -5. Some example commands you can try: - * **`list`** : lists all contacts - * **`add`**` John Doe p/98765432 e/johnd@gmail.com a/John street, block 123, #01-01` : - adds a contact named `John Doe` to the Address Book. - * **`delete`**` 3` : deletes the 3rd contact shown in the current list - * **`exit`** : exits the app -6. Refer to the [Features](#features) section below for details of each command.
+1. Ensure you have Java version `1.8.0_60` or later installed in your Computer. + +2. Download the latest `mastermind.jar` from our repository's [releases](https://github.com/CS2103AUG2016-W11-C3/main/releases) tab. +3. Copy the file to the folder you want to use as the home folder for your Mastermind. +4. Double-click the file to start the app. The app should appear in a few seconds. + > + +5. Type the command in the command box and press Enter to execute it. + + + > Some example commands you can try: + > + > - `add CS2103T tutorial by tomorrow 11am` + > - adds a task named `CS2103T tutorial` with deadline due tomorrow at 11am. + > - `delete 1` + > - deletes the 1st task shown in the current list. + + +6. Refer to the [Features](#features) section below for details of each command. ## Features -#### Viewing help : `help` -Format: `help` - -> Help is also shown if you enter an incorrect command e.g. `abcd` - -#### Adding a person: `add` -Adds a person to the address book
-Format: `add NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` - -> Words in `UPPER_CASE` are the parameters, items in `SQUARE_BRACKETS` are optional, -> items with `...` after them can have multiple instances. Order of parameters are fixed. -> -> Persons can have any number of tags (including 0) - -Examples: -* `add John Doe p/98765432 e/johnd@gmail.com a/John street, block 123, #01-01` -* `add Betsy Crowe p/1234567 e/betsycrowe@gmail.com a/Newgate Prison t/criminal t/friend` - -#### Listing all persons : `list` -Shows a list of all persons in the address book.
-Format: `list` - -#### Finding all persons containing any keyword in their name: `find` -Finds persons whose names contain any of the given keywords.
-Format: `find KEYWORD [MORE_KEYWORDS]` - -> The search is case sensitive, the order of the keywords does not matter, only the name is searched, -and persons matching at least one keyword will be returned (i.e. `OR` search). - -Examples: -* `find John`
- Returns `John Doe` but not `john` -* `find Betsy Tim John`
- Returns Any person having names `Betsy`, `Tim`, or `John` - -#### Deleting a person : `delete` -Deletes the specified person from the address book. Irreversible.
-Format: `delete INDEX` - -> Deletes the person 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, ... - -Examples: -* `list`
- `delete 2`
- Deletes the 2nd person in the address book. -* `find Betsy`
- `delete 1`
- Deletes the 1st person in the results of the `find` command. - -#### Select a person : `select` -Selects the person identified by the index number used in the last person listing.
-Format: `select INDEX` - -> Selects the person and loads the Google search page the person 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, ... - -Examples: -* `list`
- `select 2`
- Selects the 2nd person in the address book. -* `find Betsy`
- `select 1`
- Selects the 1st person in the results of the `find` command. - -#### Clearing all entries : `clear` -Clears all entries from the address book.
-Format: `clear` - -#### Exiting the program : `exit` -Exits the program.
-Format: `exit` - -#### Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data.
-There is no need to save manually. +### Command Formatting Style + +>* Words in `angle brackets < >` are required parameters, fields in `square brackets [ ]` are optional. +>* Fields that are appended with `ellipsis ...` such as `...` indicates that the field accept multiple of values. +>* Tasks will be added to the categories (event, deadlines, floating task) according to the keywords to the parameters inputed. +>* Parameters can be in any order. +>* Separate different tags with `comma ,`. +>* There are no limit to the number of tags a task can have. + +### Viewing help : `help` + +First, let's get familiar with the command features that _Mastermind_ offers! Type `help` and press Enter to display all the possible command usage. + +_Format:_ +```java +help +``` + +> _Quick Tip: Press any key to close the popup!_ + +### Adding a task: `add`, `do` + +For now, an empty list is not very interesting. Try to create a new task using `add` or `do` command. Both commands are equivalent. They exist to help you in constructing a more fluent command structure. + +_Mastermind_ automatically helps you to organize your task into three main categories: +- **Event**: Task with `startDate` and `endDate` specified. +- **Deadline**: Task with only `endDate` specified. +- **Floating Task**: Task without `startDate` and `endDate`. + +> _Quick Tip: Tag your task so you can find them easier with `find` command!_ +> - You can have multiple tags to your tasks! Simply just separate the different tags using a comma without a space. +> - There are no limit to the number of tags a task can have, feel free to tag as many as you like! + +#### Adds an event +_Format:_ +```java +(add|do) from to [#comma_separated_tags] +``` + +_Example:_ +```java +> add attend workshop from today 7pm to next monday 1pm #programming,java +``` +#### Adds a task with deadline +_Format:_ + +```java +(add|do) by [#comma_separated_tags] +``` + +_Example:_ + +```java +> add submit homework by next sunday 11pm #math,physics +``` +#### Adds a floating task +_Format:_ +```java +(add|do) [#comma_separated_tags] +``` + +_Example:_ +```java +> do chores #cleaning +``` + +#### Adds a recurring deadline +_Format:_ +```java +(add|do) by [daily|weekly|monthly|yearly] [#comma_separated_tags] +``` + +_Example:_ +```java +> do chores today daily #cleaning +> do workshop from tomorrow 3pm to tomorrow 5pm weekly +``` + +> _Quick Tip: You can add recurring events too!_ +> Use keywords like ```'daily', 'weekly', 'monthly' and 'yearly'``` for recurring tasks. + +> _Mastermind_ uses [natural language processing](http://www.ocpsoft.org/prettytime/nlp/) for `` & ``, therefore it does not enforce any specific date format. Below are the possible list of date constructs that _Mastermind_ accepts: +> +> `tomorrow 6pm` +> `next saturday` +> `13 Oct 2016 6pm` +> `...` +> +> The list above is not exhaustive. Feel free to try any possible combination you have in mind! +> +> If the date you entered is incorrectly parsed, please enter a more explicit combination such as "tomorrow 6pm" instead of "tomorrow at 6". + + +### Editing a task : `edit`, `update`, `change` +Perhaps now you have a change of schedule, or you are unsatisfied with the task name you just created. _Mastermind_ allows you to quickly modify the task you created before using `edit` command. + +_Format:_ +```java +(edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #,] +``` + +>* At least one optional field is required. +>* You can edit as many fields as required. +>* Each field is separated by a comma. +>* You can omit the last `comma ,` if there's no more fields following: +>`edit 1 name to dinner with parents, start date to tomorrow 7pm` +>* Any malformed field will be dropped. + +_Examples:_ + +```java +// Selects the 2nd task in Mastermind and edit the task name to Dinner. +> edit 2 name to parents with dinner, end date to tomorrow 7pm, recur daily, tags to #meal,family +``` +```java +// Selects the 1st task and edit the `startDate` to tomorrow 8pm. +> change 1 start date to tomorrow 8pm +``` + + +### Completing tasks : `mark` + +Mission accomplished! Now you can ask _Mastermind_ to `mark` a task as completed. + +_Mastermind_ will archive the task for you. See Archives tab to review your completed task. + +_Format:_ +```java +mark +``` +```java +mark due +``` + +> ```mark``` only affects tasks that are not complete yet. It has no effect on completed tasks. + +> Using `mark due` it will mark all tasks that are due. + +_Examples:_ +```java +// use "find" command to look for a specific task +> find CS2010 + +// select the "find" result and mark the task at index 1 as completed +> mark 1 +``` + +```java +// marks all due tasks in the tab +> mark due +``` +### Unmarking a task : `unmark` + +Oh no! You realise that one of your task is not complete yet but you have marked it. Not to worry, you can `unmark` that task. + +_Format:_ +```java +unmark +``` + +> ```unmark``` only affects task that are completed. It has no effect on an uncompleted task. + +_Examples:_ +```java +// list all the tasks that are completed +> list archives + +// mark task at index 1 as completed +> unmark 1 +``` + +```java +// use "find" command to look for a specific task +> find CS2010 + +// select the "find" result and mark the task at index 1 as completed +> unmark 1 +``` + + +### Deleting a task : `delete` + +You just realize Taylor Swift concert is cancelled! Oh no! + +Sadly, you have to ask _Mastermind_ to remove the event from your bucket list. _Mastermind_ does the removal for you with a sympathetic pat on your shoulder. + +_Format:_ +```java +delete +``` + +>* Deletes the task at the specified `index`. +>* The index refers to the index number shown in the most recent listing. + +_Examples:_ +```java +// delete entry listed at index 1 +> delete 1 +``` + +### Undo a command : `undo` + +Suddenly, you received a call that Taylor Swift concert is coming back up! Maybe you should buy the ticket after all. + +Don't worry. _Mastermind_ has a built in time machine that helps you travel to the past! You can execute the `undo` command to recover the task you just deleted! + +_Format:_ +```java +undo +``` + +> `undo` only affect commands such as `add`, `edit`, `delete`, `mark`, `unmark`. It has no effect on command such as `list`, `find`, `relocate`, `import`, `export`, `history` and `clear`. + +Example: +```java +// deleted the task: "Buy ticket for Taylor Swift concert" +> delete 1 + +// Undo the last action +> undo + +// returns +// Undo successfully. +// =====Undo Details===== +// [Undo Delete Command] Task added: Buy ticket for Taylor Swift concert. Tags: +// ================== +``` + +> You can `undo` as many times as you like. +> +> However, the `undo` takes effect on commands that you entered in the current session only. _Mastermind_ will forget the `undo` history once you close the application. + +### Redo a command : `redo` + +_Mastermind_ can travel back to the present too! If you ever regret your `undo` command, _Mastermind_ can `redo` command that you just undid. + +_Format:_ +```java +redo +``` +> `redo` is only available immediately after an `undo` command is used. + +_Example:_ +```java +// Redo the last command being undone. Refer to undo command. +> redo + +// returns +// Redo successfully. +// =====Undo Details===== +// [Redo Delete Command] Task delete: Buy ticket for Taylor Swift concert. Tags: +// ================== +``` + +> You can `redo` as many times as you like. +> +> However, the `redo` takes effect on commands that you entered in the current session only. _Mastermind_ will forget the `redo` history once you close the application. +> +> Upon executing a new command (except `undo` and `redo`), _Mastermind_ will forget any existing command remaining in the `redo` history. + + + +### Listing all tasks of a category: `list` + +After adding some tasks into _Mastermind_, you can `list` them according to their category. In addition to the [three main categories](#adding-a-task-add-do) mentioned in `add` command, _Mastermind_ also keeps a summarized view under Home tab; the Archives tab is reserved for task marked as completed. + +_Format:_ +```java +list [tab_name] +``` + +> Possible values of `tab_name` includes (case-insensitive): +> `Home` +> `Tasks` +> `Events` +> `Deadlines` +> `Archives` +> +> _Quick Tip: You can also press Ctrl + 1, 2, 3, 4 or 5 to switch to the respective tabs._ + + + +### Finding tasks that contain any keywords: `find` + +Even with the list category feature, scrolling through possibly thousands of tasks to find a specific one can be quite troublesome. Fret not! _Mastermind_ provides you with the `find` command to quickly search for a specific task your are looking for. + +`find` command displays a result of all tasks whose description contain any of the given keywords. + +_Format:_ + +```java +find ... +``` + +>* The search is case-insensitive. +>* The order of the keywords does not matter. +>* Tasks matching at least one keyword will be returned. + +_Examples:_ + +```java +// returns Dinner on 10/10/16 at 1900hrs (Date) +> find Dinner +``` +```java +// returns CS2010 PS10 on 11 Oct by 1000hrs (Assignment) +> find "cs2010 ps10" +``` +> using find will always switch back to the home tab. + +### Finding all tasks that contain specific tags: `findtag` + +You have tagged your tasks previously and now you want to know what tasks are in that category. +you can use `findtag` command to quickly search for tasks that are tagged with that specific tag. + +`findtag` command display result of all tasks whose tags contain the given keywords. + +_Format:_ + +```java +findtag ... +``` + +>* The search is case-sensitive. +>* The order of the keywords does not matter. +>* Tasks matching at least one keyword will be returned. + +_Examples:_ + +```java +// returns Dinner on 10/10/16 at 1900hrs (Date) +> findtag date +``` + + +### Show upcoming tasks : `upcoming` + +What if you have too many task to do and only want to know tasks that are going to be due. You can ask _Mastermind_ to list `upcoming` tasks! + +_Format:_ +```java +upcoming [tab_name] +``` + +> * Shows all floating tasks as well if there is no `[tab_name]` included. +> * Does not show tasks that are already due. + +_Examples:_ +```java +// list all tasks that are due within a weeks time. +> upcoming +``` +```java +// list all deadlines that are due within a weeks time. +> upcoming deadlines +``` + + + +### Changing save location : `relocate` + +_Mastermind_ allows you to relocate the save file to a new destination folder. + +_Format:_ +``` +relocate +``` + +_Example:_ +```java +// save file has been relocated to new destination folder at ~/document/mastermind +> relocate ~/document/mastermind +``` + +> You can relocate into a cloud shared folder to access your save data from any other computer! +> +> Remember to input a folder and not a file! + +### Importing file : `import` + +_Mastermind_ allows you to import file from other to do list into _Mastermind_. + +Currently _Mastermind_ supports _.csv_ and _.ics_ file. + +> _.csv_ must be compliant to Google Calendar [specification](https://support.google.com/calendar/answer/37118?hl=en). + +_Format:_ +``` +import from +``` + +_Example:_ +```java +// import success +> import from ~/document/mastermind/data.ics +``` + +### Exporting data: `export` + +_Mastermind_ can assist you in exporting your data to _.csv_ format too. The _.csv_ file is fully compatible with Google Calendar so you can use it to synchronize with your personal calendar. + +_Format:_ +```java +export [tasks] [deadlines] [events] [archives] to +``` + +_Example:_ +```java +// export all data to personal folder +> export to C:\Users\Jim\mastermind.csv +``` + +```java +// export only deadlines and events data to personal folder +> export events deadlines to C:\Users\Jim\mastermind.csv +``` + + +### Recall your action history: `history` + +_Mastermind_ remembers the list of commands you executed. If you ever forget what you've executed hours ago or are unsure of what you can `undo` or `redo`, you can ask _Mastermind_ to show you the history. + +_Format:_ +```java +history +``` + +_Example:_ +```java +> history +``` + +> * _Quick Tip: You can also click on the status bar above the command field to open up your action history._ +> * Clicking on the history entry will display the result of that particular command you executed on the right panel. +> * The command will toggle the status bar open or close. + + + +### Clearing all entries: `clear` + +Want to clear everything? _Mastermind_ can do some spring cleaning for you in all categories. + +_Format:_ +```java +clear +``` + +_Example:_ +```java +// All tasks in all categories are deleted +> clear +``` +> `clear` command cannot be undone. Make sure you really mean to discard everything on _Mastermind_! + + +### Exiting the program : `exit` + +_Mastermind_ says, "Goodbye!" + +Exits the application. + +> Worried that you have not saved your file? take a look [here](#faq) + +_Format:_ +``` +exit +``` + +_Example:_ +```java +// Mastermind says, "Goodbye!" +> exit +``` + + + +## Special Features + +### Autocomplete +_Mastermind_ is smart. While typing into _Mastermind_, it will prompt you on commands that contains letters you input. You can press Enter to complete the sentence. +_Mastermind_ will also learn the inputs you type for easier typing in the future. You can press Esc to stop the instance of Autocomplete. + + +### Repeating a previous command: + +Lazy to retype a similar command? Want to paste the previous command back to the field? +_Mastermind_ can do just that! + +> You can go back as many previous command as you want. +> Similarly, you can press to get the next command. + +_Format:_ + (Up arrow key) + ## 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 preious Address Book. - +**Q**: How do I transfer my data to another Computer? +**A**: Install the application in the other computer and overwrite the empty mastermind.xml file it creates with the mastermind.xml file that contains the data of your previous Mastermind. +Alternatively you can use the export and import function. However if you use this method, the tags will not be added to the new application. + +**Q**: Is my data secure? +**A**: Your data is stored locally on your hard drive as a .xml file. Your data is as secure as your computer + +**Q**: Where is the save button or command? +**A**: Mastermind's data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. + + ## Command Summary Command | Format --------- | :-------- -Add | `add NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` -Clear | `clear` -Delete | `delete INDEX` -Find | `find KEYWORD [MORE_KEYWORDS]` -List | `list` -Help | `help` -Select | `select INDEX` +-------- | :-------- +[Help](#viewing-help--help) | `help` +[Add, Do](#adding-a-task-add-do) | (add|do) from [start_date] to [end_date] [daily|weekly|monthly|yearly] [#comma_separated_tags] +[Edit, Update, Change](#editing-a-task--edit-update-change) | (edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #] +[Mark](#completing-tasks--mark) | mark +[Unmark](#unmarking-a-task--unmark) | `unmark ` +[Delete](#deleting-a-task--delete) | `delete ` +[Undo](#undo-a-command--undo) | `undo` +[Redo](#redo-a-command--redo) | `redo` +[List](#listing-all-tasks-of-a-category-list) | `list [tab_name]` +[Find](#finding-tasks-that-contain-any-keywords-find) | `find ...` +[Find tag](#finding-all-tasks-that-contain-specific-tags-findtag) | `findtag ...` +[Upcoming](#show-upcoming-tasks--upcoming) | `upcoming [tab_name]` +[Relocate](#changing-save-location--relocate) | `relocate ` +[Import](#importing-file--import-from) | `import from ` +[Export](#exporting-data-export) | `export [tasks] [deadlines] [events] [archives] to ` +[History](#recall-your-action-history-history) | `history` +[Clear](#clearing-all-entries-clear) | `clear` +[Exit](#exiting-the-program--exit) | `exit` + + +## Special Feature Summary + +Command | Format +-------- | :-------- +[Autocomplete](#autocomplete) | Enter +[Previous](#repeating-a-previous-command-↑) | diff --git a/docs/diagrams/Software Architecture.pptx b/docs/diagrams/Software Architecture.pptx new file mode 100644 index 000000000000..85cbb2f37c9a Binary files /dev/null and b/docs/diagrams/Software Architecture.pptx differ diff --git a/docs/images/Add Command Sequence Diagram part 1.png b/docs/images/Add Command Sequence Diagram part 1.png new file mode 100644 index 000000000000..d5ca0d645c51 Binary files /dev/null and b/docs/images/Add Command Sequence Diagram part 1.png differ diff --git a/docs/images/Add Command Sequence Diagram part 2.png b/docs/images/Add Command Sequence Diagram part 2.png new file mode 100644 index 000000000000..e2517ec38765 Binary files /dev/null and b/docs/images/Add Command Sequence Diagram part 2.png differ diff --git a/docs/images/Akshay.jpg b/docs/images/Akshay.jpg new file mode 100644 index 000000000000..65856ab608b1 Binary files /dev/null and b/docs/images/Akshay.jpg differ diff --git a/docs/images/AlbertYeoh.png b/docs/images/AlbertYeoh.png new file mode 100644 index 000000000000..28c7b0decf13 Binary files /dev/null and b/docs/images/AlbertYeoh.png differ diff --git a/docs/images/Architecture_diagram.png b/docs/images/Architecture_diagram.png new file mode 100644 index 000000000000..4deb6cbdc52d Binary files /dev/null and b/docs/images/Architecture_diagram.png differ diff --git a/docs/images/Delete Command Sequence Diagram part 1.png b/docs/images/Delete Command Sequence Diagram part 1.png new file mode 100644 index 000000000000..6fec48a2dd14 Binary files /dev/null and b/docs/images/Delete Command Sequence Diagram part 1.png differ diff --git a/docs/images/Delete Command Sequence Diagram part 2.png b/docs/images/Delete Command Sequence Diagram part 2.png new file mode 100644 index 000000000000..32a9f030bd22 Binary files /dev/null and b/docs/images/Delete Command Sequence Diagram part 2.png differ diff --git a/docs/images/DylanChew.png b/docs/images/DylanChew.png new file mode 100644 index 000000000000..417aa55b6073 Binary files /dev/null and b/docs/images/DylanChew.png differ diff --git a/docs/images/Edit Command Sequence Diagram part 1.png b/docs/images/Edit Command Sequence Diagram part 1.png new file mode 100644 index 000000000000..42c7fceb21c5 Binary files /dev/null and b/docs/images/Edit Command Sequence Diagram part 1.png differ diff --git a/docs/images/Edit Command Sequence Diagram part 2.png b/docs/images/Edit Command Sequence Diagram part 2.png new file mode 100644 index 000000000000..9af71ae00fed Binary files /dev/null and b/docs/images/Edit Command Sequence Diagram part 2.png differ diff --git a/docs/images/HuiQi.png b/docs/images/HuiQi.png new file mode 100644 index 000000000000..86422e7a4edf Binary files /dev/null and b/docs/images/HuiQi.png differ diff --git a/docs/images/KangFei.png b/docs/images/KangFei.png new file mode 100644 index 000000000000..8d04eae04d53 Binary files /dev/null and b/docs/images/KangFei.png differ diff --git a/docs/images/Logic_diagram.png b/docs/images/Logic_diagram.png new file mode 100644 index 000000000000..086d34014c5b Binary files /dev/null and b/docs/images/Logic_diagram.png differ diff --git a/docs/images/Mastermind.PNG b/docs/images/Mastermind.PNG new file mode 100644 index 000000000000..4db1089c9a87 Binary files /dev/null and b/docs/images/Mastermind.PNG differ diff --git a/docs/images/Mastermind_Software_Architecture.jpg b/docs/images/Mastermind_Software_Architecture.jpg new file mode 100644 index 000000000000..345f87173ed4 Binary files /dev/null and b/docs/images/Mastermind_Software_Architecture.jpg differ diff --git a/docs/images/Model UML.png b/docs/images/Model UML.png new file mode 100755 index 000000000000..7acd894bd084 Binary files /dev/null and b/docs/images/Model UML.png differ diff --git a/docs/images/Model_diagram.png b/docs/images/Model_diagram.png new file mode 100644 index 000000000000..74fe4339a968 Binary files /dev/null and b/docs/images/Model_diagram.png differ diff --git a/docs/images/Storage UML.png b/docs/images/Storage UML.png new file mode 100644 index 000000000000..b00e1b5d3388 Binary files /dev/null and b/docs/images/Storage UML.png differ diff --git a/docs/images/Storage_diagram.png b/docs/images/Storage_diagram.png new file mode 100644 index 000000000000..3414d02ea3ad Binary files /dev/null and b/docs/images/Storage_diagram.png differ diff --git a/docs/images/UI_diagram.png b/docs/images/UI_diagram.png new file mode 100644 index 000000000000..3da87ce8cbb8 Binary files /dev/null and b/docs/images/UI_diagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 7121a50a442a..f6aeac8aa1a6 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/developer-guide-logic.png b/docs/images/developer-guide-logic.png new file mode 100755 index 000000000000..dca7083c9982 Binary files /dev/null and b/docs/images/developer-guide-logic.png differ diff --git a/docs/images/developer-guide-ui.png b/docs/images/developer-guide-ui.png new file mode 100755 index 000000000000..07809400b4e8 Binary files /dev/null and b/docs/images/developer-guide-ui.png differ diff --git a/docs/images/latest_ui.png b/docs/images/latest_ui.png new file mode 100644 index 000000000000..00d1a1a665f6 Binary files /dev/null and b/docs/images/latest_ui.png differ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/harmony/mastermind/MainApp.java similarity index 76% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/harmony/mastermind/MainApp.java index 4b48efe60824..0dbd544f971b 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/harmony/mastermind/MainApp.java @@ -1,24 +1,25 @@ -package seedu.address; +package harmony.mastermind; import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.Config; +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.core.Version; +import harmony.mastermind.commons.events.ui.ExitAppRequestEvent; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.util.ConfigUtil; +import harmony.mastermind.commons.util.StringUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.logic.LogicManager; +import harmony.mastermind.model.*; +import harmony.mastermind.storage.Storage; +import harmony.mastermind.storage.StorageManager; +import harmony.mastermind.ui.Ui; +import harmony.mastermind.ui.UiManager; import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.*; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; import java.io.FileNotFoundException; import java.io.IOException; @@ -45,11 +46,11 @@ public MainApp() {} @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing Mastermind ]==========================="); super.init(); config = initConfig(getApplicationParameter("config")); - storage = new StorageManager(config.getAddressBookFilePath(), config.getUserPrefsFilePath()); + storage = new StorageManager(config.getTaskManagerFilePath(), config.getUserPrefsFilePath()); userPrefs = initPrefs(config); @@ -70,20 +71,22 @@ private String getApplicationParameter(String parameterName){ } private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional taskManagerOptional; + ReadOnlyTaskManager initialData; try { - addressBookOptional = storage.readAddressBook(); - if(!addressBookOptional.isPresent()){ - logger.info("Data file not found. Will be starting with an empty AddressBook"); + taskManagerOptional = storage.readTaskManager(); + if(!taskManagerOptional.isPresent()){ + logger.info("Data file not found. Will be starting with an empty TaskManager"); } - initialData = addressBookOptional.orElse(new AddressBook()); + + initialData = taskManagerOptional.orElse(new TaskManager()); + } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty TaskManager"); + initialData = new TaskManager(); } catch (FileNotFoundException e) { - logger.warning("Problem while reading from the file. . Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. . Will be starting with an empty TaskManager"); + initialData = new TaskManager(); } return new ModelManager(initialData, userPrefs); @@ -139,7 +142,7 @@ protected UserPrefs initPrefs(Config config) { "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. . Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. . Will be starting with an empty TaskManager"); initializedPrefs = new UserPrefs(); } @@ -159,13 +162,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting Mastermind " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Mastermind ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); @@ -181,7 +184,7 @@ public void handleExitAppRequestEvent(ExitAppRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); this.stop(); } - + public static void main(String[] args) { launch(args); } diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/harmony/mastermind/commons/core/ComponentManager.java similarity index 85% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/harmony/mastermind/commons/core/ComponentManager.java index 4bc8564e5824..c0122f4530ae 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/harmony/mastermind/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; -import seedu.address.commons.events.BaseEvent; +import harmony.mastermind.commons.events.BaseEvent; /** * Base class for *Manager classes diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/harmony/mastermind/commons/core/Config.java similarity index 60% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/harmony/mastermind/commons/core/Config.java index 6441c9ef20f4..abdb3c058ac1 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/harmony/mastermind/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; import java.util.Objects; import java.util.logging.Level; @@ -11,24 +11,27 @@ 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 = "Mastermind"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; + private String taskManagerFilePath = "data/mastermind.xml"; + private String taskManagerName = "MyTaskManager"; public Config() { } - + public String getAppTitle() { return appTitle; } + //@@author A0139194X public void setAppTitle(String appTitle) { + assert appTitle != null; this.appTitle = appTitle; } + //@@author public Level getLogLevel() { return logLevel; } @@ -41,27 +44,35 @@ public String getUserPrefsFilePath() { return userPrefsFilePath; } + //@@author A0139194X public void setUserPrefsFilePath(String userPrefsFilePath) { + assert userPrefsFilePath != null; this.userPrefsFilePath = userPrefsFilePath; } - public String getAddressBookFilePath() { - return addressBookFilePath; + //@@author + public String getTaskManagerFilePath() { + return taskManagerFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + //@@author A0139194X + public void setTaskManagerFilePath(String taskManagerFilePath) { + assert taskManagerFilePath != null; + this.taskManagerFilePath = taskManagerFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getTaskManagerName() { + return taskManagerName; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + //@@author A0139194X + public void setTaskManagerName(String taskManagerName) { + assert taskManagerName != null; + this.taskManagerName = taskManagerName; } + //@@author @Override public boolean equals(Object other) { if (other == this){ @@ -76,13 +87,13 @@ public boolean equals(Object other) { return Objects.equals(appTitle, o.appTitle) && Objects.equals(logLevel, o.logLevel) && Objects.equals(userPrefsFilePath, o.userPrefsFilePath) - && Objects.equals(addressBookFilePath, o.addressBookFilePath) - && Objects.equals(addressBookName, o.addressBookName); + && Objects.equals(taskManagerFilePath, o.taskManagerFilePath) + && Objects.equals(taskManagerName, o.taskManagerName); } @Override public int hashCode() { - return Objects.hash(appTitle, logLevel, userPrefsFilePath, addressBookFilePath, addressBookName); + return Objects.hash(appTitle, logLevel, userPrefsFilePath, taskManagerFilePath, taskManagerName); } @Override @@ -91,8 +102,8 @@ public String toString(){ sb.append("App title : " + appTitle); sb.append("\nCurrent log level : " + logLevel); sb.append("\nPreference file Location : " + userPrefsFilePath); - sb.append("\nLocal data file location : " + addressBookFilePath); - sb.append("\nAddressBook name : " + addressBookName); + sb.append("\nLocal data file location : " + taskManagerFilePath); + sb.append("\nTaskManager name : " + taskManagerName); return sb.toString(); } diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/harmony/mastermind/commons/core/EventsCenter.java similarity index 91% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/harmony/mastermind/commons/core/EventsCenter.java index 9652cd5c227b..f6b6da755edd 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/harmony/mastermind/commons/core/EventsCenter.java @@ -1,7 +1,8 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; + +import harmony.mastermind.commons.events.BaseEvent; import java.util.logging.Logger; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/harmony/mastermind/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/harmony/mastermind/commons/core/GuiSettings.java index e157ac8b8679..b381974e01d6 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/harmony/mastermind/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; import java.awt.*; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/harmony/mastermind/commons/core/LogsCenter.java similarity index 95% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/harmony/mastermind/commons/core/LogsCenter.java index 17939bab4975..ad2971349c74 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/harmony/mastermind/commons/core/LogsCenter.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; - -import seedu.address.commons.events.BaseEvent; +package harmony.mastermind.commons.core; import java.io.IOException; import java.util.logging.*; +import harmony.mastermind.commons.events.BaseEvent; + /** * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
@@ -15,7 +15,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "mastermind.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/harmony/mastermind/commons/core/Messages.java b/src/main/java/harmony/mastermind/commons/core/Messages.java new file mode 100644 index 000000000000..86a4ad36634b --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/core/Messages.java @@ -0,0 +1,17 @@ +package harmony.mastermind.commons.core; + +/** + * Container for user visible messages. + */ +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_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_TASK_NOT_IN_MASTERMIND = "Task could not be found in Mastermind"; + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + + public static final String MESSAGE_INVALID_DATE = "Start date cannot be after end date."; + + +} diff --git a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java b/src/main/java/harmony/mastermind/commons/core/UnmodifiableObservableList.java similarity index 99% rename from src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java rename to src/main/java/harmony/mastermind/commons/core/UnmodifiableObservableList.java index 5c25d8647a8d..3a4acc80dc33 100644 --- a/src/main/java/seedu/address/commons/core/UnmodifiableObservableList.java +++ b/src/main/java/harmony/mastermind/commons/core/UnmodifiableObservableList.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/harmony/mastermind/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/harmony/mastermind/commons/core/Version.java index 7ecb85b18f82..1c370c9d3015 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/harmony/mastermind/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package harmony.mastermind.commons.core; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/harmony/mastermind/commons/events/BaseEvent.java similarity index 89% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/harmony/mastermind/commons/events/BaseEvent.java index 723a9c69fbd5..98f241a559bc 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/harmony/mastermind/commons/events/BaseEvent.java @@ -1,4 +1,4 @@ -package seedu.address.commons.events; +package harmony.mastermind.commons.events; public abstract class BaseEvent { diff --git a/src/main/java/harmony/mastermind/commons/events/model/TaskManagerChangedEvent.java b/src/main/java/harmony/mastermind/commons/events/model/TaskManagerChangedEvent.java new file mode 100644 index 000000000000..52e801d32662 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/model/TaskManagerChangedEvent.java @@ -0,0 +1,19 @@ +package harmony.mastermind.commons.events.model; + +import harmony.mastermind.commons.events.BaseEvent; +import harmony.mastermind.model.ReadOnlyTaskManager; + +/** Indicates the TaskManager in the model has changed*/ +public class TaskManagerChangedEvent extends BaseEvent { + + public final ReadOnlyTaskManager data; + + public TaskManagerChangedEvent(ReadOnlyTaskManager data){ + this.data = data; + } + + @Override + public String toString() { + return "number of tasks " + data.getTaskList().size() + ", number of tags " + data.getTagList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/harmony/mastermind/commons/events/storage/DataSavingExceptionEvent.java similarity index 76% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/harmony/mastermind/commons/events/storage/DataSavingExceptionEvent.java index f0a0640ee523..f8dbadd9043b 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/harmony/mastermind/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package harmony.mastermind.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import harmony.mastermind.commons.events.BaseEvent; /** * Indicates an exception during a file saving diff --git a/src/main/java/harmony/mastermind/commons/events/storage/RelocateFilePathEvent.java b/src/main/java/harmony/mastermind/commons/events/storage/RelocateFilePathEvent.java new file mode 100644 index 000000000000..fa35c4837751 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/storage/RelocateFilePathEvent.java @@ -0,0 +1,25 @@ +package harmony.mastermind.commons.events.storage; + +import harmony.mastermind.commons.events.BaseEvent; + +//@@author A0139194X +/* + * Event that holds the new file path the user wants to relocate to + */ +public class RelocateFilePathEvent extends BaseEvent { + + private final String newFilePath; + + public RelocateFilePathEvent(String newFilePath) { + this.newFilePath = newFilePath; + } + + @Override + public String toString() { + return "Change save location file path to: " + newFilePath; + } + + public String getFilePath() { + return this.newFilePath; + } +} diff --git a/src/main/java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java new file mode 100644 index 000000000000..f2b2415715b8 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/ExecuteCommandEvent.java @@ -0,0 +1,31 @@ +package harmony.mastermind.commons.events.ui; + +import java.util.Date; + +import harmony.mastermind.commons.events.BaseEvent; + +//this class is to support event for displaying user action in action history ui +//@@author A0138862W +public class ExecuteCommandEvent extends BaseEvent{ + + public Date dateExecuted; + public String title; + public String description; + + //@@author A0138862W + public ExecuteCommandEvent(String title, String description){ + dateExecuted = new Date(); + + this.title = title; + this.description = description; + } + + //@@author A0138862W + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + + +} diff --git a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/ExitAppRequestEvent.java similarity index 67% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/harmony/mastermind/commons/events/ui/ExitAppRequestEvent.java index 9af6194543a3..b87d99648f15 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/harmony/mastermind/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package harmony.mastermind.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import harmony.mastermind.commons.events.BaseEvent; /** * Indicates a request for App termination diff --git a/src/main/java/harmony/mastermind/commons/events/ui/HighlightLastActionedRowRequestEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/HighlightLastActionedRowRequestEvent.java new file mode 100644 index 000000000000..73ef47a471ca --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/HighlightLastActionedRowRequestEvent.java @@ -0,0 +1,25 @@ +package harmony.mastermind.commons.events.ui; + +import harmony.mastermind.commons.events.BaseEvent; +import harmony.mastermind.model.task.Task; + + +//@@author A0138862W +/* + * This event is raise when a command request UI to highlight the action row in the tableview + * + */ +public class HighlightLastActionedRowRequestEvent extends BaseEvent { + + public Task task; + + public HighlightLastActionedRowRequestEvent(Task task){ + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/IncorrectCommandAttemptedEvent.java similarity index 66% rename from src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java rename to src/main/java/harmony/mastermind/commons/events/ui/IncorrectCommandAttemptedEvent.java index 991f7ae9fa25..d07aea1d6c00 100644 --- a/src/main/java/seedu/address/commons/events/ui/IncorrectCommandAttemptedEvent.java +++ b/src/main/java/harmony/mastermind/commons/events/ui/IncorrectCommandAttemptedEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package harmony.mastermind.commons.events.ui; -import seedu.address.commons.events.BaseEvent; -import seedu.address.logic.commands.Command; +import harmony.mastermind.commons.events.BaseEvent; +import harmony.mastermind.logic.commands.Command; /** * Indicates an attempt to execute an incorrect command diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/JumpToListRequestEvent.java similarity index 66% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/harmony/mastermind/commons/events/ui/JumpToListRequestEvent.java index 0580d27aecf5..a97ef9a3d685 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/harmony/mastermind/commons/events/ui/JumpToListRequestEvent.java @@ -1,9 +1,9 @@ -package seedu.address.commons.events.ui; +package harmony.mastermind.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import harmony.mastermind.commons.events.BaseEvent; /** - * Indicates a request to jump to the list of persons + * Indicates a request to jump to the list of tasks */ public class JumpToListRequestEvent extends BaseEvent { diff --git a/src/main/java/harmony/mastermind/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/NewResultAvailableEvent.java new file mode 100644 index 000000000000..9c7d2d97082b --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/NewResultAvailableEvent.java @@ -0,0 +1,24 @@ +package harmony.mastermind.commons.events.ui; + +import harmony.mastermind.commons.events.BaseEvent; + +/** + * Indicates that a new result is available. + */ +public class NewResultAvailableEvent extends BaseEvent { + + public final String title; + + public final String message; + + public NewResultAvailableEvent(String title, String message) { + this.title = title; + this.message = message; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/ShowHelpRequestEvent.java new file mode 100644 index 000000000000..f0db81d505c4 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/ShowHelpRequestEvent.java @@ -0,0 +1,28 @@ +package harmony.mastermind.commons.events.ui; + +import java.util.ArrayList; + +import harmony.mastermind.commons.events.BaseEvent; +import harmony.mastermind.logic.HelpPopupEntry; + +/**@@author A0139194X + * An event requesting to view the help page. + */ +public class ShowHelpRequestEvent extends BaseEvent { + + private final ArrayList helpEntries; + + public ShowHelpRequestEvent(ArrayList helpEntries) { + this.helpEntries = helpEntries; + } + + public ArrayList getHelpEntries() { + return this.helpEntries; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/harmony/mastermind/commons/events/ui/TabChangedEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/TabChangedEvent.java new file mode 100644 index 000000000000..c14572b9b4d2 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/TabChangedEvent.java @@ -0,0 +1,21 @@ +package harmony.mastermind.commons.events.ui; + +import harmony.mastermind.commons.events.BaseEvent; + +//@@author A0138862W +public class TabChangedEvent extends BaseEvent { + + public final String fromTabId; + public final String toTabId; + + public TabChangedEvent(String fromTabId, String toTabId){ + this.fromTabId = fromTabId; + this.toTabId = toTabId; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/harmony/mastermind/commons/events/ui/ToggleActionHistoryEvent.java b/src/main/java/harmony/mastermind/commons/events/ui/ToggleActionHistoryEvent.java new file mode 100644 index 000000000000..4c275d5c3581 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/events/ui/ToggleActionHistoryEvent.java @@ -0,0 +1,18 @@ +package harmony.mastermind.commons.events.ui; + +import harmony.mastermind.commons.events.BaseEvent; + +//@@author A0138862W +/** + * + * This event is raised when the ActionHistoryCommand request to toggle the UI. + * + */ +public class ToggleActionHistoryEvent extends BaseEvent{ + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/harmony/mastermind/commons/exceptions/DataConversionException.java similarity index 82% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/harmony/mastermind/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..41fd57882af9 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/harmony/mastermind/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package harmony.mastermind.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java b/src/main/java/harmony/mastermind/commons/exceptions/DuplicateDataException.java similarity index 83% rename from src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java rename to src/main/java/harmony/mastermind/commons/exceptions/DuplicateDataException.java index 17aa63d5020c..abe5290e8317 100644 --- a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java +++ b/src/main/java/harmony/mastermind/commons/exceptions/DuplicateDataException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package harmony.mastermind.commons.exceptions; /** * Signals an error caused by duplicate data where there should be none. diff --git a/src/main/java/harmony/mastermind/commons/exceptions/FolderDoesNotExistException.java b/src/main/java/harmony/mastermind/commons/exceptions/FolderDoesNotExistException.java new file mode 100644 index 000000000000..93d180a8a768 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/exceptions/FolderDoesNotExistException.java @@ -0,0 +1,12 @@ +package harmony.mastermind.commons.exceptions; + + +/** + * @author A0139194X + * Signals that the input file path does not exist + */ +public class FolderDoesNotExistException extends Exception { + public FolderDoesNotExistException(String message) { + super (message); + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/harmony/mastermind/commons/exceptions/IllegalValueException.java similarity index 87% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/harmony/mastermind/commons/exceptions/IllegalValueException.java index a473b43bd86f..e77a907a05db 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/harmony/mastermind/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package harmony.mastermind.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/harmony/mastermind/commons/exceptions/InvalidEventDateException.java b/src/main/java/harmony/mastermind/commons/exceptions/InvalidEventDateException.java new file mode 100644 index 000000000000..5566a927a1fc --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/exceptions/InvalidEventDateException.java @@ -0,0 +1,12 @@ +package harmony.mastermind.commons.exceptions; + + +/** + * Signals that the start date is after end date for the Task of event type + */ +public class InvalidEventDateException extends Exception { + + public InvalidEventDateException() { + super("Cannot create an event with start date that is after end date"); + } +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/commons/exceptions/NotRecurringTaskException.java b/src/main/java/harmony/mastermind/commons/exceptions/NotRecurringTaskException.java new file mode 100644 index 000000000000..78665d6e91f2 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/exceptions/NotRecurringTaskException.java @@ -0,0 +1,14 @@ +package harmony.mastermind.commons.exceptions; + +//@@author A0124797R +/** + * Signals that current Task is not recurring Task and not able to perform methods that are + * for recurring tasks + */ +public class NotRecurringTaskException extends Exception{ + + public NotRecurringTaskException() { + super("Task is not a recurring Task"); + } + +} diff --git a/src/main/java/harmony/mastermind/commons/exceptions/TaskAlreadyMarkedException.java b/src/main/java/harmony/mastermind/commons/exceptions/TaskAlreadyMarkedException.java new file mode 100644 index 000000000000..a13106a1598f --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/exceptions/TaskAlreadyMarkedException.java @@ -0,0 +1,13 @@ +package harmony.mastermind.commons.exceptions; + +//@@author A0138862W +/* + * This exception is thrown when attempt to execute MarkCOmmand on a task that's already marked + */ +public class TaskAlreadyMarkedException extends Exception { + + + public TaskAlreadyMarkedException(){ + super(); + } +} diff --git a/src/main/java/harmony/mastermind/commons/exceptions/UnwrittableFolderException.java b/src/main/java/harmony/mastermind/commons/exceptions/UnwrittableFolderException.java new file mode 100644 index 000000000000..f841bc7028c0 --- /dev/null +++ b/src/main/java/harmony/mastermind/commons/exceptions/UnwrittableFolderException.java @@ -0,0 +1,13 @@ +package harmony.mastermind.commons.exceptions; + +//@@author A0139194X +/* + * An exception say that a filepath has no write permission + */ +public class UnwrittableFolderException extends Exception { + + public UnwrittableFolderException(String message) { + super(message); + } + +} diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/harmony/mastermind/commons/util/AppUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/harmony/mastermind/commons/util/AppUtil.java index 09a528f1188e..1eb7ae483b40 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/AppUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; +import harmony.mastermind.MainApp; import javafx.scene.image.Image; -import seedu.address.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/harmony/mastermind/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/harmony/mastermind/commons/util/CollectionUtil.java index fde8394f31e5..ed622d72d80a 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import java.util.Collection; import java.util.HashSet; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/harmony/mastermind/commons/util/ConfigUtil.java similarity index 89% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/harmony/mastermind/commons/util/ConfigUtil.java index af42e03df06c..d54c57008af6 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/ConfigUtil.java @@ -1,14 +1,14 @@ -package seedu.address.commons.util; - -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +package harmony.mastermind.commons.util; import java.io.File; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; +import harmony.mastermind.commons.core.Config; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.exceptions.DataConversionException; + /** * A class for accessing the Config File. */ @@ -58,5 +58,5 @@ public static void saveConfig(Config config, String configFilePath) throws IOExc FileUtil.serializeObjectToJsonFile(new File(configFilePath), config); } - + } diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/harmony/mastermind/commons/util/FileUtil.java similarity index 70% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/harmony/mastermind/commons/util/FileUtil.java index ca8221250de4..21c102322d0d 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/FileUtil.java @@ -1,8 +1,13 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.exceptions.UnwrittableFolderException; /** * Writes and reads file @@ -92,4 +97,33 @@ public static T deserializeObjectFromJsonFile(File jsonFile, Class classO throws IOException { return JsonUtil.fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize); } + + //@@author A0139194X + /** + * Checks if directory is writable + * @param newFilePath + * @throws UnwrittableFolderException + */ + + public static void checkWrittableDirectory(String newFilePath) throws UnwrittableFolderException { + assert newFilePath != null; + File newFile = new File(newFilePath); + if (!(newFile.isDirectory() && newFile.canWrite())) { + throw new UnwrittableFolderException(newFilePath + " is not writtable."); + } + } + + //@@author A0139194X + /** + * Checks if file path string exists + * @param newFilePath + * @throws FolderDoesNotExistException + */ + public static void checkSaveLocation(String newFilePath) throws FolderDoesNotExistException { + assert newFilePath != null; + Path filePath = Paths.get(newFilePath); + if (!Files.exists(filePath)) { + throw new FolderDoesNotExistException(newFilePath + " does not exist"); + } + } } diff --git a/src/main/java/seedu/address/commons/util/FxViewUtil.java b/src/main/java/harmony/mastermind/commons/util/FxViewUtil.java similarity index 91% rename from src/main/java/seedu/address/commons/util/FxViewUtil.java rename to src/main/java/harmony/mastermind/commons/util/FxViewUtil.java index 900efa6bf5c3..f3b26c609f56 100644 --- a/src/main/java/seedu/address/commons/util/FxViewUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/FxViewUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/harmony/mastermind/commons/util/JsonUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/harmony/mastermind/commons/util/JsonUtil.java index 80b67de5b7e8..26235d894439 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/harmony/mastermind/commons/util/StringUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/harmony/mastermind/commons/util/StringUtil.java index 0e7f12c80042..2bd3c8f039af 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/StringUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/address/commons/util/UrlUtil.java b/src/main/java/harmony/mastermind/commons/util/UrlUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/UrlUtil.java rename to src/main/java/harmony/mastermind/commons/util/UrlUtil.java index c701fea753d5..d8a648445785 100644 --- a/src/main/java/seedu/address/commons/util/UrlUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/UrlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import java.net.URL; diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/harmony/mastermind/commons/util/XmlUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/XmlUtil.java rename to src/main/java/harmony/mastermind/commons/util/XmlUtil.java index 2087e7628a1d..23f882095757 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/harmony/mastermind/commons/util/XmlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package harmony.mastermind.commons.util; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; diff --git a/src/main/java/harmony/mastermind/logic/HelpPopupEntry.java b/src/main/java/harmony/mastermind/logic/HelpPopupEntry.java new file mode 100644 index 000000000000..6dbad2a404f9 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/HelpPopupEntry.java @@ -0,0 +1,29 @@ +package harmony.mastermind.logic; + +/** + * @@author A0139194X + * Class to store entries into the Help Popup table. + */ +public class HelpPopupEntry { + private String commandWord; + private String format; + private String description; + + public HelpPopupEntry(String commandWord, String format, String description) { + this.commandWord = commandWord; + this.format = format; + this.description = description; + } + + public String getFormat() { + return format; + } + + public String getCommandWord() { + return commandWord; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/Logic.java b/src/main/java/harmony/mastermind/logic/Logic.java new file mode 100644 index 000000000000..339140d436b5 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/Logic.java @@ -0,0 +1,35 @@ +package harmony.mastermind.logic; + +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.collections.ObservableList; + +//@@author A0124797R +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @return the result of the command execution. + */ + CommandResult execute(String commandText); + + /** Returns the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns the filtered list of floating tasks*/ + ObservableList getFilteredFloatingTaskList(); + + /** Returns the filtered list of events*/ + ObservableList getFilteredEventList(); + + /** Returns the filtered list of deadlines*/ + ObservableList getFilteredDeadlineList(); + + /** Returns the filtered list of archived tasks*/ + ObservableList getFilteredArchiveList(); + + +} diff --git a/src/main/java/harmony/mastermind/logic/LogicManager.java b/src/main/java/harmony/mastermind/logic/LogicManager.java new file mode 100644 index 000000000000..0d072062d7d1 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/LogicManager.java @@ -0,0 +1,128 @@ +package harmony.mastermind.logic; + +import java.util.logging.Logger; + +import harmony.mastermind.commons.core.ComponentManager; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.logic.commands.Command; +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.logic.parser.Parser; +import harmony.mastermind.model.Model; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.storage.Storage; +import javafx.collections.ObservableList; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final Storage storage; + private final Parser parser; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + this.parser = new Parser(); + } + + @Override + //@@author A0124797R + public CommandResult execute(String commandText) { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + Command command = parser.parseCommand(commandText); + command.setData(model, storage); + CommandResult result = command.execute(); + return result; + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + + @Override + public ObservableList getFilteredFloatingTaskList() { + return model.getFilteredFloatingTaskList(); + } + + @Override + public ObservableList getFilteredEventList() { + return model.getFilteredEventList(); + } + + @Override + public ObservableList getFilteredDeadlineList() { + return model.getFilteredDeadlineList(); + } + + @Override + public ObservableList getFilteredArchiveList() { + return model.getFilteredArchiveList(); + } + + //@@author A0124797R-unused + // disable the use of importing txt file + /** + * parse the result of commands and handle ImportCommand separately + */ + /* + private CommandResult parseResult(Command cmd) { + CommandResult result = cmd.execute(); + if (result.feedbackToUser.equals(ImportCommand.MESSAGE_READ_SUCCESS)) { + result = handleImport((ImportCommand) cmd); + } + + return result; + } + + /** + * handle the inputs from the reading of file from ImportCommand + */ + /* + public CommandResult handleImport(ImportCommand command) { + ArrayList lstOfCmd = command.getTaskToAdd(); + String errLines = ""; + int errCount = 0; + int lineCounter = 0; + + for (String cmd: lstOfCmd) { + lineCounter += 1; + Command cmdResult = parser.parseCommand(cmd); + cmdResult.setData(model, storage); + CommandResult addResult = cmdResult.execute(); + boolean isFailure = isAddFailure(addResult); + + if (isFailure) { + errCount += 1; + errLines += Integer.toString(lineCounter) + ","; + } + } + + int addCount = lineCounter - errCount; + if (errLines.isEmpty()) { + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(ImportCommand.MESSAGE_IMPORT_TXT_SUCCESS, addCount)); + } else { + errLines = errLines.substring(0,errLines.length()-1); + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(ImportCommand.MESSAGE_IMPORT_TXT_FAILURE, addCount, errLines)); + } + + } + */ + + + /** + * Checks if adding of tasks from ImportCommand is valid + */ + /* + private boolean isAddFailure(CommandResult cmdResult) { + if (cmdResult.toString().substring(MESSAGE_START_INDEX, MESSAGE_END_INDEX).equals(AddCommand.MESSAGE_SUCCESS.substring(MESSAGE_START_INDEX, MESSAGE_END_INDEX))) { + return false; + } + + return true; + } + */ +} diff --git a/src/main/java/harmony/mastermind/logic/commands/AddCommand.java b/src/main/java/harmony/mastermind/logic/commands/AddCommand.java new file mode 100644 index 000000000000..abc97cf6ca1a --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/AddCommand.java @@ -0,0 +1,273 @@ +package harmony.mastermind.logic.commands; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.logic.parser.Parser; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.TaskBuilder; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; +import harmony.mastermind.ui.UiManager; + +/** + * Adds a task to the task manager. + * + */ +// @@author A0138862W +public class AddCommand extends Command implements Undoable, Redoable { + private static final Logger logger = LogsCenter.getLogger(AddCommand.class); + + public static final String COMMAND_KEYWORD_ADD = "add"; + public static final String COMMAND_KEYWORD_DO = "do"; + + // Better regex, support better NLP: + // general form: add some task name from tomorrow 8pm to next friday 8pm daily #recurring,awesome + // https://regex101.com/r/M2A3tB/8 + public static final String COMMAND_ARGUMENTS_REGEX = "(?='(?.+)'|(?(?:(?:.(?!by|from|#)))+))" + + "(?:(?=.*(?:by|from)\\s(?(?:.(?!#|.*'))+)?))?" + + "(?:(?=.*(?daily|weekly|monthly|yearly)))?" + + "(?:(?=.*#(?.+)))?" + + ".*"; + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_DESCRIPTION = "Adding a task"; + + public static final String COMMAND_FORMAT = "Floating Task: (add|do) #[]\n" + + "Deadline: (add|do) by [daily|weekly|monthly|yearly] #[comma_separated_tags]\n" + + "Event: (add|do) from to [daily|weekly|monthly|yearly] #[comma_separated_tags]"; + + public static final String MESSAGE_EXAMPLE_EVENT = "add attend workshop from today 7pm to next monday 1pm #programming,java"; + public static final String MESSAGE_EXAMPLE_DEADLINE = "add submit homework by next sunday 11pm #math,physics"; + public static final String MESSAGE_EXAMPLE_FLOATING = "do chores #cleaning"; + public static final String MESSAGE_EXAMPLE_RECUR_DEADLINE = "add submit homework by next sunday 11pm weekly #math,physics"; + public static final String MESSAGE_EXAMPLE_RECUR_EVENT = "add attend workshop from today 7pm to next monday 1pm monthly #programming,java"; + + public static final String MESSAGE_EXAMPLES = new StringBuilder() + .append("[Format]\n") + .append(COMMAND_FORMAT+ "\n\n") + .append("[Examples]:\n") + .append("Event: "+ MESSAGE_EXAMPLE_EVENT+"\n") + .append("Deadline: "+MESSAGE_EXAMPLE_DEADLINE+"\n") + .append("Floating: "+MESSAGE_EXAMPLE_FLOATING+"\n") + .append("Recurring Deadline: "+MESSAGE_EXAMPLE_RECUR_DEADLINE+"\n") + .append("Recurring Event: "+MESSAGE_EXAMPLE_RECUR_EVENT+"\n") + .toString(); + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Add Command] Task deleted: %1$s"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Add Command] Task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in Mastermind"; + + private final Task toAdd; + + + private static final String TASK = "Task"; + private static final String DEADLINE = "Deadline"; + private static final String EVENT = "Event"; + + static GenericMemory task; + static GenericMemory deadline; + static GenericMemory event; + + // @@author A0124797R-used + /** adds an event */ + /** + * @deprecated + */ + /* + public AddCommand(String name, String startDate, String endDate, Set tags, String recurVal, Memory mem) throws IllegalValueException, InvalidEventDateException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + Date createdDate = new Date(); + Date startTime = prettyTimeParser.parse(startDate).get(0); + Date endTime = prettyTimeParser.parse(endDate).get(0); + + if (startTime.after(endTime)) { + throw new InvalidEventDateException(); + } + + this.toAdd = new Task(name, startTime, endTime, new UniqueTagList(tagSet), recurVal, createdDate); + + //Converting Date start to Calendar start + Calendar start = dateToCalendar(startTime); + + //Converting Date end to Calendar end + Calendar end = dateToCalendar(endTime); + + event = new GenericMemory(tags.toString(), name, "", start, end, 0); + mem.add(event); + } + */ + + // deadline + // @@author A0138862W-unused + /** + * + * The builder constructor has taken care of all the construction of event, floating and deadline + * @see AddCommand(AddCommandBuilder) + * @deprecated + * + */ + /* + public AddCommand(String name, String endDateStr, Set tags, String recur, Memory mem) throws IllegalValueException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + Date createdDate = new Date(); + Date endDate = prettyTimeParser.parse(endDateStr).get(0); + + + this.toAdd = new Task(name, endDate, new UniqueTagList(tagSet), recur, createdDate); + + //Converting Date end to Calendar end + Calendar end = dateToCalendar(endDate); + + deadline = new GenericMemory(tags.toString(), name, "", end); + mem.add(deadline); + + } + */ + + // floating + // @@author A0138862W-unused + /** + * The builder constructor has taken care of all the construction of event, floating and deadline + * @see AddCommand(AddCommandBuilder) + * @deprecated + * + */ + /* + public AddCommand(String name, Set tags, Memory mem) throws IllegalValueException { + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + + Date createdDate = new Date(); + + this.toAdd = new Task(name, new UniqueTagList(tagSet), createdDate); + + task = new GenericMemory(tags.toString(), name, ""); + mem.add(task); + } + */ + + // @@author A0138862W + /** + * Build the AddCommand using addCommandBuilder. + * Depending on the builder attributes, the taskBuilder will return the appropriate event/floating/deadline task. + * + * @param addCommandBuilder to build the command safely + * + */ + protected AddCommand(AddCommandBuilder addCommandBuilder) throws IllegalValueException, InvalidEventDateException{ + TaskBuilder taskBuilder = new TaskBuilder(addCommandBuilder.getName()); + taskBuilder.withTags(addCommandBuilder.getTags()); + + if(addCommandBuilder.isDeadline()){ + taskBuilder.asDeadline(addCommandBuilder.getEndDate()); + }else if(addCommandBuilder.isEvent()){ + taskBuilder.asEvent(addCommandBuilder.getStartDate(), addCommandBuilder.getEndDate()); + } + + if(addCommandBuilder.isRecurring()){ + taskBuilder.asRecurring(addCommandBuilder.getRecur()); + } + + toAdd = taskBuilder.build(); + + Parser.mem.add(new GenericMemory(toAdd.getTags().toString(), toAdd.getName(), "")); + } + + @Override + //@@author A0138862W + public CommandResult execute() { + assert model != null; + try { + executeAdd(); + + model.pushToUndoHistory(this); + + // this is a new command entered by user (not undo/redo) + // need to clear the redoHistory Stack + model.clearRedoHistory(); + + requestHighlightLastActionedRow(toAdd); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_SUCCESS, toAdd)); + } catch (UniqueTaskList.DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_ADD,MESSAGE_DUPLICATE_TASK); + } + + } + + // @@author A0138862W + /** action to perform when ModelManager requested to undo this command **/ + @Override + public CommandResult undo() { + try { + model.deleteTask(toAdd); + + model.pushToRedoHistory(this); + + logger.info("Task undo" + toAdd.getName()); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_UNDO_SUCCESS, toAdd)); + } catch (UniqueTaskList.TaskNotFoundException pne) { + return new CommandResult(COMMAND_KEYWORD_ADD,Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } + } + + // @@author A0138862W + /** action to perform when ModelManager requested to redo this command**/ + @Override + public CommandResult redo() { + assert model != null; + try { + executeAdd(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(toAdd); + + logger.info("Task redo" + toAdd.getName()); + + return new CommandResult(COMMAND_KEYWORD_ADD,String.format(MESSAGE_REDO_SUCCESS, toAdd)); + } catch (UniqueTaskList.DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_ADD,MESSAGE_DUPLICATE_TASK); + } + } + + + // @@author A0138862W + /** extract method since it's reusable for execute() and redo()**/ + private void executeAdd() throws DuplicateTaskException { + model.addTask(toAdd); + } + + //@@author A0143378Y + private Calendar dateToCalendar(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal; + } + + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/AddCommandBuilder.java b/src/main/java/harmony/mastermind/logic/commands/AddCommandBuilder.java new file mode 100644 index 000000000000..2cc5fffc948c --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/AddCommandBuilder.java @@ -0,0 +1,95 @@ +package harmony.mastermind.logic.commands; + +import java.util.Date; +import java.util.Set; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; + + +//@@author A0138862W +/* + * AddCommandBuilder provides a safe way to create an AddCommand + * + */ +public class AddCommandBuilder { + public String name; + public Date startDate; + public Date endDate; + public Set tags; + public String recur; + + public AddCommandBuilder(String name) { + this.name = name; + } + + public AddCommandBuilder asEvent(Date startDate, Date endDate) throws InvalidEventDateException{ + if (startDate.after(endDate)) { + throw new InvalidEventDateException(); + } + + this.startDate = startDate; + this.endDate = endDate; + return this; + } + + public AddCommandBuilder asDeadline(Date endDate) { + this.startDate = null; + this.endDate = endDate; + return this; + } + + public AddCommandBuilder withTags(Set tags) throws IllegalValueException { + this.tags = tags; + return this; + } + + public AddCommandBuilder asRecurring(String recur) { + this.recur = recur; + return this; + } + + public AddCommand build() throws IllegalValueException, InvalidEventDateException { + return new AddCommand(this); + } + + public boolean isFloating() { + return startDate == null + && endDate == null; + } + + public boolean isDeadline() { + return startDate == null + && endDate != null; + } + + public boolean isEvent() { + return startDate != null + && endDate != null; + } + + public boolean isRecurring() { + return this.recur != null; + } + + public String getName() { + return name; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Set getTags() { + return tags; + } + + public String getRecur() { + return recur; + } + +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/commands/ClearCommand.java b/src/main/java/harmony/mastermind/logic/commands/ClearCommand.java new file mode 100644 index 000000000000..cc92f7b6b980 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/ClearCommand.java @@ -0,0 +1,24 @@ +package harmony.mastermind.logic.commands; + +import harmony.mastermind.model.TaskManager; + +//@@author A0139194X +/** + * Clears the task manager by reseting the data and clearing both undo and redo history + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Mastermind has been cleared!"; + public static final String COMMAND_DESCRIPTION = "Clearing all of Mastermind's data"; + + public ClearCommand() {} + + @Override + public CommandResult execute() { + assert model != null; + model.resetData(TaskManager.getEmptyTaskManager()); + + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/harmony/mastermind/logic/commands/Command.java b/src/main/java/harmony/mastermind/logic/commands/Command.java new file mode 100644 index 000000000000..43efc2144fa7 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/Command.java @@ -0,0 +1,72 @@ +package harmony.mastermind.logic.commands; + +import java.text.SimpleDateFormat; + +import org.ocpsoft.prettytime.nlp.PrettyTimeParser; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.events.ui.ExecuteCommandEvent; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.events.ui.IncorrectCommandAttemptedEvent; +import harmony.mastermind.model.Model; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.storage.Storage; +import harmony.mastermind.storage.StorageManager; + +/** + * Represents a command with hidden internal logic and the ability to be executed. + */ +public abstract class Command { + + // Social date parser + protected static final PrettyTimeParser prettyTimeParser = new PrettyTimeParser(); + + protected Model model; + + /** + * Constructs a feedback message to summarise an operation that displayed a listing of tasks. + * + * @param displaySize used to generate summary + * @return summary message for tasks displayed + */ + public static String getMessageForTaskListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, displaySize); + } + + /** + * Executes the command and returns the result message. + * + * @return feedback message of the operation result for display + */ + public abstract CommandResult execute(); + + /** + * Provides any needed dependencies to the command. + * Commands making use of any of these should override this method to gain + * access to the dependencies. + */ + public void setData(Model model, Storage storage) { + this.model = model; + } + + /** + * Raises an event to indicate an attempt to execute an incorrect command + */ + protected void indicateAttemptToExecuteIncorrectCommand() { + EventsCenter.getInstance().post(new IncorrectCommandAttemptedEvent(this)); + } + + // @@author A0138862W + /** + * Raises an event to highlight the last action row in table + * This event should be subscribed by UiManager to update the table + * + * @param the row that contain the task that needs to be highlighted + * + */ + protected void requestHighlightLastActionedRow(Task task){ + EventsCenter.getInstance().post(new HighlightLastActionedRowRequestEvent(task)); + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/CommandResult.java b/src/main/java/harmony/mastermind/logic/commands/CommandResult.java new file mode 100644 index 000000000000..11c14af31aeb --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/CommandResult.java @@ -0,0 +1,24 @@ +package harmony.mastermind.logic.commands; + +/** + * Represents the result of a command execution. + */ +public class CommandResult { + + public final String feedbackToUser; + + public final String title; + + + public CommandResult(String title, String feedbackToUser) { + assert feedbackToUser != null; + this.title = title; + this.feedbackToUser = feedbackToUser; + } + + @Override + public String toString() { + return this.feedbackToUser; + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/DeleteCommand.java b/src/main/java/harmony/mastermind/logic/commands/DeleteCommand.java new file mode 100644 index 000000000000..f1a643a78011 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/DeleteCommand.java @@ -0,0 +1,147 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; +import java.util.Stack; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; + +/** + * Deletes a task identified using it's last displayed index from the task + * manager. + */ +public class DeleteCommand extends Command implements Undoable, Redoable { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task identified by the index number used in the last task listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + + COMMAND_WORD + + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + public static final String COMMAND_DESCRIPTION = "Deletes the task identified by the index number"; + + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Delete Command] Task added: %1$s"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Delete Command] Deleted Task: %1$s"; + + public final int targetIndex; + + private ReadOnlyTask toDelete; + + public static GenericMemory detailedView = null; + public static ArrayList listView = null; + public static String listName = null; + Memory memory; + + public DeleteCommand(int targetIndex, Memory mem) { + this.targetIndex = targetIndex; + this.memory = mem; + } + + @Override + public CommandResult execute() { + try { + executeDelete(); + + model.pushToUndoHistory(this); + + model.clearRedoHistory(); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_DELETE_TASK_SUCCESS, toDelete)); + + } catch (TaskNotFoundException | IndexOutOfBoundsException | ArchiveTaskList.TaskNotFoundException tnfe) { + + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + + } + } + + @Override + + // @@author A0138862W + /** action to perform when ModelManager requested to undo this delete command **/ + public CommandResult undo() { + try { + model.addTask((Task) toDelete); + + model.pushToRedoHistory(this); + + requestHighlightLastActionedRow((Task) toDelete); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, toDelete)); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_WORD, AddCommand.MESSAGE_DUPLICATE_TASK); + } + } + + @Override + // @@author A0138862W + /** action to perform when ModelManager requested to redo this delete command **/ + public CommandResult redo() { + try { + executeDelete(); + + model.pushToUndoHistory(this); + + } catch (TaskNotFoundException | IndexOutOfBoundsException | ArchiveTaskList.TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_REDO_SUCCESS, toDelete)); + } + + private void executeDelete() throws TaskNotFoundException, IndexOutOfBoundsException, ArchiveTaskList.TaskNotFoundException { + UnmodifiableObservableList lastShownList = model.getCurrentList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new IndexOutOfBoundsException(); + } + + if (toDelete == null) { + toDelete = lastShownList.get(targetIndex - 1); + deleteDirectly(toDelete.toString(), memory); + } + + if (toDelete.isMarked()) { + model.deleteArchive(toDelete); + }else { + model.deleteTask(toDelete); + } + } + + //@@author A0143378Y + /* + * Perform a delete by name operation + */ + public static void deleteDirectly(String name, Memory memory) { + ArrayList searchResult = FindCommand.searchExact(name, memory); + if (searchResult.size() == 1){ + deleteItem(searchResult.get(0), memory); + History.advance(memory); + } + } + + //@@author A0143378Y + /* + * Delete given GenericMemory item from memory + */ + private static void deleteItem(GenericMemory item, Memory memory) { + assert item != null; + memory.remove(item); + } +} diff --git a/src/main/java/harmony/mastermind/logic/commands/EditCommand.java b/src/main/java/harmony/mastermind/logic/commands/EditCommand.java new file mode 100644 index 000000000000..78838c5db2a2 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/EditCommand.java @@ -0,0 +1,251 @@ +package harmony.mastermind.logic.commands; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.TaskBuilder; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; + +/** + * Edits a task in task manager + * + */ +public class EditCommand extends Command implements Undoable, Redoable { + + public static final String COMMAND_KEYWORD_EDIT = "edit"; + public static final String COMMAND_KEYWORD_UPDATE = "update"; + public static final String COMMAND_KEYWORD_CHANGE = "change"; + + //@@author A0138862W + public static final String COMMAND_ARGUMENTS_REGEX = "(?=(?\\d+))" + + "(?:(?=.*name to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*start date to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*end date to (?:(?.+?)(?:,|$|\\R))?))?" + + "(?:(?=.*tags to #(?:(?.+?)(?:\\s|,\\s|$|,$|\\R))?))?" + + "(?:(?=.*recur (?daily|weekly|monthly|yearly)(?:,|$|\\R)))?" + + ".+"; + + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_FORMAT = "(edit|update|change) [name to ,] [start date to ,] [end date to ,] [recur (daily|weekly|monthly|yearly),] [tags to #,]"; + + public static final String MESSAGE_USAGE = COMMAND_FORMAT + + "\n" + + "Edits the task identified by the index number used in the last task listing.\n" + + "Example: \n" + + "edit 2 name to parents with dinner, end date to tomorrow 7pm, recur daily, tags to #meal,family"; + + public static final String MESSAGE_EDIT_TASK_PROMPT = "Edit the following task: %1$s"; + + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Edit Command] Task reverted: %1$s"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Edit Command] Edit the following task: %1$s"; + + public static final String COMMAND_DESCRIPTION = "Editing a task"; + + // private MainWindow window; + private ReadOnlyTask originalTask; + private Task editedTask; + + private final int targetIndex; + private Optional name; + private Optional startDate; + private Optional endDate; + private Optional recur; + private Optional> tags; + //@@author + + public EditCommand(int targetIndex, Optional name, Optional startDate, Optional endDate, Optional> tags, Optional recur) throws IllegalValueException, ParseException { + this.targetIndex = targetIndex; + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + this.recur = recur; + this.tags = tags; + } + + @Override + public CommandResult execute() { + + try { + executeEdit(); + + model.pushToUndoHistory(this); + + // this is a new command entered by user (not undo/redo) + // need to clear the redoHistory Stack + model.clearRedoHistory(); + + requestHighlightLastActionedRow(editedTask); + + return new CommandResult(COMMAND_KEYWORD_EDIT, String.format(MESSAGE_EDIT_TASK_PROMPT, originalTask)); + + } catch (TaskNotFoundException | IndexOutOfBoundsException ie) { + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } catch (IllegalValueException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_COMMAND_FORMAT); + } catch (InvalidEventDateException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_DATE); + } + + } + + @Override + // @@author A0138862W + /* + * Strategy implementation to undo the edit command + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + + try { + model.deleteTask(editedTask); + + // add back the original task + model.addTask((Task) originalTask); + + model.pushToRedoHistory(this); + + requestHighlightLastActionedRow((Task)originalTask); + + return new CommandResult(COMMAND_KEYWORD_EDIT, String.format(MESSAGE_UNDO_SUCCESS, originalTask)); + } catch (UniqueTaskList.TaskNotFoundException pne) { + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_KEYWORD_EDIT, AddCommand.MESSAGE_DUPLICATE_TASK); + } + } + + @Override + // @@author A0138862W + /* + * Strategy implementation to redo the edit command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + + try { + executeEdit(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(editedTask); + + return new CommandResult(COMMAND_KEYWORD_EDIT, String.format(MESSAGE_REDO_SUCCESS, originalTask)); + } catch (TaskNotFoundException | IndexOutOfBoundsException ie) { + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } catch (IllegalValueException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_COMMAND_FORMAT); + } catch (InvalidEventDateException e){ + return new CommandResult(COMMAND_KEYWORD_EDIT, Messages.MESSAGE_INVALID_DATE); + } + } + + // @@author A0138862W + private void executeEdit() throws TaskNotFoundException, IndexOutOfBoundsException, InvalidEventDateException, IllegalValueException { + UnmodifiableObservableList lastShownList = model.getFilteredTaskList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new IndexOutOfBoundsException(); + } + + if (originalTask == null) { + originalTask = lastShownList.get(targetIndex - 1); + } + + // parsing inputs + // if user provides explicit field and value, we change them + // otherwise, all omitted field are taken from the original + String toEditName = name.map(val -> val).orElse(originalTask.getName()); + Date toEditStartDate = startDate.map(val -> prettyTimeParser.parse(val).get(0)).orElse(originalTask.getStartDate()); + Date toEditEndDate = endDate.map(val -> prettyTimeParser.parse(val).get(0)).orElse(originalTask.getEndDate()); + String toEditRecur = recur.map(val -> val).orElse(originalTask.getRecur()); + UniqueTagList toEditTags = new UniqueTagList(tags.map(val -> { + final Set tagSet = new HashSet<>(); + for (String tagName : val) { + try { + tagSet.add(new Tag(tagName)); + } catch (IllegalValueException e) { + e.printStackTrace(); + } + } + return tagSet; + }).orElse(originalTask.getTags().toSet())); + Date toEditCreatedDate = originalTask.getCreatedDate(); + + + // initialize the new task with edited values + editedTask = buildEditedTask(toEditName, toEditStartDate, toEditEndDate, toEditRecur, toEditTags, toEditCreatedDate); + + model.deleteTask(originalTask); + model.addTask(editedTask); + } + + //@@author A0138862W + /** + * Attempt to build a task based on edited value. + * If this command has build it before, it'll simply return the previous instance. + * + * @param toEditName The edited name + * @param toEditStartDate the edited start date + * @param toEditEndDate edited end date + * @param toEditRecur recurring keyword + * @param toEditTags tags + * @param toEditCreatedDate custom creation date + * @return + * @throws IllegalValueException if tags are not alphanumeric + * @throws InvalidEventDateException if start date is after end date + */ + private Task buildEditedTask(String toEditName, Date toEditStartDate, Date toEditEndDate, String toEditRecur, UniqueTagList toEditTags, Date toEditCreatedDate) throws IllegalValueException, InvalidEventDateException { + if (editedTask == null) { + TaskBuilder taskBuilder = new TaskBuilder(toEditName); + taskBuilder.withCreationDate(toEditCreatedDate); + taskBuilder.withTags(toEditTags); + taskBuilder.asRecurring(toEditRecur); + + if (isEvent(toEditStartDate, toEditEndDate)){ + taskBuilder.asEvent(toEditStartDate,toEditEndDate); + } else if (isDeadline(toEditStartDate, toEditEndDate)){ + taskBuilder.asDeadline(toEditEndDate); + } else if (isFloating(toEditStartDate, toEditEndDate)){ + taskBuilder.asFloating(); + } + editedTask = taskBuilder.build(); + } + + return editedTask; + } + //@@author + + private boolean isFloating(Date toEditStartDate, Date toEditEndDate) { + return toEditStartDate == null && toEditEndDate == null; + } + + private boolean isDeadline(Date toEditStartDate, Date toEditEndDate) { + return toEditStartDate == null && toEditEndDate != null; + } + + private boolean isEvent(Date toEditStartDate, Date toEditEndDate) { + return toEditStartDate != null && toEditEndDate != null; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/harmony/mastermind/logic/commands/ExitCommand.java similarity index 50% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/harmony/mastermind/logic/commands/ExitCommand.java index d98233ce2a0b..d42d30f9f747 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/harmony/mastermind/logic/commands/ExitCommand.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package harmony.mastermind.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.events.ui.ExitAppRequestEvent; /** * Terminates the program. @@ -10,14 +10,16 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Mastermind as requested ..."; + + public static final String COMMAND_DESCRIPTION = "Exit Mastermind"; public ExitCommand() {} @Override public CommandResult execute() { EventsCenter.getInstance().post(new ExitAppRequestEvent()); - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); + return new CommandResult(COMMAND_WORD, MESSAGE_EXIT_ACKNOWLEDGEMENT); } } diff --git a/src/main/java/harmony/mastermind/logic/commands/ExportCommand.java b/src/main/java/harmony/mastermind/logic/commands/ExportCommand.java new file mode 100644 index 000000000000..bc48678fce60 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/ExportCommand.java @@ -0,0 +1,177 @@ +package harmony.mastermind.logic.commands; + +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.model.task.ReadOnlyTask; + +//@@author A0138862W +public class ExportCommand extends Command { + + public static final String COMMAND_KEYWORD_EXPORT = "export"; + + public static final String COMMAND_ARGUMENTS_REGEX = "(?:(?=.*(?tasks)))?" + + "(?:(?=.*(?deadlines)))?" + + "(?:(?=.*(?events)))?" + + "(?:(?=.*(?archives)))?" + + ".*to " + + "(?.+)"; + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String COMMAND_FORMAT = "export [tasks] [deadlines] [events] [archives] to "; + + public static final String COMMAND_DESCRIPTION = "Export to desired format"; + + public static final String MESSAGE_EXAMPLE = "export tasks deadlines to C:\\Desktop\\mastermind.csv"; + + public static final String MESSAGE_SUCCESS = "CSV exported."; + + public static final String MESSAGE_FAILURE = "Failed to export CSV."; + + private static final String NEWLINE_CHARACTER = "\n"; + + private static final Object[] GOOGLE_CALENDAR_HEADER = { "Subject", "Start Date", "Start Time", "End Date", "End Time", "All Day Event", "Description", "Location", "Private" }; + + private static final SimpleDateFormat GOOGLE_CALENDAR_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy"); + + private static final SimpleDateFormat GOOGLE_CALENDAR_TIME_FORMAT = new SimpleDateFormat("HH:mm"); + + private final String destination; + + private final boolean isExportingTasks; + + private final boolean isExportingDeadlines; + + private final boolean isExportingEvents; + + private final boolean isExportingArchives; + + //@@author A0138862W + /** + * Create a ExportCommand + * + * @param destination the file output destination + * @param isExportingTasks whether to export all floating tasks + * @param isExportingDeadlines whether to export all deadlines + * @param isExportingEvents whether to export all events + * @param isExportingArchives whether to export all archives + * @throws IOException if destination file path is invalid + */ + public ExportCommand(String destination, boolean isExportingTasks, boolean isExportingDeadlines, boolean isExportingEvents, boolean isExportingArchives) throws IOException { + this.destination = destination; + this.isExportingTasks = isExportingTasks; + this.isExportingDeadlines = isExportingDeadlines; + this.isExportingEvents = isExportingEvents; + this.isExportingArchives = isExportingArchives; + } + + //@@author A0138862W + @Override + public CommandResult execute() { + + CSVFormat csvFormat = CSVFormat.EXCEL; + + try (FileWriter fileWriter = new FileWriter(destination); CSVPrinter csvPrinter = new CSVPrinter(fileWriter, csvFormat);) { + printHeader(csvPrinter); + printTasks(csvPrinter); + printDeadlines(csvPrinter); + printEvents(csvPrinter); + printArchives(csvPrinter); + return new CommandResult(COMMAND_KEYWORD_EXPORT, MESSAGE_SUCCESS); + } catch (IOException e) { + return new CommandResult(COMMAND_KEYWORD_EXPORT, MESSAGE_FAILURE); + } + } + + //@@author A0138862W + private void printHeader(CSVPrinter csvPrinter) throws IOException { + csvPrinter.printRecord(GOOGLE_CALENDAR_HEADER); + } + + //@@author A0138862W + private void printTasks(CSVPrinter csvPrinter) throws IOException { + if (isExportingTasks) { + UnmodifiableObservableList tasks = model.getFilteredFloatingTaskList(); + printDataBody(csvPrinter, tasks); + } + } + + //@@author A0138862W + private void printDeadlines(CSVPrinter csvPrinter) throws IOException{ + if(isExportingDeadlines){ + UnmodifiableObservableList deadlines = model.getFilteredDeadlineList(); + printDataBody(csvPrinter, deadlines); + } + } + + //@@author A0138862W + private void printEvents(CSVPrinter csvPrinter) throws IOException{ + if(isExportingEvents){ + UnmodifiableObservableList events = model.getFilteredEventList(); + printDataBody(csvPrinter, events); + } + } + + //@@author A0138862W + private void printArchives(CSVPrinter csvPrinter) throws IOException{ + if(isExportingArchives){ + UnmodifiableObservableList archives = model.getFilteredArchiveList(); + printDataBody(csvPrinter, archives); + } + } + + //@@author A0138862W + /** + * A helper method to print list of tasks into csv. + * Due to the limitation of Google Calendar (must specify start and end date): + * + * - Floating tasks are assigned with dummy date (exported date) as their start and end date, in addition set the all day event as true. + * - Deadlines will share the same start and end dates + * - Event will and distinct start and end dates + * + * All tags are exported under descriptions + * + * @param csvPrinter CSVPrinter object + * @param tasks list of tasks to print + * @throws IOException if file is not writable + */ + private void printDataBody(CSVPrinter csvPrinter, UnmodifiableObservableList tasks) throws IOException { + for (ReadOnlyTask task : tasks) { + List data = new ArrayList<>(); + data.add(task.getName()); + if(task.isFloating()){ + Date dummyDate = new Date(); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(dummyDate)); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(dummyDate)); + } else if (task.isDeadline()){ + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + } else if (task.isEvent()){ + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getStartDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getStartDate())); + data.add(GOOGLE_CALENDAR_DATE_FORMAT.format(task.getEndDate())); + data.add(GOOGLE_CALENDAR_TIME_FORMAT.format(task.getEndDate())); + } + data.add((task.isFloating())? "TRUE": "FALSE"); + data.add(task.getTags().toString().replaceAll(",", " ")); + data.add(null); + data.add("TRUE"); + csvPrinter.printRecord(data); + } + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/FindCommand.java b/src/main/java/harmony/mastermind/logic/commands/FindCommand.java new file mode 100644 index 000000000000..b17dca41e57b --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/FindCommand.java @@ -0,0 +1,167 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Set; + +import harmony.mastermind.logic.parser.ParserSearch; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; + +/** + * Finds and lists all tasks in task manager whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all tasks whose names contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " dinner 2105"; + + public static final String COMMAND_SUMMARY = "Searching for a task:" + + "\n" + COMMAND_WORD + " KEYWORD" + "\n" + "\n" + "Searching for a task based on date:" + + "\n" + COMMAND_WORD + " DATE in DDMMYY format"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " [more_keywords]..."; + public static final String COMMAND_DESCRIPTION = "Finds task based on keywords input entered"; + public static final String FIND_SUCCESS = "Found with keyword(s): %1$s"; + + + private final Set keywords; + public static ArrayList findResult; + Memory memory; + + public FindCommand(Set keywords, Memory mem) { + this.keywords = keywords; + this.memory = mem; + } + + @Override + public CommandResult execute() { + model.updateFilteredList(keywords); + /* unused code due to better flow with updateFilteredList + * String[] list = keywords.toArray(new String[keywords.size()]); + ArrayList results = searchTerms(list, memory); + + StringBuilder sb = new StringBuilder(); + for (GenericMemory s : results) + { + sb.append(s.getName()); + sb.append("\n"); + } + */ + + +// return new CommandResult(COMMAND_WORD, String.format(FIND_SUCCESS, sb)); + return new CommandResult(COMMAND_WORD, getMessageForTaskListShownSummary(model.getCurrentListSize())); + } + + //@@author A0143378Y + /* + * Checks if memory item contains the keyword. + * If contains, add to exactResult + */ + private static void containsKeyword(String keyword, Memory memory, ArrayList exactResult, int i) { + if (memory.get(i).getName().equals(keyword)) { // found exact matching name item + exactResult.add(memory.get(i)); + } + } + + //@@author A0143378Y + /* + * Only for delete and update direct commands to search for exact matching names + * Should only return list of 1 item ideally + */ + public static ArrayList searchExact(String keyword, Memory memory) { + assert keyword.length() != 0; + ArrayList exactResult = new ArrayList(); + for (int i=0; i searchTerms(String[] keywords, Memory memory) { + ArrayList result = new ArrayList(memory.getList()); + findResult = recursiveSearchTerms(keywords, 0, result); // calls recursive search to narrow down results + if (findResult.size() >= 1) { + assert findResult.size() > 1; + String title = formatSearchTitle(keywords); + ListCommand.displayList(findResult, title); + } + + return findResult; + } + + //@@author A0143378Y + /* + * Formats search title with keywords used to perform the search + */ + private static String formatSearchTitle(String[] keywords) { + String title = "Search Keywords Result: " + keywords[0]; + for (int i=1; i recursiveSearchTerms(String[] keywords, int index, ArrayList result) { + ArrayList newResult = new ArrayList(); + if (index >= keywords.length) { // Base case: no more terms to search + return result; + } else { // Narrowing down results + assert index < keywords.length; + for (int i=0; i < result.size(); i++) { + if (result.get(i).getName().contains(keywords[index])) { // Found term in name + newResult.add(result.get(i)); + } else if ((result.get(i).getDescription()!= null) && result.get(i).getDescription().contains(keywords[index])) { // has description and found term in description + newResult.add(result.get(i)); + } + } + return recursiveSearchTerms(keywords, index+1, newResult); + } + } + + //@@author A0143378Y + public static ArrayList findDate(Calendar date, Memory memory) { + assert date != null; + findResult = new ArrayList(); + for (int i=0; i < memory.getSize(); i++) { + Calendar start = memory.get(i).getStart(); + Calendar end = memory.get(i).getEnd(); + + if (testTwoCalendar(date, start) || // Checks if given date is equals or between item's start and end date + testTwoCalendar(date, end) || + (date.after(start) && date.before(end)) ) { + findResult.add(memory.get(i)); + } + } + + return findResult; + } + + //@@author A0143378Y + /* + * Returns true if two dates are the same + */ + private static boolean testTwoCalendar(Calendar a, Calendar b) { + if (b == null) { + return false; + } else { + assert (a != null) && (b != null); + return (a.get(Calendar.YEAR)== b.get(Calendar.YEAR))&& + (a.get(Calendar.MONTH) == b.get(Calendar.MONTH))&& + (a.get(Calendar.DATE)== b.get(Calendar.DATE)); + } + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/FindTagCommand.java b/src/main/java/harmony/mastermind/logic/commands/FindTagCommand.java new file mode 100644 index 000000000000..250ff8d0792c --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/FindTagCommand.java @@ -0,0 +1,49 @@ +package harmony.mastermind.logic.commands; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.tag.Tag; + +//@@author A0124797R +/** + * Finds and lists all tasks in task manager whose tag contains any of the argument keywords. + * Keyword matching is case sensitive. + */ + +public class FindTagCommand extends Command { + + public static final String COMMAND_WORD = "findtag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + "Finds all tasks whose tags contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n\t" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n\t" + + "Example: " + COMMAND_WORD + " meal finals"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " ..."; + public static final String COMMAND_DESCRIPTION = "Finds all tasks whose tags contain any of the specified keywords " + + "(case-sensitive)"; + + private final Set keywords; + + public FindTagCommand(Set keywords) throws IllegalValueException { + + this.keywords = keywords; + } + + /** + * Returns copy of keywords in this command. + */ + public Set getKeywords() { + return new HashSet<>(keywords); + } + + @Override + public CommandResult execute() { + model.updateFilteredTagTaskList(keywords); + return new CommandResult(COMMAND_WORD, getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } + +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/commands/HelpCommand.java b/src/main/java/harmony/mastermind/logic/commands/HelpCommand.java new file mode 100644 index 000000000000..88808a92069d --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/HelpCommand.java @@ -0,0 +1,142 @@ +package harmony.mastermind.logic.commands; + + +import java.util.ArrayList; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.events.ui.ShowHelpRequestEvent; +import harmony.mastermind.logic.HelpPopupEntry; + +/**@@author A0139194X + * Formats full help instructions for every command for display. + */ +public class HelpCommand extends Command { + + public static final String COMMAND_WORD = "help"; + + public static final String COMMAND_DESCRIPTION = "Shows program usage instructions"; + + public static final String COMMAND_SUMMARY = "Getting help:" + + "\n" + COMMAND_WORD; + + private ArrayList commandList; + private ArrayList formatList; + private ArrayList descriptionList; + private ArrayList helpEntries; + + public static final String SUCCESSFULLY_SHOWN = "Command summary displayed."; + + public HelpCommand() { + initInfo(); + } + + private void initInfo() { + //if its already initialised, don't redo it + if (helpEntries == null) { + initCommandWords(); + initFormat(); + initDescription(); + initEntries(); + } + } + + /** + * Initialise the Entries that will be sent to the UI component + */ + private void initEntries() { + helpEntries = new ArrayList(); + for (int i = 0; i < commandList.size(); i++) { + helpEntries.add(new HelpPopupEntry(commandList.get(i), formatList.get(i), descriptionList.get(i))); + } + } + + /** + * Consolidate all the command words + */ + private void initCommandWords() { + commandList = new ArrayList(); + commandList.add(HelpCommand.COMMAND_WORD); + commandList.add(AddCommand.COMMAND_KEYWORD_ADD + ", " + AddCommand.COMMAND_KEYWORD_DO); + commandList.add(EditCommand.COMMAND_KEYWORD_EDIT + ", " + + EditCommand.COMMAND_KEYWORD_UPDATE + ", " + + EditCommand.COMMAND_KEYWORD_CHANGE); + commandList.add(MarkCommand.COMMAND_WORD); + commandList.add(UnmarkCommand.COMMAND_WORD); + commandList.add(DeleteCommand.COMMAND_WORD); + commandList.add(UndoCommand.COMMAND_WORD); + commandList.add(RedoCommand.COMMAND_WORD); + commandList.add(ListCommand.COMMAND_WORD); + commandList.add(FindCommand.COMMAND_WORD); + commandList.add(FindTagCommand.COMMAND_WORD); + commandList.add(UpcomingCommand.COMMAND_WORD); + commandList.add(RelocateCommand.COMMAND_WORD); + commandList.add(ImportCommand.COMMAND_WORD); + commandList.add(ExportCommand.COMMAND_KEYWORD_EXPORT); + commandList.add(HistoryCommand.COMMAND_KEYWORD_ACTIONHISTORY); + commandList.add(ClearCommand.COMMAND_WORD); + commandList.add(ExitCommand.COMMAND_WORD); + } + + /** + * Consolidate all the formats for help + */ + private void initFormat() { + formatList = new ArrayList(); + formatList.add(HelpCommand.COMMAND_WORD); + formatList.add(AddCommand.COMMAND_FORMAT); + formatList.add(EditCommand.COMMAND_FORMAT); + formatList.add(MarkCommand.COMMAND_FORMAT); + formatList.add(UnmarkCommand.COMMAND_FORMAT); + formatList.add(DeleteCommand.COMMAND_FORMAT); + formatList.add(UndoCommand.COMMAND_WORD); + formatList.add(RedoCommand.COMMAND_WORD); + formatList.add(ListCommand.COMMAND_FORMAT); + formatList.add(FindCommand.COMMAND_FORMAT); + formatList.add(FindTagCommand.COMMAND_FORMAT); + formatList.add(UpcomingCommand.COMMAND_FORMAT); + formatList.add(RelocateCommand.COMMAND_FORMAT); + formatList.add(ImportCommand.COMMAND_FORMAT); + formatList.add(ExportCommand.COMMAND_FORMAT); + formatList.add(HistoryCommand.COMMAND_KEYWORD_ACTIONHISTORY); + formatList.add(ClearCommand.COMMAND_WORD); + formatList.add(ExitCommand.COMMAND_WORD); + } + + /** + * Consolidate all the descriptions for help + */ + private void initDescription() { + descriptionList = new ArrayList(); + descriptionList.add(HelpCommand.COMMAND_DESCRIPTION); + descriptionList.add(AddCommand.COMMAND_DESCRIPTION); + descriptionList.add(EditCommand.COMMAND_DESCRIPTION); + descriptionList.add(MarkCommand.COMMAND_DESCRIPTION); + descriptionList.add(UnmarkCommand.COMMAND_DESCRIPTION); + descriptionList.add(DeleteCommand.COMMAND_DESCRIPTION); + descriptionList.add(UndoCommand.COMMAND_DESCRIPTION); + descriptionList.add(RedoCommand.COMMAND_DESCRIPTION); + descriptionList.add(ListCommand.COMMAND_DESCRIPTION); + descriptionList.add(FindCommand.COMMAND_DESCRIPTION); + descriptionList.add(FindTagCommand.COMMAND_DESCRIPTION); + descriptionList.add(UpcomingCommand.COMMAND_DESCRIPTION); + descriptionList.add(RelocateCommand.COMMAND_DESCRIPTION); + descriptionList.add(ImportCommand.COMMAND_DESCRIPTION); + descriptionList.add(ExportCommand.COMMAND_DESCRIPTION); + descriptionList.add(HistoryCommand.COMMAND_DESCRIPTION); + descriptionList.add(ClearCommand.COMMAND_DESCRIPTION); + descriptionList.add(ExitCommand.COMMAND_DESCRIPTION); + } + + //@@author A0139194X + @Override + public CommandResult execute() { + EventsCenter.getInstance().post(new ShowHelpRequestEvent(getEntries())); + return new CommandResult(COMMAND_WORD, SUCCESSFULLY_SHOWN); + } + + //@@author A0139194X + public ArrayList getEntries() { + return helpEntries; + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/History.java b/src/main/java/harmony/mastermind/logic/commands/History.java new file mode 100644 index 000000000000..ddd6037de5ed --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/History.java @@ -0,0 +1,105 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; +import java.util.Stack; + +import harmony.mastermind.storage.StorageMemory; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; + +public class History { + private static ArrayList current = null; + private static Stack> back = new Stack>(); + private static Stack> forward = new Stack>(); + + //@@author A0143378Y + /* + * Creates a snapshot of changes in the memory and moves previous state into the back stack. forward stack is reinitialized + */ + public static void advance(Memory memory) { + forward = new Stack>(); + back.push(current); + current = new ArrayList(); + duplicateMemory(memory); + } + + //@@author A0143378Y + /* + * Duplicates memory into the current ArrayList + */ + private static void duplicateMemory(Memory memory) { + for (int i = 0; i< memory.getSize(); i++) { // Duplicating of memory into snapshot + current.add(new GenericMemory(memory.get(i).getType(), + memory.get(i).getName(), + memory.get(i).getDescription(), + memory.get(i).getStart(), + memory.get(i).getEnd(), + memory.get(i).getState())); + } + } + + /* + * Unused code: + * Reason: For future implementations of Memory + //@@author A0143378Y-unused + // Retrieves most recent snapshot in the forward stack and swap it with current memory. Current memory gets pushed into back stack + public static void redo(Memory memory) { + if (forward.isEmpty() || (forward.peek() == null)) { + System.out.println("Nothing to redo!"); + return; + } + + redoMemorySwap(memory); + StorageMemory.saveToStorage(memory); + System.out.println("Redo successful."); + } + + //@@author A0143378Y + // Swaps memory used for redo + private static void redoMemorySwap(Memory memory) { + assert forward.size() > 0; + back.push(current); + current = forward.pop(); + memory.setList(current); + } + + //@@author A0143378Y + // Retrieves most recent snapshot in the back stack and swap it with current memory. Current memory gets pushed into forward stack + public static void undo(Memory memory) { + if (back.isEmpty() || (back.peek() == null)) { + System.out.println("Nothing to undo!"); + return; + } + + ArrayList temp = duplicateTemp(memory); + undoMemorySwap(memory, temp); + StorageMemory.saveToStorage(memory); + System.out.println("Undo successful."); + } + + //@@author A0143378Y + // Swaps memory used for undo + private static void undoMemorySwap(Memory memory, ArrayList temp) { + forward.push(temp); + current = back.pop(); + assert current != null; + memory.setList(current); + } + + //@@author A0143378Y + // Duplicate current memory into temp ArrayList + private static ArrayList duplicateTemp(Memory memory) { + ArrayList temp = new ArrayList(); // Duplicating of memory into forward stack + for (int i = 0; i < memory.getSize(); i++) { + temp.add(new GenericMemory(memory.get(i).getType(), + memory.get(i).getName(), + memory.get(i).getDescription(), + memory.get(i).getStart(), + memory.get(i).getEnd(), + memory.get(i).getState())); + } + return temp; + } + */ + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/HistoryCommand.java b/src/main/java/harmony/mastermind/logic/commands/HistoryCommand.java new file mode 100644 index 000000000000..ac2ddcf38837 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/HistoryCommand.java @@ -0,0 +1,34 @@ +package harmony.mastermind.logic.commands; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.events.ui.ToggleActionHistoryEvent; + +//@@author A0138862W +/** + * + * Command to toggle ActionHistory in UI + * + * This command takes no parameter. + * @author kfwong + * + */ +public class HistoryCommand extends Command { + + public static final String COMMAND_KEYWORD_ACTIONHISTORY = "history"; + public static final String COMMAND_DESCRIPTION = "Toggles action history bar"; + + public static final String MESSAGE_SUCCESS = "Action history toggled."; + + @Override + public CommandResult execute() { + + requestToggleActionHistory(); + return new CommandResult(COMMAND_KEYWORD_ACTIONHISTORY, MESSAGE_SUCCESS); + + } + + private void requestToggleActionHistory() { + EventsCenter.getInstance().post(new ToggleActionHistoryEvent()); + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/ImportCommand.java b/src/main/java/harmony/mastermind/logic/commands/ImportCommand.java new file mode 100644 index 000000000000..f3890388d730 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/ImportCommand.java @@ -0,0 +1,295 @@ +package harmony.mastermind.logic.commands; + +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVRecord; + +import biweekly.Biweekly; +import biweekly.ICalendar; +import biweekly.component.VEvent; +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.TaskBuilder; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; + + +//@@author A0138862W +/** + * Reads either ics/csv file and import the tasks into Mastermind + */ +public class ImportCommand extends Command { + private static final int HEADER_LINE = 1; + private static final int INDEX_NAME = 0; + private static final int INDEX_START_DATE = 1; + private static final int INDEX_START_TIME = 2; + private static final int INDEX_END_DATE = 3; + private static final int INDEX_END_TIME = 4; + + public static final String COMMAND_WORD = "import"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reads file and add all task from file into Mastermind\n" + + "Parameters: File location\n" + + "Example:\n" + + "for Mac:\n" + + COMMAND_WORD + "from" + + " Users/Jim/Desktop/jim@gmail.com.ics\n" + + "for Windows:\n" + + COMMAND_WORD + "from" + + " C:\\Users\\Jim\\jim@gmail.com.ics"; + + public static final String COMMAND_ARGUMENTS_REGEX = "from (?.+)(?<=(?txt|csv|ics))"; + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile(COMMAND_ARGUMENTS_REGEX); + + public static final String MESSAGE_READ_SUCCESS = "Read success on imported file"; + public static final String MESSAGE_READ_FAILURE = "Invalid file path: %1$s"; + public static final String MESSAGE_CSV_READ_FAILURE = "Header in csv File is invalid\n" + + "First row of your csv file should include headers " + + "like Subject, Start Date, Start Time, End Date, End Time"; + public static final String MESSAGE_IMPORT_TXT_SUCCESS = "Import success: %1$s tasks added"; + public static final String MESSAGE_IMPORT_TXT_FAILURE = "Import failure: %1$s tasks added \nInvalid lines: %2$s"; + public static final String MESSAGE_IMPORT_ICS_SUCCESS = "Import ics success."; + public static final String MESSAGE_IMPORT_ICS_FAILURE = "Failed to import ics."; + + public static final String MESSAGE_FAILURE_DUPLICATE_TASK = "Failed to import ics. Duplicate task detected when importing."; + + public static final String COMMAND_FORMAT = "import "; + public static final String COMMAND_DESCRIPTION = "Reads file and add all task from file into Mastermind"; + + public static final String HEADER_NAME = "Subject"; + public static final String HEADER_START_DATE = "Start Date"; + public static final String HEADER_START_TIME = "Start Time"; + public static final String HEADER_END_DATE = "End Date"; + public static final String HEADER_END_TIME = "End Time"; + + + public static final String EXT_CSV = "csv"; + public static final String EXT_ICS = "ics"; + + public static final String REGEX_COMMA = ","; + + public static final String EMPTY_ARG = ""; + + private String fileToImport; + private String extension; + private ArrayList lstOfCmd; + + //@@author A0124797R + public ImportCommand(String filePath, String extension) { + this.fileToImport = filePath.trim(); + this.extension = extension; + lstOfCmd = new ArrayList(); + } + + @Override + public CommandResult execute() { + assert fileToImport != null; + assert lstOfCmd != null; + + CommandResult result = null; + + switch (extension) { + case EXT_ICS: + result = importIcsFile(); + break; + case EXT_CSV: + result = importCsvFile(); + break; + } + return result; + + } + + /** gets the list of commands for adding tasks */ + public ArrayList getTaskToAdd() { + return this.lstOfCmd; + } + + private CommandResult importCsvFile() { + int currLine = 0; + int errCount = 0; + String errLines = ""; + try { + Iterable records = CSVFormat.RFC4180 + .withHeader(HEADER_NAME, HEADER_START_DATE, HEADER_START_TIME, HEADER_END_DATE, HEADER_END_TIME) + .parse(new FileReader(fileToImport)); + + boolean isTask = false; + for (CSVRecord record : records) { + if (isTask) { + currLine++; + try { + Optional taskToAdd = parseCsvRecord(record); + if (taskToAdd.isPresent()) { + model.addTask(taskToAdd.get()); + } else { + errCount++; + errLines += Integer.toString(currLine) + ","; + } + } catch (IllegalValueException | InvalidEventDateException | IllegalArgumentException e) { + errCount++; + errLines += Integer.toString(currLine) + ","; + } + + } else { + currLine++; + isTask = true; + if (!record.get(INDEX_NAME).equals(HEADER_NAME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_START_DATE).equals(HEADER_START_DATE)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_START_TIME).equals(HEADER_START_TIME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_END_DATE).equals(HEADER_END_DATE)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + if (!record.get(INDEX_END_TIME).equals(HEADER_END_TIME)) { + return new CommandResult(COMMAND_WORD, MESSAGE_CSV_READ_FAILURE); + } + } + } + + } catch (IOException ioe) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_READ_FAILURE, fileToImport)); + } + + int addCount = currLine - errCount - HEADER_LINE; + if (errLines.isEmpty()) { + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(MESSAGE_IMPORT_TXT_SUCCESS, addCount)); + } else { + errLines = errLines.substring(0,errLines.length()-1); + return new CommandResult(ImportCommand.COMMAND_WORD, String.format(MESSAGE_IMPORT_TXT_FAILURE, addCount, errLines)); + } + } + + /** + * reads a csv record and return a Task if its valid else an empty Optional + * @return Parsed Task object + * @throws IllegalValueException + * @throws InvalidEventDateException + */ + private Optional parseCsvRecord(CSVRecord record) throws IllegalValueException, InvalidEventDateException { + Optional name; + Optional startDate; + Optional endDate; + + if (record.get(HEADER_NAME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + name = Optional.ofNullable(record.get(HEADER_NAME)); + } + + if (record.get(HEADER_START_DATE).equals(EMPTY_ARG)) { + startDate = Optional.empty(); + } else if (record.get(HEADER_START_TIME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + startDate = Optional.ofNullable(record.get(HEADER_START_DATE) + " " + record.get(HEADER_START_TIME)); + } + if (record.get(HEADER_END_DATE).equals(EMPTY_ARG)) { + endDate = Optional.empty(); + } else if (record.get(HEADER_END_TIME).equals(EMPTY_ARG)) { + return Optional.empty(); + } else { + endDate = Optional.ofNullable(record.get(HEADER_END_DATE) + " " + record.get(HEADER_END_TIME)); + } + if (startDate.isPresent() && !endDate.isPresent()) { + return Optional.empty(); + } + + + Set tags = new HashSet(); + tags.add("CSVIMPORT"); + + TaskBuilder taskBuilder = new TaskBuilder(name.get()); + taskBuilder.withTags(tags); + + if (startDate.isPresent() && endDate.isPresent()) { + taskBuilder.asEvent(prettyTimeParser.parse(startDate.get()).get(0), prettyTimeParser.parse(endDate.get()).get(0)); + return Optional.ofNullable(taskBuilder.build()); + } else if (endDate.isPresent()) { + taskBuilder.asDeadline(prettyTimeParser.parse(endDate.get()).get(0)); + return Optional.ofNullable(taskBuilder.build()); + } else if (!endDate.isPresent()) { + taskBuilder.asFloating(); + return Optional.ofNullable(taskBuilder.build()); + } else { + return Optional.empty(); + } + + } + + //@@author A0124797R-unused + // find that it does not make sense to import a txt file of commands, + // instead user could have + /* + private CommandResult importTxtFile() { + try { + BufferedReader br = model.importFile(fileToImport); + + String line; + + while ((line = br.readLine()) != null) { + lstOfCmd.add(line); + } + + } catch (IOException e) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_READ_FAILURE, fileToImport)); + } + + return new CommandResult(COMMAND_WORD, MESSAGE_READ_SUCCESS); + }*/ + + + //@@author A0138862W + private CommandResult importIcsFile() { + + try (FileInputStream fis = new FileInputStream(fileToImport)){ + ICalendar ical = Biweekly.parse(fis).first(); + + for (VEvent event : ical.getEvents()) { + Task task = parseTask(event); + model.addTask(task); + } + + return new CommandResult(COMMAND_WORD, MESSAGE_IMPORT_ICS_SUCCESS); + } catch (DuplicateTaskException e){ + return new CommandResult(COMMAND_WORD, MESSAGE_FAILURE_DUPLICATE_TASK); + } catch (InvalidEventDateException | IOException | IllegalValueException e) { + return new CommandResult(COMMAND_WORD, MESSAGE_IMPORT_ICS_FAILURE); + } + } + + /** + * This method will attempt to parse a ical's VEvent to a Mastermind Task Object + * + * @param event The ical VEvent Object to parse + * @return the parsed Task object + * @throws InvalidEventDateException if start date is after end date + * @throws IllegalValueException if tags contains non-alphanumeric characters + */ + private Task parseTask(VEvent event) throws InvalidEventDateException, IllegalValueException { + Set tags = new HashSet(); + tags.add("ICALIMPORT"); + + TaskBuilder taskBuilder = new TaskBuilder(event.getSummary().getValue()); + taskBuilder.asEvent(event.getDateStart().getValue(), event.getDateEnd().getValue()); + taskBuilder.withTags(tags); + + return taskBuilder.build(); + } + + +} diff --git a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java b/src/main/java/harmony/mastermind/logic/commands/IncorrectCommand.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/IncorrectCommand.java rename to src/main/java/harmony/mastermind/logic/commands/IncorrectCommand.java index 491d9cb9da35..2b22f03f4d23 100644 --- a/src/main/java/seedu/address/logic/commands/IncorrectCommand.java +++ b/src/main/java/harmony/mastermind/logic/commands/IncorrectCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package harmony.mastermind.logic.commands; /** @@ -15,7 +15,7 @@ public IncorrectCommand(String feedbackToUser){ @Override public CommandResult execute() { indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(feedbackToUser); + return new CommandResult("INVALID COMMAND", feedbackToUser); } } diff --git a/src/main/java/harmony/mastermind/logic/commands/ListCommand.java b/src/main/java/harmony/mastermind/logic/commands/ListCommand.java new file mode 100644 index 000000000000..c5755166d89b --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/ListCommand.java @@ -0,0 +1,154 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.model.ModelManager; +import java.util.Optional; + +/** + * Lists all tasks in the task manager to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " [tab_name]"; + public static final String COMMAND_DESCRIPTION = "Listing all tasks in the tab"; + + public static final String MESSAGE_USAGE = "[Examples] \nlist \nlist events"; + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + public static final String MESSAGE_SUCCESS_TASKS = "Listed all floating tasks"; + public static final String MESSAGE_SUCCESS_EVENTS = "Listed all events"; + public static final String MESSAGE_SUCCESS_DEADLINES = "Listed all deadlines"; + public static final String MESSAGE_SUCCESS_ARCHIVES = "Listed all archived tasks"; + public static final String COMMAND_SUMMARY = "Listing all tasks:" + + "\n" + COMMAND_WORD; + + private static final String BRACKET_CLOSE = "] "; + private static final String BRACKET_OPEN = ". ["; + private static final String BLANK = " "; + private static final String ALERT = "!"; + private static final String MARK = "x"; + private static final String ITEM_VIEW = "Item View"; + private static final String DISPLAY_ITEM_TYPE = "Display item type: "; + private static final String LINE2 = "_________________________________________________________________"; + private static final String LINE = "_________________________________________________________________\n"; + public static GenericMemory detailedView = null; + public static ArrayList listView = null; + public static String listName = null; + + private String tab; + + //@@author A0124797R + public ListCommand() { + tab = ModelManager.TAB_HOME.toLowerCase(); + } + + public ListCommand(Optional args) { + tab = args.get(); + } + + @Override + public CommandResult execute() { + assert tab != null; + + CommandResult cResult = null; + String tabToShow = null; + + + if (tab.equals(ModelManager.TAB_TASKS.toLowerCase())) { + tabToShow = ModelManager.TAB_TASKS; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_TASKS); + } else if (tab.equals(ModelManager.TAB_EVENTS.toLowerCase())) { + tabToShow = ModelManager.TAB_EVENTS; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_EVENTS); + } else if (tab.equals(ModelManager.TAB_DEADLINES.toLowerCase())) { + tabToShow = ModelManager.TAB_DEADLINES; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_DEADLINES); + } else if (tab.equals(ModelManager.TAB_ARCHIVES.toLowerCase())) { + tabToShow = ModelManager.TAB_ARCHIVES; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_ARCHIVES); + } else if (tab.equals(ModelManager.TAB_HOME.toLowerCase())){ + tabToShow = ModelManager.TAB_HOME; + cResult = new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS); + } + + model.updateFilteredListToShow(tabToShow); + + return cResult; + } + + //@author A0143378Y + /* + * Clears all main window text + */ + public static void displayClear() { + detailedView = null; + } + + //@author A0143378Y + /* + * Displays an individual GenericMemory item with all non-null details + */ + public static String displayDetailed(GenericMemory item) { + displayClear(); + detailedView = item; + + assert detailedView != null; + + return formatItem(item); + } + + //@author A0143378Y + /* + * Formats the item display + */ + private static String formatItem(GenericMemory item) { + String result = ITEM_VIEW + "\n" + LINE + "\n" + item + "\n" + LINE2; + return result; + } + + //@author A0143378Y + /* + * Takes an ArrayList of GenericMemoryitems, and display them in a list form, with name as the heading + */ + public static String displayList(ArrayList list, String name) { + displayClear(); + detailedView = null; + listName = name; + + assert listName != null; + + return formatList(list); + } + + //@author A0143378Y + /* + * Formats the list display + */ + private static String formatList(ArrayList list) { + String result = listName + "\n" + LINE; + for (int i=0; i < list.size(); i++) { + String line = i + 1 + BRACKET_OPEN; + line += BRACKET_CLOSE + list.get(i).getName(); + + result = result + "\n" + line; + } + + result = result + "\n" + LINE2; + return result; + } + + //@author A0143378Y + /* + * Displays list of item with the corresponding type + */ + public static void displayType(ArrayList list, String type) { + assert type.length() != 0; + + if (list.size() > 1) { // if list contains multiple items, show as list + displayList(list, DISPLAY_ITEM_TYPE + type); + } else if (list.size() == 1){ // if only one item, show item + displayDetailed(list.get(0)); + } + } +} diff --git a/src/main/java/harmony/mastermind/logic/commands/MarkCommand.java b/src/main/java/harmony/mastermind/logic/commands/MarkCommand.java new file mode 100644 index 000000000000..a244b24dbc70 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/MarkCommand.java @@ -0,0 +1,158 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; +import java.util.Date; + +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.exceptions.NotRecurringTaskException; +import harmony.mastermind.commons.exceptions.TaskAlreadyMarkedException; +import harmony.mastermind.model.ModelManager; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; +import javafx.collections.ObservableList; + +//@@author A0124797R +/** + * marks a task as complete and moves it to the archives tab + */ +public class MarkCommand extends Command implements Undoable, Redoable { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": mark a task as done " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + + COMMAND_WORD + + " 1"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + public static final String COMMAND_DESCRIPTION = "Marking a task as done"; + + public static final String MESSAGE_MARK_SUCCESS = "%1$s has been archived"; + public static final String MESSAGE_MARK_DUE_SUCCESS = "All Tasks that are due has been archived"; + public static final String MESSAGE_MARK_FAILURE = "Selected is already marked"; + public static final String MESSAGE_MARK_RECURRING_FAILURE = "Unable to add recurring Task"; + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Mark Command] %1$s has been unmarked"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Mark Command] %1$s has been archived"; + + private int targetIndex; + private String type; + private ArrayList tasksToMark; + + public MarkCommand(int targetIndex) { + this.targetIndex = targetIndex; + this.type = "empty"; + tasksToMark = new ArrayList(); + } + + public MarkCommand(String taskType) { + this.type = taskType; + tasksToMark = new ArrayList(); + } + + @Override + public CommandResult execute() { + try { + if (this.model.getCurrentTab().equals(ModelManager.TAB_ARCHIVES)) { + return new CommandResult(COMMAND_WORD, MarkCommand.MESSAGE_MARK_FAILURE); + } + + executeMark(); + + model.pushToUndoHistory(this); + model.clearRedoHistory(); + + if (type.equals("due")) { + return new CommandResult(COMMAND_WORD, MESSAGE_MARK_DUE_SUCCESS); + } else { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_MARK_SUCCESS, tasksToMark.get(0))); + } + } catch (TaskNotFoundException pnfe) { + return new CommandResult(COMMAND_WORD,Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } catch (DuplicateTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } catch (NotRecurringTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } + + } + + // @@author A0138862W + @Override + /* + * Strategy to undo mark command + * + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + try { + + for (Task t:tasksToMark) { + model.unmarkTask(t); + + requestHighlightLastActionedRow(t); + } + model.pushToRedoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, tasksToMark.get(0))); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(UnmarkCommand.MESSAGE_DUPLICATE_UNMARK_TASK, tasksToMark.get(0))); + } catch (ArchiveTaskList.TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + + // @@author A0138862W + @Override + /* + * Strategy to redo mark command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + try { + executeMark(); + + model.pushToUndoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_REDO_SUCCESS, tasksToMark.get(0))); + + } catch (TaskNotFoundException pnfe) { + return new CommandResult(COMMAND_WORD,Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } catch (DuplicateTaskException | NotRecurringTaskException e) { + return new CommandResult(COMMAND_WORD,MESSAGE_MARK_RECURRING_FAILURE); + } + } + + //@@author A0124797R + private void executeMark() throws TaskNotFoundException, DuplicateTaskException, NotRecurringTaskException { + ObservableList lastShownList = model.getListToMark(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new TaskNotFoundException(); + } + + if (type.equals("empty")) { + tasksToMark.add(lastShownList.get(targetIndex - 1)); + Task taskToMark = tasksToMark.get(0); + model.markTask(taskToMark); + if (taskToMark.isRecur()) { + model.addNextTask(taskToMark); + } + } else if (type.equals("due")){ + model.updateFilteredListToShowUpcoming(new Date().getTime(),type); + lastShownList = model.getListToMark(); + for (Task task: lastShownList) { + tasksToMark.add(task); + } + + model.markDue(tasksToMark); + } + + + } +} diff --git a/src/main/java/harmony/mastermind/logic/commands/RedoCommand.java b/src/main/java/harmony/mastermind/logic/commands/RedoCommand.java new file mode 100644 index 000000000000..ada1b41c282b --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/RedoCommand.java @@ -0,0 +1,35 @@ +package harmony.mastermind.logic.commands; + +import java.util.EmptyStackException; + +public class RedoCommand extends Command{ + +public static final String COMMAND_WORD = "redo"; + + public static final String MESSAGE_SUCCESS = "Redo successfully."; + public static final String MESSAGE_EMPTY_COMMAND_HISTORY = "There's no more action available to redo."; + public static final String MESSAGE_COMMAND_NOT_UNDOABLE = "This command is not redoable"; + public static final String COMMAND_DESCRIPTION = "Redoing an action"; + + @Override + //@@author A0138862W + public CommandResult execute() { + + try{ + // All Command supports undo operation must implement Redoable interface + + // execute the redo strategy implemented by the underlying command + CommandResult redoResult = model.redo(); + + // display successful message and the details of the undo operations + return new CommandResult(COMMAND_WORD, + MESSAGE_SUCCESS + "\n" + + "=====Redo Details=====\n" + + redoResult.feedbackToUser + "\n"+ + "=================="); + }catch(EmptyStackException ex){ + return new CommandResult(COMMAND_WORD, MESSAGE_EMPTY_COMMAND_HISTORY); + } + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/Redoable.java b/src/main/java/harmony/mastermind/logic/commands/Redoable.java new file mode 100644 index 000000000000..25f8a5afb54d --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/Redoable.java @@ -0,0 +1,10 @@ +package harmony.mastermind.logic.commands; +// @@author A0138862W +public interface Redoable { + /** + * Specify the redo strategy according to the nature of the corresponding class. + * + * @return CommandResult, the Object contains details & feedback about the redo operation. + */ + public CommandResult redo(); +} diff --git a/src/main/java/harmony/mastermind/logic/commands/RelocateCommand.java b/src/main/java/harmony/mastermind/logic/commands/RelocateCommand.java new file mode 100644 index 000000000000..7b8dcf07cef3 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/RelocateCommand.java @@ -0,0 +1,69 @@ +package harmony.mastermind.logic.commands; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.UnwrittableFolderException; +import harmony.mastermind.commons.util.FileUtil; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.*; +import harmony.mastermind.storage.StorageManager; + +/** + * @@author A0139194X + * Relocates save location to another file path + */ +public class RelocateCommand extends Command { + + public static final String COMMAND_WORD = "relocate"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes save location in MasterMind. " + + "Parameters: FILE_PATH\n" + + "Example: " + COMMAND_WORD + + "Desktop"; + + public static final String COMMAND_DESCRIPTION = "Change your data's save location"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + + public static final String MESSAGE_SUCCESS = "Relocated save location to %1$s"; + public static final String MESSAGE_INVALID_INPUT = "%1$s is not valid."; + public static final String MESSAGE_UNWRITTABLE_FOLDER = "%1$s is not writtable."; + + private final String newFilePath; + + /** + * @@author A0139194X + * Convenience constructor using raw values. + */ + public RelocateCommand(String newFilePath) { + this.newFilePath = newFilePath.trim(); + } + + //@@author A0139194X + @Override + public CommandResult execute() { + assert model != null; + assert newFilePath != null; + try { + FileUtil.checkSaveLocation(newFilePath); + FileUtil.checkWrittableDirectory(newFilePath); + model.relocateSaveLocation(newFilePath); + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_SUCCESS, newFilePath)); + } catch (FolderDoesNotExistException fdnee) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_INVALID_INPUT, newFilePath)); + } catch (UnwrittableFolderException ufe) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNWRITTABLE_FOLDER, newFilePath)); + } + } +} diff --git a/src/main/java/harmony/mastermind/logic/commands/Sort.java b/src/main/java/harmony/mastermind/logic/commands/Sort.java new file mode 100644 index 000000000000..1d4f4497f560 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/Sort.java @@ -0,0 +1,83 @@ +package harmony.mastermind.logic.commands; + +import java.util.ArrayList; + +import org.ocpsoft.prettytime.shade.edu.emory.mathcs.backport.java.util.Collections; + +import harmony.mastermind.memory.GenericMemory; + +public class Sort { + private static ArrayList task; + private static ArrayList deadline; + private static ArrayList event; + + //@@author A0143378Y + /* Sort all items in memory by urgency. + * Items arranged by Event, Deadline, Task + * Event sorted by earliest start date followed by end date + * Deadline sorted by earliest due date + * Task sorted by alphabet + */ + public static void sort(ArrayList list) { + task = new ArrayList(); + deadline = new ArrayList(); + event = new ArrayList(); + splitMemory(list); + + sortAllList(); + + joinList(list); + } + + //@@author A0143378Y + /* + * Reform list by adding Event, Deadline, Task items in sorted order + */ + private static void joinList(ArrayList list) { + list.removeAll(list); + assert list.size() == 0; + for (int i = 0; i < event.size(); i++) { + list.add(event.get(i)); + } + + for (int i = 0; i < deadline.size(); i++) { + list.add(deadline.get(i)); + } + + for (int i = 0; i < task.size(); i++) { + list.add(task.get(i)); + } + } + + //@@author A0143378Y + /* + * Sorts the 3 lists + */ + private static void sortAllList() { + Collections.sort(task); + Collections.sort(deadline); + Collections.sort(event); + } + + //@@author A0143378Y + /* + * Split all items into their own list for sorting + */ + public static void splitMemory(ArrayList list) { + for (int i = 0; i < list.size(); i++) { + switch (list.get(i).getType()) { + case "Task": + task.add(list.get(i)); + break; + + case "Deadline": + deadline.add(list.get(i)); + break; + + case "Event": + event.add(list.get(i)); + break; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/commands/UndoCommand.java b/src/main/java/harmony/mastermind/logic/commands/UndoCommand.java new file mode 100644 index 000000000000..f8bf50add75a --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/UndoCommand.java @@ -0,0 +1,40 @@ +package harmony.mastermind.logic.commands; + +import java.util.EmptyStackException; + +import harmony.mastermind.model.TaskManager; +// @@author A0138862W +public class UndoCommand extends Command{ + + public static final String COMMAND_WORD = "undo"; + + public static final String MESSAGE_SUCCESS = "Undo successfully."; + public static final String MESSAGE_EMPTY_COMMAND_HISTORY = "There's no more action available to undo."; + public static final String MESSAGE_COMMAND_NOT_UNDOABLE = "This command is not undoable"; + public static final String COMMAND_DESCRIPTION = "Undo an action"; + + @Override + //@@author A0138862W + public CommandResult execute() { + + try{ + // All Command supports undo operation must implement Undoable interface + + // execute the undo strategy implemented by the underlying command + CommandResult undoResult = model.undo(); + + // display successful message and the details of the undo operations + return new CommandResult(COMMAND_WORD, + MESSAGE_SUCCESS + "\n" + + "=====Undo Details=====\n" + + undoResult.feedbackToUser + "\n"+ + "=================="); + }catch(EmptyStackException ex){ + return new CommandResult(COMMAND_WORD, MESSAGE_EMPTY_COMMAND_HISTORY); + } + } + + + +} + diff --git a/src/main/java/harmony/mastermind/logic/commands/Undoable.java b/src/main/java/harmony/mastermind/logic/commands/Undoable.java new file mode 100644 index 000000000000..733af894639d --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/Undoable.java @@ -0,0 +1,16 @@ +package harmony.mastermind.logic.commands; + +// @@author A0138862W +public interface Undoable { + + /** + * Specify the undo strategy according to the nature of the corresponding class. + * For example, to implement an undo operation on AddCommand, a delete operation should be implemented. + * Similarly, a DeleteCommand should implement a add operation to restore the record. + * + * @return CommandResult, the Object contains details & feedback about the undo operation. + */ + //@@author A0138862W + public CommandResult undo(); + +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/commands/UnmarkCommand.java b/src/main/java/harmony/mastermind/logic/commands/UnmarkCommand.java new file mode 100644 index 000000000000..99376c512e59 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/UnmarkCommand.java @@ -0,0 +1,122 @@ +package harmony.mastermind.logic.commands; + +import harmony.mastermind.commons.core.Messages; +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.model.ModelManager; +import harmony.mastermind.model.task.ArchiveTaskList.TaskNotFoundException; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; + +//@@author A0124797R +public class UnmarkCommand extends Command implements Undoable, Redoable{ + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": undo marking of task as done" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + + COMMAND_WORD + + " 1"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " "; + public static final String COMMAND_DESCRIPTION = "Unmarking a task as done"; + + public static final String MESSAGE_UNMARK_SUCCESS = "%1$s has been unmarked"; + public static final String MESSAGE_DUPLICATE_UNMARK_TASK = "%1$s already exist in not completed list"; + public static final String MESSAGE_UNMARK_FAILURE = "Tasks in current tab has not been marked"; + + public static final String MESSAGE_UNDO_SUCCESS = "[Undo Unmark Command] %1$s has been archived"; + public static final String MESSAGE_REDO_SUCCESS = "[Redo Unmark Command] %1$s has been unmarked"; + + private final int targetIndex; + private Task taskToUnmark; + + public UnmarkCommand(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() { + try { + if (!this.model.getCurrentTab().equals(ModelManager.TAB_ARCHIVES)) { + return new CommandResult(COMMAND_WORD, UnmarkCommand.MESSAGE_UNMARK_FAILURE); + } + + executeUnmark(); + + model.pushToUndoHistory(this); + + model.clearRedoHistory(); + + requestHighlightLastActionedRow(taskToUnmark); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNMARK_SUCCESS, taskToUnmark)); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_DUPLICATE_UNMARK_TASK, taskToUnmark)); + } catch (TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + + //@@author A0138862W + @Override + /* + * Strategy to undo unmark command + * + * @see harmony.mastermind.logic.commands.Undoable#undo() + */ + public CommandResult undo() { + try { + // remove the task that's previously added. + model.markTask(taskToUnmark); + + model.pushToRedoHistory(this); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNDO_SUCCESS, taskToUnmark)); + } catch (UniqueTaskList.TaskNotFoundException e) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_TASK_NOT_IN_MASTERMIND); + } + } + + @Override + //@@author A0138862W + /* + * + * Strategy to redo unmark command + * + * @see harmony.mastermind.logic.commands.Redoable#redo() + */ + public CommandResult redo() { + try { + executeUnmark(); + + model.pushToUndoHistory(this); + + requestHighlightLastActionedRow(taskToUnmark); + + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_UNMARK_SUCCESS, taskToUnmark)); + } catch (DuplicateTaskException dte) { + return new CommandResult(COMMAND_WORD, String.format(MESSAGE_DUPLICATE_UNMARK_TASK, taskToUnmark)); + } catch (TaskNotFoundException tnfe) { + return new CommandResult(COMMAND_WORD, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + } + + //@@author A0124797R + private void executeUnmark() throws IndexOutOfBoundsException, TaskNotFoundException, + DuplicateTaskException { + UnmodifiableObservableList lastShownList = model.getFilteredArchiveList(); + + if (lastShownList.size() < targetIndex) { + indicateAttemptToExecuteIncorrectCommand(); + throw new TaskNotFoundException(); + } + + taskToUnmark = (Task) lastShownList.get(targetIndex - 1); + + model.unmarkTask(taskToUnmark); + } + +} diff --git a/src/main/java/harmony/mastermind/logic/commands/UpcomingCommand.java b/src/main/java/harmony/mastermind/logic/commands/UpcomingCommand.java new file mode 100644 index 000000000000..9c4b0f0c80ca --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/commands/UpcomingCommand.java @@ -0,0 +1,57 @@ +package harmony.mastermind.logic.commands; + +import java.util.regex.Pattern; + +//@@author A0124797R +/** + * Lists all upcoming tasks in the task manager to the user. + */ +public class UpcomingCommand extends Command { + + public static final String COMMAND_WORD = "upcoming"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": List upcoming deadlines and events.\n" + + "Parameters: TASKTYPE (Optional)\n" + + "Example: \n" + + COMMAND_WORD + "\n" + + COMMAND_WORD + " events\n" + + COMMAND_WORD + " deadlines"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + "[tab_name]"; + public static final String COMMAND_DESCRIPTION = "List tasks due in the week"; + + + public static final Pattern COMMAND_ARGUMENTS_PATTERN = Pattern.compile("(?deadlines|events)"); + + public final String TIME_TODAY = "today 2359"; + + public static final String MESSAGE_SUCCESS_UPCOMING = "Listed all upcoming tasks"; + public static final String MESSAGE_SUCCESS_UPCOMING_DEADLINE = "Listed all upcoming deadlines"; + public static final String MESSAGE_SUCCESS_UPCOMING_EVENT = "Listed all upcoming events"; + public static final String COMMAND_SUMMARY = "Upcoming tasks: \n" + COMMAND_WORD; + + private String taskType; + + public UpcomingCommand() {} + + public UpcomingCommand(String type) { + this.taskType = type; + } + + @Override + public CommandResult execute() { + model.updateFilteredListToShowUpcoming(prettyTimeParser.parse(TIME_TODAY).get(0).getTime(), taskType); + + switch (taskType) { + case "deadlines" : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING_DEADLINE); + case "events" : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING_EVENT); + default : + return new CommandResult(COMMAND_WORD, MESSAGE_SUCCESS_UPCOMING); + } + } + + +} diff --git a/src/main/java/harmony/mastermind/logic/parser/Parser.java b/src/main/java/harmony/mastermind/logic/parser/Parser.java new file mode 100644 index 000000000000..f173d5f26084 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/parser/Parser.java @@ -0,0 +1,597 @@ +package harmony.mastermind.logic.parser; + +import static harmony.mastermind.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static harmony.mastermind.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.io.IOException; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.ocpsoft.prettytime.nlp.PrettyTimeParser; +import org.ocpsoft.prettytime.nlp.parse.DateGroup; + +import com.google.common.base.Strings; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.commons.util.StringUtil; +import harmony.mastermind.logic.commands.HistoryCommand; +import harmony.mastermind.logic.commands.AddCommand; +import harmony.mastermind.logic.commands.AddCommandBuilder; +import harmony.mastermind.logic.commands.ClearCommand; +import harmony.mastermind.logic.commands.Command; +import harmony.mastermind.logic.commands.DeleteCommand; +import harmony.mastermind.logic.commands.EditCommand; +import harmony.mastermind.logic.commands.ExitCommand; +import harmony.mastermind.logic.commands.ExportCommand; +import harmony.mastermind.logic.commands.FindCommand; +import harmony.mastermind.logic.commands.FindTagCommand; +import harmony.mastermind.logic.commands.HelpCommand; +import harmony.mastermind.logic.commands.ImportCommand; +import harmony.mastermind.logic.commands.IncorrectCommand; +import harmony.mastermind.logic.commands.ListCommand; +import harmony.mastermind.logic.commands.MarkCommand; +import harmony.mastermind.logic.commands.RedoCommand; +import harmony.mastermind.logic.commands.RelocateCommand; +import harmony.mastermind.logic.commands.UndoCommand; +import harmony.mastermind.logic.commands.UnmarkCommand; +import harmony.mastermind.logic.commands.UpcomingCommand; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.model.tag.Tag; + +/** + * Parses user input. + */ +public class Parser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + private static final Pattern KEYWORDS_ARGS_FORMAT = Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); + + private static final Pattern TASK_INDEX_ARGS_FORMAT = Pattern.compile("(?.+)"); + private static final Pattern TAB_ARGS_FORMAT = Pattern.compile("(?home|tasks|events|deadlines|archives)"); + + private static final String TAB_ARCHIVES = "Archives"; + + public static Memory mem; + + public Parser() { + Memory memory = initializeMemory(); + } + + /** + * Parses user input into command for execution. + * + * @param userInput + * full user input string + * @return the command based on the user input + */ + public Command parseCommand(String userInput) { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.COMMAND_SUMMARY)); + } + + final String keyword = matcher.group("keyword"); + final String arguments = matcher.group("arguments"); + + switch (keyword) { + + case AddCommand.COMMAND_KEYWORD_ADD: // main command + case AddCommand.COMMAND_KEYWORD_DO: // alias (fall through) + return prepareAdd(arguments); + + case DeleteCommand.COMMAND_WORD: + return prepareDelete(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return prepareFind(arguments); + + case FindTagCommand.COMMAND_WORD: + return prepareFindTag(arguments); + + case ListCommand.COMMAND_WORD: + return prepareList(arguments); + + case UpcomingCommand.COMMAND_WORD: + return prepareUpcoming(arguments); + + case MarkCommand.COMMAND_WORD: + return prepareMark(arguments); + + case EditCommand.COMMAND_KEYWORD_EDIT: + case EditCommand.COMMAND_KEYWORD_UPDATE: + case EditCommand.COMMAND_KEYWORD_CHANGE: + return prepareEdit(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RelocateCommand.COMMAND_WORD: + return new RelocateCommand(arguments); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case UnmarkCommand.COMMAND_WORD: + return prepareUnmark(arguments); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case ImportCommand.COMMAND_WORD: + return prepareImport(arguments); + + case ExportCommand.COMMAND_KEYWORD_EXPORT: + return prepareExport(arguments); + + case HistoryCommand.COMMAND_KEYWORD_ACTIONHISTORY: + return new HistoryCommand(); + default: + return new IncorrectCommand(MESSAGE_UNKNOWN_COMMAND+": "+userInput); + } + } + + /** + * Parses arguments in the context of the add task command. + * + * @param args + * full command args string + * @return the prepared command + */ + // @@author A0138862W + private Command prepareAdd(String args) { + try { + + final Matcher matcher = AddCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate user command input + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_EXAMPLES)); + } + + // mandatory field + // catch escaped name group if exists, otherwise use unescaped name group + final String nameEscaped = matcher.group("nameEscaped"); + final String name = (nameEscaped != null)? nameEscaped: matcher.group("name"); + + // at this point name variable should never be null because the regex only capture full match of mandatory components + // check for bug in regex expression if the following throws assertion error + assert name != null; + + // optionals + final Optional dates = Optional.ofNullable(matcher.group("dates")); + final Optional tags = Optional.ofNullable(matcher.group("tags")); + final Optional recur = Optional.ofNullable(matcher.group("recur")); + + // return internal value if present. else, return empty string + final Set tagSet = getTagsFromArgs(tags.map(val -> val).orElse("")); + + // after init every capturing groups, we start to build the command + AddCommand addCommand = buildAddCommand(name, dates, recur, tagSet); + + return addCommand; + } catch (IllegalValueException | InvalidEventDateException e) { + return new IncorrectCommand(e.getMessage()); + } + } + + //@@author A0138862W + /** + * Build the AddCommand + * + * @param name is mandatory field + * @param dates contain user input date string that has to be parsed by natural language processing library. Optional + * @param recur contain recur input contain the keyword such as daily, weekly, monthly. Optional + * @param tagSet unique set of tag string associated to the task + * + * @throws IllegalValueException if tags contain non-alphanumeric value + * @throws InvalidEventDateException if event start date is after end date + */ + private AddCommand buildAddCommand(final String name, final Optional dates, final Optional recur, final Set tagSet) throws IllegalValueException, InvalidEventDateException { + AddCommandBuilder addCommandBuilder = new AddCommandBuilder(name); + addCommandBuilder.withTags(tagSet); + + if(dates.isPresent()){ + PrettyTimeParser ptp = new PrettyTimeParser(); + List parsedDates = ptp.parseSyntax(dates.get()); + + if(!parsedDates.isEmpty()){ + recur.ifPresent(recurVal -> addCommandBuilder.asRecurring(recurVal)); + List startEndDates = parsedDates.get(0).getDates(); + + /* + * We assume two conditions after parsing nlp dates: + * 1. Found only 1 date, then we assume it is a deadline + * 2. Found 2 dates, then we assume it is an event + */ + if(shouldParseAsDeadline(startEndDates)){ + addCommandBuilder.asDeadline(startEndDates.get(0)); + }else if(shouldParseAsEvent(startEndDates)){ + addCommandBuilder.asEvent(startEndDates.get(0), startEndDates.get(1)); + } + } + }; + return addCommandBuilder.build(); + } + + //@@author A0138862W + /* + * Determine the date should be parse as deadline task + */ + private boolean shouldParseAsDeadline(List dates){ + return dates.size() == 1; + } + + //@@author A0138862W + /* + * Determine the date should be parse as event task + */ + private boolean shouldParseAsEvent(List dates){ + return dates.size() == 2; + } + + //@@author A0138862W + /* + * Extract the source destination string and prepare the Import ICS command. + * + */ + private Command prepareImport(String args){ + final Matcher matcher = ImportCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } + + final String source = matcher.group("source"); + final String extension = matcher.group("extension"); + + assert source != null; + assert extension != null; + + return new ImportCommand(source, extension); + } + + /** + * Parses arguments in the context of the edit task command. + * + * @param args + * full command args string + * @return the prepared command + */ + // @@author A0138862W + private Command prepareEdit(String args) { + final Matcher matcher = EditCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + try { + + // mandatory + // regex accept only numbers in index field, encountering NumberFormatException is impossible + final int index = Integer.parseInt(matcher.group("index")); + + //optional + final Optional recur = Optional.ofNullable(matcher.group("recur")); + final Optional name = Optional.ofNullable(matcher.group("name")); + final Optional startDate = Optional.ofNullable(matcher.group("startDate")); + final Optional endDate = Optional.ofNullable(matcher.group("endDate")); + final Optional tags = Optional.ofNullable(matcher.group("tags")); + + Optional> tagSet = Optional.empty(); + if(tags.isPresent()){ + tagSet = Optional.ofNullable(getTagsFromArgs(tags.get())); + }; + + return new EditCommand(index, name, startDate, endDate, tagSet, recur); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } catch (ParseException pe) { + return new IncorrectCommand(pe.getMessage()); + } + + } + + private Command prepareExport(String args){ + final Matcher matcher = ExportCommand.COMMAND_ARGUMENTS_PATTERN.matcher(args.trim()); + + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_EXAMPLE)); + } + + try { + // mandatory + final String destination = matcher.group("destination"); + + // at this point destination variable should never be null because the regex only capture full match of mandatory components + // check for bug in regex expression if the following throws assertion error + assert destination != null; + + // capture all matched string if present + final Optional tasks = Optional.ofNullable(matcher.group("tasks")); + final Optional deadlines = Optional.ofNullable(matcher.group("deadlines")); + final Optional events = Optional.ofNullable(matcher.group("events")); + final Optional archives = Optional.ofNullable(matcher.group("archives")); + + if(isExportingAll(tasks, deadlines, events, archives)){ + return new ExportCommand(destination, true, true, true, true); + }else{ + boolean isExportingTasks = tasks.isPresent(); + boolean isExportingDeadlines = deadlines.isPresent(); + boolean isExportingEvents = events.isPresent(); + boolean isExportingArchives = archives.isPresent(); + + return new ExportCommand(destination, isExportingTasks, isExportingDeadlines, isExportingEvents, isExportingArchives); + } + } catch (IOException e) { + return new IncorrectCommand(e.getMessage()); + } + } + + /* + * This method return true if user did not specify any categories to export, + * then we assume user wants to export all categories. + * eg: export to C:\User\Jim\Workspace\mastermind.csv + */ + private boolean isExportingAll(Optional tasks, Optional deadlines, Optional events, Optional archives){ + return tasks.isPresent() == false && + deadlines.isPresent() == false && + events.isPresent() == false && + archives.isPresent() == false; + } + + // @@author + + /** + * Extracts the new task's tags from the add command's tag arguments string. + * Merges duplicate tag strings. + */ + private static Set getTagsFromArgs(String tagArguments) throws IllegalValueException { + // no tags + if (Strings.isNullOrEmpty(tagArguments)) { + return Collections.emptySet(); + } + // replace first delimiter prefix, then split + final Collection tagStrings = Arrays.asList(tagArguments.split(",")); + return new HashSet<>(tagStrings); + } + + /** + * Parses arguments in the context of the delete task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareDelete(String args) { + + Optional index = parseIndex(args); + if (!index.isPresent()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + Command result = new DeleteCommand(index.get(), mem); + + return result; + } + + //@@author A0124797R + /** + * Parses arguments in the context of the mark task command.
+ * + * @return the prepared mark command + */ + private Command prepareMark(String args) { + Optional index = parseIndex(args); + if (!index.isPresent()) { + if (args.trim().equals("due")) { + return new MarkCommand(args.trim()); + } + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + } + return new MarkCommand(index.get()); + } + + //@@author A0124797R + /** + * Parses arguments in the context of the list task command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareList(String args) { + Optional type = parseTab(args.toLowerCase()); + if (type.isPresent()) { + if (type.get().equals("empty")) { + return new ListCommand(); + } else { + return new ListCommand(type); + } + } else { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + } + + + //@@author A0124797R + /** + * Parses arguments in the context of the unmark task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareUnmark(String args) { + Optional index = parseIndex(args); + if (!index.isPresent()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + } + return new UnmarkCommand(index.get()); + } + + //@@author generated + /** + * Returns the specified index in the {@code command} IF a positive unsigned + * integer is given as the index. Returns an {@code Optional.empty()} + * otherwise. + */ + private Optional parseIndex(String command) { + final Matcher matcher = TASK_INDEX_ARGS_FORMAT.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String index = matcher.group("targetIndex"); + if (!StringUtil.isUnsignedInteger(index)) { + return Optional.empty(); + } + return Optional.of(Integer.parseInt(index)); + + } + + + //@@author A0124797R + /** + * Returns the specified Tab name in the {@code ListCommand} + * IF a correct Tab name is given. + * Returns an {@code Optional.empty()} otherwise. + */ + private Optional parseTab(String command) { + if (command.isEmpty()) { + return Optional.of("empty"); + } + + final Matcher matcher = TAB_ARGS_FORMAT.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String type = matcher.group("tab").toLowerCase(); + + return Optional.of(type); + + } + + //@@author generated + /** + * Parses arguments in the context of the find task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareFind(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new FindCommand(keywordSet, mem); + } + + + //@@author A0124797R + /** + * Parses arguments in the context of the upcoming task command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareUpcoming(String args) { + + Optional taskType = parseUpcoming(args); + + if (taskType.isPresent()) { + return new UpcomingCommand(taskType.get()); + } else { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UpcomingCommand.MESSAGE_USAGE)); + } + + } + + //@@author A0124797R + /** + * Returns the specified arguments for {@code UpcomingCommand} + * IF a correct arguments is given. + */ + private Optional parseUpcoming(String command) { + if (command.isEmpty()) { + return Optional.of("empty"); + } + + final Matcher matcher = UpcomingCommand.COMMAND_ARGUMENTS_PATTERN.matcher(command.trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + + String type = matcher.group("taskType").toLowerCase(); + + return Optional.of(type); + + } + + /** + * Parses arguments in the context of the find tag command. + * + * @param args + * full command args string + * @return the prepared command + */ + private Command prepareFindTag(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + + final Set tagSet = new HashSet<>(); + + try { + + for (String tagName : keywords) { + tagSet.add(new Tag(tagName)); + } + + return new FindTagCommand(tagSet); + } catch (IllegalValueException ive) { + return new IncorrectCommand(ive.getMessage()); + } + } + + //@@author A0143378Y + private Memory initializeMemory() { + Memory memory = new Memory(); + mem = memory; + memory.loadFromFile(memory); + return memory; + } +} diff --git a/src/main/java/harmony/mastermind/logic/parser/ParserMemoryMain.java b/src/main/java/harmony/mastermind/logic/parser/ParserMemoryMain.java new file mode 100644 index 000000000000..6d5057266264 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/parser/ParserMemoryMain.java @@ -0,0 +1,426 @@ +package harmony.mastermind.logic.parser; + +import java.util.Calendar; + +public class ParserMemoryMain { + + private static final String STRING_AND = "and"; + private static final String STRING_ON = "on"; + private static final String STRING_FROM = "from"; + private static final String STRING_TO = "to"; + private static final String STRING_BETWEEN = "between"; + private static final String STRING_BEFORE = "before"; + private static final String STRING_TILL = "till"; + private static final String STRING_UNTIL = "until"; + private static final String STRING_BY = "by"; + private static final String STRING_DEADLINE = "deadline"; + + //Commands for start date + protected static final int BETWEEN = 13; + protected static final int ON = 12; + protected static final int FROM = 11; + + //Commands for end date + protected static final int AND = 7; + protected static final int TILL = 6; + protected static final int TO = 5; + protected static final int BEFORE = 4; + protected static final int UNTIL = 3; + protected static final int BY = 2; + protected static final int DEADLINE = 1; + + public static String command; + protected static String taskName; + protected static String description; + + protected static final int INVALID_INT = -1; + + protected static int type; + protected static int length; + + public static boolean containsDescription; + public static boolean setProper; + + private static int day; + private static int month; + private static int year; + + private static final int INT_INVALID = -1; + private static final int INVALID_STRING = 0; + + private static final String INVALID_DATE_TIME = "Invalid date/time: "; + private static final String INVALID_COMMAND = "Invalid command, please try again"; + + //@@author A0143378Y + /* + * General getters and setters + */ + public static String getCommand() { + return command; + } + + //@@author A0143378Y + public static void setCommand(String newCommand) { + command = newCommand; + } + + //@@author A0143378Y + public static void setTaskName(String newName) { + taskName = newName; + } + + //@@author A0143378Y + public static String getTaskName() { + return taskName; + } + + //@@author A0143378Y + public static void setDescription(String newDescription) { + description = newDescription; + } + + //@@author A0143378Y + public static String getDescription() { + return description; + } + + //@@author A0143378Y + public static void setLength(int newLength) { + length = newLength; + } + + //@@author A0143378Y + public static int getLength() { + return length; + } + + //@@author A0143378Y + public static void setType(int newType) { + type = newType; + } + + //@@author A0143378Y + public static int getType() { + return type; + } + + //@@author A0143378Y + public static void setContainsDescription(boolean cd) { + containsDescription = cd; + } + + //@@author A0143378Y + public static void setProper(boolean sp) { + setProper = sp; + } + + //@@author A0143378Y + /* + * Set date to a calendar object setEvent + * If set, return true. + */ + public static boolean setDate(String date, Calendar setEvent) { + boolean isValid = false; + + initialiseDate(); + getDate(date); + + isValid = setDateIfContainDDMMYY(day, month, year, setEvent); + + return isValid; + } + + //@@author A0143378Y + public static boolean setTime(String time, Calendar setEvent) { + int newTime = INT_INVALID; + int hour = 23, minute = 59; + boolean isValid = false; + + String timeReduced = reduceToInt(time); + newTime = convertToInt(timeReduced); + + //Checks that time string has exactly 4 digit. + if(timeReduced.length() == 4 && newTime != INVALID_INT){ + minute = newTime%100; + hour = newTime/100; + } + + //Checks that the time set is valid + if(!(invalidMinute(minute)||invalidHour(hour)||timeReduced.length()!=4)){ + isValid = setTimeIfHHMMSS(hour, minute, setEvent); + } + + return isValid; + } + + //@@author A0143378Y + /* + * Check the format the date is in + * dd/mm/yy or dd-mm-yy and parse accordingly + */ + protected static void getDate(String date){ + + if(date.contains("/")){ + getInt("/", date); + + }else if(date.contains("-")){ + getInt("-", date); + + } + } + + //@@author A0143378Y + /* + * Parse the date string with the symbol "/" or "-" + */ + protected static void getInt(String symbol, String date){ + + String[] details = date.split(symbol); + + boolean dateIsNumeric = true; + + //Check that date has all 3 component: day, month and year + if(details.length == 3){ + dateIsNumeric = checkIfDateIsNumeric(details); + } + + if(dateIsNumeric){ + setDDMMYY(details); + } + } + + //@@author A0143378Y + /* + * Returns true if user command is an empty string or contains symbols only + */ + public static Boolean isUselessCommand(String input){ + + if(reduceToIntAndChar(input).length()==0){ + return true; + }else{ + return false; + } + } + + //@@author A0143378Y + /* + * Returns true if end date and time is before start date and time + */ + protected static Boolean endIsBeforeStart(Calendar start, Calendar end){ + return end.before(start); + } + + //@@author A0143378Y + /* + * Checks if string contains number only + * Returns true if it does + */ + protected static boolean isNumeric(String temp){ + try{ + Integer.parseInt(temp); + }catch(NumberFormatException e){ + return false; + } + return true; + } + + //@@author A0143378Y + /* + * Prints error message and set setProper as false + */ + protected static void generalError(){ + System.out.println(INVALID_COMMAND); + setProper(false); + } + + //@@author A0143378Y + protected static String removeAllInt(String name){ + return name.replaceAll("[0-9 ]", ""); + } + + //@@author A0143378Y + public static String reduceToInt(String name){ + return name.replaceAll("[^0-9]", ""); + } + + //@@author A0143378Y + public static String reduceToIntAndChar(String name){ + return name.replaceAll("[^a-zA-Z0-9]", ""); + } + + //@@author A0143378Y + protected static String reduceToChar(String name){ + return name.replaceAll("[^a-zA-Z]", ""); + } + + //@@author A0143378Y + /* + * set dates + * If day and month are appropriate, returns true + */ + private static boolean setDateIfContainDDMMYY(int day, int mth, int yr, Calendar setEvent){ + int year = 2000 + yr; + int month = mth -1; + if(!(invalidMonth(month)||invalidDay(day))){ + setEvent.set(Calendar.DATE, day); + setEvent.set(Calendar.MONTH, month); + setEvent.set(Calendar.YEAR, year); + return true; + }else{ + return false; + } + } + + //@@author A0143378Y + /* + * Converts string to integer + * If string is empty or contains non-number, print error message. + */ + private static int convertToInt(String value){ + int i = INVALID_INT; + try{ + i= Integer.parseInt(value); + }catch(NumberFormatException e){ + System.err.println(INVALID_DATE_TIME+ e.getMessage()); + } + return i; + } + + //@@author A0143378Y + /* + * Month is invalid if + * Month is less than 0 + * Month is greater than 11 + */ + private static boolean invalidMonth(int month){ + return (month<0||month>11); + } + + //@@author A0143378Y + /* + * returns true is day is 0 or greater than 31 + */ + private static boolean invalidDay(int day){ + return (day<=0||day>31); + } + + //@@author A0143378Y + /* + * returns true if time is set properly + */ + private static boolean setTimeIfHHMMSS(int hour, int minute, Calendar setEvent){ + setEvent.set(Calendar.HOUR_OF_DAY, hour); + setEvent.set(Calendar.MINUTE, minute); + if(hour == 23 && minute == 59){ + setEvent.set(Calendar.SECOND, 59); + }else{ + setEvent.set(Calendar.SECOND, 0); + } + return true; + } + + //@@author A0143378Y + /* + * returns true if minute is negative or more than 59 + */ + private static boolean invalidMinute(int minute){ + return (minute<0||minute>=60); + } + + //@@author A0143378Y + /* + * returns true if hour is negative or more than 23 + */ + private static boolean invalidHour(int hour){ + return (hour<0||hour>=24); + } + + //@@author A0143378Y + /* + * Check that the word is a command word for date + */ + protected static int isCommandWord(String word){ + switch(word){ + case STRING_DEADLINE: + return DEADLINE; + case STRING_BY: + return BY; + case STRING_UNTIL: + return UNTIL; + case STRING_TILL: + return TILL; + case STRING_BEFORE: + return BEFORE; + case STRING_BETWEEN: + return BETWEEN; + case STRING_TO: + return TO; + case STRING_FROM: + return FROM; + case STRING_ON: + return ON; + case STRING_AND: + return AND; + default: + return INVALID_STRING; + } + } + + //@@author A0143378Y + /* + * remove additional space between each word in case of typo + */ + protected static void removeAdditionalSpacesInCommand(){ + String[] temp = command.split(" "); + String newCommand = ""; + for (int i = 0; i < temp.length; i++){ + if(temp[i].length() != 0){ + newCommand = newCommand + temp[i] + " "; + } + } + setCommand(newCommand.trim()); + } + + //@@author A0143378Y + /* + * Returns true if the command word is a command for start date + */ + protected static boolean isStartCommand(String word){ + return (isCommandWord(word)>= FROM && + isCommandWord(word)<= BETWEEN); + } + + //@@author A0143378Y + /* + * Returns true if the command word is a command for end date + */ + protected static boolean isEndCommand(String word){ + return (isCommandWord(word)>= BY && + isCommandWord(word)<= AND); + } + + //author A0143378Y + private static void setDDMMYY(String[] details){ + day = Integer.parseInt(details[0]); + month = Integer.parseInt(details[1]); + year = Integer.parseInt(details[2]); + } + + //@@author A0143378Y + private static void initialiseDate(){ + day = INT_INVALID; + month = INT_INVALID; + year = INT_INVALID; + } + + //@@author A0143378Y + private static boolean checkIfDateIsNumeric(String[] details){ + for(int i = 0; i < 3; i++ ){ + if(!isNumeric(details[i])){ + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/logic/parser/ParserSearch.java b/src/main/java/harmony/mastermind/logic/parser/ParserSearch.java new file mode 100644 index 000000000000..67ddb001a053 --- /dev/null +++ b/src/main/java/harmony/mastermind/logic/parser/ParserSearch.java @@ -0,0 +1,156 @@ +package harmony.mastermind.logic.parser; + +/* + * Parser for search + * Takes in command from GUI.Main, and call appropriate method in search + * + */ + + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import harmony.mastermind.logic.commands.*; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; + +public class ParserSearch extends ParserMemoryMain { + + private static final String SEARCHING_FOR_TERM = "Searching for term"; + + private static final String SEARCH_DATE_RESULT = "Search Date Result"; + + private static final String ERROR_ITEM_NOT_FOUND = "No items found!"; + private static final String ERROR_SEARCH_FOR_DATE = "Item with date found."; + private static final String ERROR_INVALID_DATE = "Error with date set. Please try again"; + private static final String ERROR_INVALID_COMMAND = "You may only search for term(s) or items contain a date"; + + private static final int SEARCH_TERM = 21; + private static final int SEARCH_DATE = 22; + + private static Calendar dateTime; + + //@author A0143378Y + public static void run(String command, Memory memory){ + initVar(); + setCommand(command); + search(memory); + + } + + //@author A0143378Y + /* + * Initializing variables + */ + public static void initVar(){ + dateTime = new GregorianCalendar(); + setType(-1); + setLength(-1); + setProper(true); + } + + //@author A0143378Y + /* + * Set command type + */ + private static void setSearchType(String[] details) { + + if(command.contains("date")&&(details.length == 2)){ + setType(SEARCH_DATE); + }else if(details.length >= 1 && command.trim().length() !=0){ + setType(SEARCH_TERM); + } + } + + //@author A0143378Y + /* + * Takes in terms from user, parse and send as String array to method search + */ + private static void searchTerm(Memory memory) { + String[] toSearch = command.split(", "); + FindCommand.searchTerms(toSearch, memory); + } + + //@author A0143378Y + /* + * Takes in date from user, check that date is set in variable dateTime + * If date is not in proper form or invalid, return an error message to user + * Else calls searchDate to search for item with the date + */ + private static void searchDate(String[] details, Memory memory) { + setProper(setDate(details[1], dateTime)); + if(setProper) { + assert setProper == true; + searchDate(memory); + } + } + + //@author A0143378Y + /* + * Takes in command and memory + * Set command type and call method corresponding to the command. + * If command is invalid, generate error message. + * + */ + private static void search(Memory memory){ + String[] details = command.split(" "); + + setSearchType(details); + runCodeIfCommandIsValid(memory, details); + + } + + //@author A0143378Y + /* + * Check if command is valid + * If command is invalid, generate error message. + * If command is valid, run search + */ + private static void runCodeIfCommandIsValid(Memory memory, String[] details) { + switch (type) { + case SEARCH_TERM: + searchTerm(memory); + break; + + case SEARCH_DATE: + searchDate(details, memory); + break; + + } + } + + //@author A0143378Y + /* + * Takes in an arraylist, message to be printed if search is successful, and the type of result being displayed + * If list is empty, error message is printed + * If list has exactly one item, displayDetailed is called (display an item) + * If list has more than one item, then displayList is called (display a list) + */ + private static String displayResult(ArrayList result, String reportSuccess, String type){ + if (result.size() == 0) { + return ERROR_ITEM_NOT_FOUND; + } else if (result.size() == 1) {; + return ListCommand.displayDetailed(result.get(0)); + } else { + assert result.size() >1; + return ListCommand.displayList(result, type); + } + } + + //@author A0143378Y + /* + * With the dateTime variable set in searchDate, method calls findDate to search for item with the + * date. + * This means if it is an event, then the date will fall after the start date and before the end date + * If it is a one day event or a deadline, then it will return such item if the date is exactly the + * start date of the event or end date of the deadline. + * Items are stored in an arraylist and displayed + * + */ + private static void searchDate(Memory memory) { + ArrayList findResult = FindCommand.findDate(dateTime, memory); + displayResult(findResult, ERROR_SEARCH_FOR_DATE, SEARCH_DATE_RESULT); + + } +} diff --git a/src/main/java/harmony/mastermind/memory/GenericMemory.java b/src/main/java/harmony/mastermind/memory/GenericMemory.java new file mode 100644 index 000000000000..0263358af00c --- /dev/null +++ b/src/main/java/harmony/mastermind/memory/GenericMemory.java @@ -0,0 +1,505 @@ +package harmony.mastermind.memory; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class GenericMemory implements Comparable { + private static final String TYPE_STRING = "Type: "; + private static final String NAME_STRING = "\nName: "; + private static final String DESCRIPTION_STRING = "\nDescription : "; + private static final String START_STRING = "\nStart: "; + private static final String END_STRING = "\nEnd: "; + private static final String DUE_BY = "\nDue by: "; + private static final String STATUS_INCOMPLETE = "\nStatus: Incomplete"; + private static final String STATUS_COMPLETED = "\nStatus: Completed"; + private static final String STATUS_OVERDUE = "\nStatus: Overdue"; + private static final String STATUS_UPCOMING = "\nStatus: Upcoming"; + private static final String STATUS_OVER = "\nStatus: Over"; + private static final String STATUS_ONGOING = "\nStatus: Ongoing"; + private static final String PM = "PM"; + private static final String AM = "AM"; + private static final String SUN = "Sun"; + private static final String SAT = "Sat"; + private static final String FRI = "Fri"; + private static final String THURS = "Thurs"; + private static final String WED = "Wed"; + private static final String TUES = "Tues"; + private static final String MON = "Mon"; + private static final String EVENT = "Event"; + private static final String DEADLINE = "Deadline"; + private static final String TASK = "Task"; + + private static final String ONGOING = "Ongoing"; + private static final String OVER = "Over"; + private static final String UPCOMING = "Upcoming"; + private static final String OVERDUE = "Overdue"; + private static final String COMPLETED = "Completed"; + private static final String INCOMPLETE = "Incomplete"; + + private String type; + private String name; + private String description; + private Calendar start; + private Calendar end; + private int state; + + // State + public static final int INT_OVERDUE = 2; + public static final int INT_ONGOING = 2; + public static final int INT_COMPLETED = 1; + public static final int INT_OVER = 1; + public static final int INT_INCOMPLETE = 0; + public static final int INT_UPCOMING = 0; + + //@@author A0143378 + /* + * Setting up tasks + */ + public GenericMemory(String type, String name, String description) { + this.type = type; + this.name = name; + this.description = description; + this.state = 0; + } + + //@@author A0143378Y + /* + * Setting up deadlines + */ + public GenericMemory(String type, String name, String description, Calendar end) { + this.type = type; + this.name = name; + this.description = description; + this.end = end; + this.state = 0; + } + + //@@author A0143378Y + /* + * Events and constructor used to load from storage + */ + public GenericMemory(String type, String name, String description, Calendar startDate, Calendar end, int state) { + this.type = type; + this.name = name; + this.description = description; + this.start = startDate; + this.end = end; + this.state = state; + } + + //@@author A0143378Y + /* + * Returns type of the to do item + */ + public String getType() { + return type; + } + + //@@author A0143378Y + /* + * Returns name of the to do item + */ + public String getName() { + return name; + } + + //@@author A0143378Y + /* + * Returns description of the to do item + */ + public String getDescription() { + return description; + } + + //@@author A0143378Y + /* + * Returns Calendar start of the to do item + */ + public Calendar getStart() { + return start; + } + + //@@author A0143378Y + /* + * Returns Calendar end of the to do item + */ + public Calendar getEnd() { + return end; + } + + //@@author A0143378Y + /* + * Returns the state of the to do item + */ + public int getState() { + return state; + } + + //@@author A0143378Y + /* + * Initializes start calendar - having a real calendar instead of hard coding everything + */ + public void initStart(){ + start = new GregorianCalendar(); + } + + //@@author A0143378Y + /* + * Initializes end calendar + */ + public void initEnd(){ + end = new GregorianCalendar(); + } + + //@@author A0143378Y + /* + * Set type of to do item + */ + public void setType(String type) { + this.type = type; + } + + //@@author A0143378Y + /* + * Set name of to do item + */ + public void setName(String name) { + this.name = name; + } + + //@@author A0143378Y + /* + * Set description of to do item + */ + public void setDescription(String description) { + this.description = description; + } + + //@@author A0143378Y + /* + * Set start time of to do item using hours and minutes + */ + public void setStartTime(int hourOfDay, int minute) { + start.set(Calendar.HOUR_OF_DAY, hourOfDay); + start.set(Calendar.MINUTE, minute); + } + + //@@author A0143378Y + /* + * Set start time of to do item using hours, minutes and seconds + */ + public void setStartTime(int hourOfDay, int minute, int second) { + start.set(Calendar.HOUR_OF_DAY, hourOfDay); + start.set(Calendar.MINUTE, minute); + start.set(Calendar.SECOND, second); + } + + //@@author A0143378Y + /* + * Set end time of to do item using hours and minutes + */ + public void setEndTime(int hourOfDay, int minute) { + end.set(Calendar.HOUR_OF_DAY, hourOfDay); + end.set(Calendar.MINUTE, minute); + } + + //@@author A0143378Y + /* + * Set end time of to do item using hours, minutes and seconds + */ + public void setEndTime(int hourOfDay, int minute, int second) { + end.set(Calendar.HOUR_OF_DAY, hourOfDay); + end.set(Calendar.MINUTE, minute); + end.set(Calendar.SECOND, second); + } + + //@@author A0143378Y + /* + * Set start date of to do item + */ + public void setStartDate(int year, int month, int date) { + start.set(Calendar.YEAR, year); + start.set(Calendar.MONTH, month); + start.set(Calendar.DATE, date); + } + + //@@author A0143378Y + /* + * Set end date of to do item + */ + public void setEndDate(int year, int month, int date) { + end.set(Calendar.YEAR, year); + end.set(Calendar.MONTH, month); + end.set(Calendar.DATE, date); + } + + //@@author A0143378Y + /* + * Set state of to do item + */ + public void setState(int newstate) { + this.state = newstate; + } + + //@@author A0143378Y + /* Converts GenericEvents object into string representation + * Outputs in the format + * Name + * Description + * Start + * End + * State + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String output = TYPE_STRING + getType() + + NAME_STRING + getName(); + output = descriptionToString(output); + + if (getType().equals(TASK)) { // Task + output = taskDeadlineStateToString(output); + return output; + } + + if (getType().equals(DEADLINE)) { // Deadline + output = deadlineDateToString(output); + output = taskDeadlineStateToString(output); + return output; + } + + if (getType().equals(EVENT)) { // Event + output = eventDatesToString(output); + output = eventStateToString(output); + return output; + } + return output; + } + + //@@author A0143378Y + /* + * Converts description into string representation + */ + public String descriptionToString(String output) { + if (description != null) { // If description exists + output += DESCRIPTION_STRING + getDescription(); + } + return output; + } + + //@@author A0143378Y + /* + * Converts due date into string representation + */ + public String deadlineDateToString(String output) { + if (end != null) { + output += DUE_BY + getDate(end) + " " + getTime(end); + } + return output; + } + + //@@author A0143378Y + /* + * Converts event start and end dates into string representation + */ + public String eventDatesToString(String output) { + if (start != null) { + output += START_STRING + getDate(start) + " " + getTime(start); + } + if (end != null) { + output += END_STRING + getDate(end) + " " + getTime(end); + } + return output; + } + + //@@author A0143378Y + /* + * Converts event state into string representation + */ + public String eventStateToString(String output) { + if (getState() == 0) { // Printing of state into string + output+= STATUS_UPCOMING; + } else if (getState() == 1) { + output+= STATUS_OVER; + } else { + output+= STATUS_ONGOING; + } + return output; + } + + //@@author A0143378Y + /* + * Converts task or deadline state into string representation + */ + public String taskDeadlineStateToString(String output) { + if (getState() == 0) { // Printing of state into string + output+= STATUS_INCOMPLETE; + } else if (getState() == 1) { + output+= STATUS_COMPLETED; + } else { + output+= STATUS_OVERDUE; + } + return output; + } + + //@@author A0143378Y + /* + * Returns string representation of date as DD/MM/YY + */ + public static String getDate(Calendar item){ + if(item != null){ + return item.get(Calendar.DATE) + "/" + + (item.get(Calendar.MONTH) + 1) + "/" + + (item.get(Calendar.YEAR)%100) + " " + + dayOfTheWeek(item); + }else{ + return null; + } + } + + //@@author A0143378Y + /* + * Returns string representation of time as HH:MM AM/PM + */ + public static String getTime(Calendar item){ + if(item != null){ + return hour(item) + ":" + + min(item) + " " + + AM_PM(item); + }else{ + return null; + } + } + + //@@author A0143378Y + /* + * Return string representation of day of the week of calendar object + */ + public static String dayOfTheWeek(Calendar item){ + int dayOfTheWeek = item.get(Calendar.DAY_OF_WEEK); + + switch(dayOfTheWeek){ + case Calendar.MONDAY: + return MON; + + case Calendar.TUESDAY: + return TUES; + + case Calendar.WEDNESDAY: + return WED; + + case Calendar.THURSDAY: + return THURS; + + case Calendar.FRIDAY: + return FRI; + + case Calendar.SATURDAY: + return SAT; + + case Calendar.SUNDAY: + return SUN; + } + return null; + } + + //@@author A0143378Y + /* + * Check item for AM/PM and return the correct time period + */ + public static String AM_PM(Calendar item){ + if(item.get(Calendar.AM_PM) == Calendar.AM){ + return AM; + }else{ + return PM; + } + } + + //@@author A0143378Y + /* + * Return the hour of time to form of HH + */ + public static String hour(Calendar item){ + if(item.get(Calendar.HOUR_OF_DAY)==12){ + return "12"; + }else if( item.get(Calendar.HOUR)<10){ + return "0" + item.get(Calendar.HOUR); + }else{ + return Integer.toString(item.get(Calendar.HOUR)); + } + } + + //@@author A0143378Y + /* + * Return minute of time to form of MM + */ + public static String min(Calendar item){ + if( item.get(Calendar.MINUTE)<10){ + return "0" + item.get(Calendar.MINUTE); + }else{ + return Integer.toString(item.get(Calendar.MINUTE)); + } + } + + //@@author A0143378Y + /* Comparator between to do item and o + * Only valid when comparing items of the same type + * eg. Task vs Task, Deadline vs Deadline, Event vs Event + * Task: Compare names lexicographically, ignoring case differences -> alphabetical order + * Deadline: Compare due dates + * Event: Compare start dates, else end dates + */ + public int compareTo(GenericMemory o) { + if (this.start == null && this.end == null && o.start == null && o.end == null){ //both task + return this.name.compareToIgnoreCase(o.name); + } + + if (this.start == null && this.end != null && o.start == null && o.end != null){ //both deadline + return this.end.compareTo(o.end); + } + + if (this.start != null && this.end != null && o.start != null && o.end != null){ //both event + return eventCompare(o); + } + return 0; + } + + //@@author A0143378Y + /* + * Compare's start date followed by end dates + */ + int eventCompare(GenericMemory o) { + if (this.start.compareTo(o.start) != 0) { + return this.start.compareTo(o.start); + } else { + return this.end.compareTo(o.end); + } + } + + //@@author A0143378Y + /* + * Return state of the item in the form of a string + */ + public String getStateType(){ + + if(type.equals(DEADLINE)|| type.equals(TASK)){ + if(state == INT_INCOMPLETE){ + return INCOMPLETE; + }else if (state == INT_COMPLETED){ + return COMPLETED; + }else if (state == INT_OVERDUE){ + return OVERDUE; + } + } + if(type.equals(EVENT)){ + if(state == INT_UPCOMING){ + return UPCOMING; + }else if(state == INT_OVER){ + return OVER; + }else if (state == INT_ONGOING){ + return ONGOING; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/memory/Memory.java b/src/main/java/harmony/mastermind/memory/Memory.java new file mode 100644 index 000000000000..956761688c74 --- /dev/null +++ b/src/main/java/harmony/mastermind/memory/Memory.java @@ -0,0 +1,79 @@ +package harmony.mastermind.memory; + +import java.util.ArrayList; + +import harmony.mastermind.storage.StorageMemory; + + +public class Memory{ + public ArrayList memory = new ArrayList(); + + //@@author A0143378Y + /* + * Adds a given GenericMemory item into the memory ArrayList + * Updates quick view and saves to file + */ + public void add(GenericMemory item){ + assert item != null; + + String type = item.getType(); + + memory.add(item); + StorageMemory.saveToStorage(this); + } + + //@@author A0143378Y + /* + * Returns GenericMemory object at index + */ + public GenericMemory get(int index){ + return memory.get(index); + } + + //@@author A0143378Y + /* + * Returns ArrayList of GenericMemory used by memory + */ + public ArrayList getList(){ + return memory; + } + + //@@author A0143378Y + /* + * Returns number of GenericMemory in memory (size) + */ + public int getSize(){ + return memory.size(); + } + + //@@author A0143378Y + /* + * Loads GenericMemory from save file into memory + */ + public void loadFromFile(Memory memory){ + assert memory != null; + StorageMemory.checkForFileExists(memory); + } + + //@@author A0143378Y + /* + * Removes given item from memory + */ + public void remove(GenericMemory item){ + assert item != null; + + String type = item.getType(); + + memory.remove(item); + StorageMemory.saveToStorage(this); + } + + //@@author A0143378Y + /* + * Swap out ArrayList used by memory with new given list + */ + public void setList(ArrayList list){ + memory = list; + } + +} diff --git a/src/main/java/harmony/mastermind/model/Model.java b/src/main/java/harmony/mastermind/model/Model.java new file mode 100644 index 000000000000..0e802bd1f455 --- /dev/null +++ b/src/main/java/harmony/mastermind/model/Model.java @@ -0,0 +1,146 @@ +package harmony.mastermind.model; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.EmptyStackException; +import java.util.Set; + +import javafx.collections.ObservableList; + +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.exceptions.NotRecurringTaskException; +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.logic.commands.Redoable; +import harmony.mastermind.logic.commands.Undoable; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; + +/** + * The API of the Model component. + */ +public interface Model { + /** Clears existing backing model and replaces with the provided new data. */ + void resetData(ReadOnlyTaskManager newData); + + /** Returns the TaskManager*/ + ReadOnlyTaskManager getTaskManager(); + + /** Deletes the given task. */ + void deleteTask(ReadOnlyTask target) throws UniqueTaskList.TaskNotFoundException; + + //@@author A0124797R + /** Deletes the given Archived Task */ + void deleteArchive(ReadOnlyTask target) throws TaskNotFoundException, ArchiveTaskList.TaskNotFoundException; + + //@@author + /** Adds the given task */ + void addTask(Task task) throws UniqueTaskList.DuplicateTaskException; + + //@@author A0124797R + /** Add the next recurring task */ + void addNextTask(Task task) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException; + + //@@author A0124797R + /** Marks the given task as done */ + void markTask(Task target) throws UniqueTaskList.TaskNotFoundException; + + /** Marks the given List of due tasks as done */ + void markDue(ArrayList targets) throws UniqueTaskList.TaskNotFoundException; + + //@@author A0124797R + /** Updates the completed task as not done */ + void unmarkTask(Task target) throws UniqueTaskList.DuplicateTaskException, + ArchiveTaskList.TaskNotFoundException; + + //@@author A0139194X + /** Relocates save location to given directory */ + void relocateSaveLocation(String directory) throws FolderDoesNotExistException; + + + /** push the command to undo history */ + void pushToUndoHistory(Undoable command); + + //@@author A0138862W + /** undo last action performed, throws EmptyStackException is there's no more action can be undone **/ + CommandResult undo() throws EmptyStackException; + + //@@author A0124797R + /** Returns the current list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getCurrentList(); + + //@@author A0138862W + /** push the command to redo history */ + void pushToRedoHistory(Redoable command); + + //@@author A0138862W + /** undo last action performed, throws EmptyStackException is there's no more action can be undone **/ + CommandResult redo() throws EmptyStackException; + + /** empty redoHistory **/ + // required when a new command is entered, model should throw away all remaining commands in the redo history + void clearRedoHistory(); + + /** Returns the filtered task list as an {@code UnmodifiableObservableList} */ + //@@author generated + UnmodifiableObservableList getFilteredTaskList(); + + //@@author A0124797R + /** Returns the filtered floating task list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredFloatingTaskList(); + + /** Returns the filtered event list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredEventList(); + + /** Returns the filtered deadline list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredDeadlineList(); + + /** Returns the filtered archive list as an {@code UnmodifiableObservableList} */ + UnmodifiableObservableList getFilteredArchiveList(); + + //@@author A0138862W + /** Returns filtered task list as an {@code ObervableList} */ + ObservableList getListToMark(); + + //@@author A0124797R + /** update current tab to the specified tab*/ + void updateCurrentTab(String tab); + + /** Updates the filter of the filtered task list for current tab to show all tasks */ + void updateFilteredListToShowAll(); + + /** Updates the filter of the filtered task list for specified tab to show all tasks */ + void updateFilteredListToShow(String tab); + + /** Updates the filter of the filtered task list + * for Home tab to show all upcoming tasks */ + void updateFilteredListToShowUpcoming(long time, String taskType); + + //@@author + /** Updates the filter of the filtered task list to filter by the given keywords*/ + void updateFilteredList(Set keywords); + + /** Updates the filter of the filtered task list to filter by the given tag keywords*/ + void updateFilteredTagTaskList(Set keywords); + + //@@author A0124797R + String getCurrentTab(); + + /** get the filtered list size for the current tab */ + int getCurrentListSize(); + + //@@author A0138862W + /** Search */ + void searchTask(String input); + + //@@author A0124797R + /** reads the file indicated */ + BufferedReader importFile(String fileToImport) throws FileNotFoundException; + + +} diff --git a/src/main/java/harmony/mastermind/model/ModelManager.java b/src/main/java/harmony/mastermind/model/ModelManager.java new file mode 100644 index 000000000000..609744e7bdee --- /dev/null +++ b/src/main/java/harmony/mastermind/model/ModelManager.java @@ -0,0 +1,641 @@ +package harmony.mastermind.model; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EmptyStackException; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.ComponentManager; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.core.UnmodifiableObservableList; +import harmony.mastermind.commons.events.model.TaskManagerChangedEvent; +import harmony.mastermind.commons.events.storage.RelocateFilePathEvent; +import harmony.mastermind.commons.events.ui.TabChangedEvent; +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.exceptions.NotRecurringTaskException; +import harmony.mastermind.commons.util.StringUtil; +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.logic.commands.Redoable; +import harmony.mastermind.logic.commands.Undoable; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +// @@author A0124797R +/** + * 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 int FAIL = -1; + private final TaskManager taskManager; + private final FilteredList filteredTasks; + private final FilteredList filteredFloatingTasks; + private final FilteredList filteredEvents; + private final FilteredList filteredDeadlines; + private final FilteredList filteredArchives; + private final Stack undoHistory; + private final Stack redoHistory; + private final Stack commandHistory; + + public static final String TAB_HOME = "Home"; + public static final String TAB_TASKS = "Tasks"; + public static final String TAB_EVENTS = "Events"; + public static final String TAB_DEADLINES = "Deadlines"; + public static final String TAB_ARCHIVES = "Archives"; + + private String currentTab; + + /** + * Initializes a ModelManager with the given TaskManager TaskManager and its + * variables should not be null + */ + public ModelManager(TaskManager src, UserPrefs userPrefs) { + super(); + assert src != null; + assert userPrefs != null; + + logger.fine("Initializing with task manager: " + + src + + " and user prefs " + + userPrefs); + + taskManager = new TaskManager(src); + filteredTasks = new FilteredList<>(taskManager.getTasks()); + filteredFloatingTasks = new FilteredList<>(taskManager.getFloatingTasks()); + filteredEvents = new FilteredList<>(taskManager.getEvents()); + filteredDeadlines = new FilteredList<>(taskManager.getDeadlines()); + filteredArchives = new FilteredList<>(taskManager.getArchives()); + undoHistory = new Stack<>(); + redoHistory = new Stack<>(); + commandHistory = new Stack(); + currentTab = TAB_HOME; + } + + //@@author + public ModelManager() { + this(new TaskManager(), new UserPrefs()); + } + + // @@author A0124797R + public ModelManager(ReadOnlyTaskManager initialData, UserPrefs userPrefs) { + taskManager = new TaskManager(initialData); + filteredTasks = new FilteredList<>(taskManager.getTasks()); + filteredFloatingTasks = new FilteredList<>(taskManager.getFloatingTasks()); + filteredEvents = new FilteredList<>(taskManager.getEvents()); + filteredDeadlines = new FilteredList<>(taskManager.getDeadlines()); + filteredArchives = new FilteredList<>(taskManager.getArchives()); + undoHistory = new Stack<>(); + redoHistory = new Stack<>(); + commandHistory = new Stack(); + indicateTaskManagerChanged(); + currentTab = TAB_HOME; + } + + //@@author + @Override + public void resetData(ReadOnlyTaskManager newData) { + taskManager.resetData(newData); + clearUndoHistory(); + clearRedoHistory(); + indicateTaskManagerChanged(); + } + + @Override + public ReadOnlyTaskManager getTaskManager() { + return taskManager; + } + + /** Raises an event to indicate the model has changed */ + private void indicateTaskManagerChanged() { + raise(new TaskManagerChangedEvent(taskManager)); + } + + // @@author A0124797R + @Override + /** update current tab to the specified tab*/ + public void updateCurrentTab(String tab) { + this.currentTab = tab; + } + + // @@author A0138862W + @Override + /* + * push the command to the undo history. + * @see harmony.mastermind.model.Model#pushToUndoHistory(harmony.mastermind.logic.commands.Undoable) + */ + public void pushToUndoHistory(Undoable command) { + undoHistory.push(command); + } + + // @@author A0138862W + @Override + /** undo last action performed **/ + public CommandResult undo() throws EmptyStackException { + CommandResult commandResult = undoHistory.pop().undo(); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + return commandResult; + } + + @Override + // @@author A0138862W + /* + * push the command to the redo history. Should only be called after an undo operation + * @see harmony.mastermind.model.Model#pushToRedoHistory(harmony.mastermind.logic.commands.Redoable) + */ + public void pushToRedoHistory(Redoable command) { + redoHistory.push(command); + } + + @Override + // @@author A0138862W + /** redo the action that being undone function **/ + public CommandResult redo() throws EmptyStackException { + CommandResult commandResult = redoHistory.pop().redo(); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + return commandResult; + } + + @Override + /** + * This method should only be called when the user entered a new command + * other than redo/undo + **/ + // @@author A0138862W + public void clearRedoHistory() { + redoHistory.clear(); + } + + // @@author A0139194X + /** + * This method should only be called when the user entered a new command + * other than redo/undo + **/ + public void clearUndoHistory() { + undoHistory.clear(); + } + + @Override + public synchronized void deleteTask(ReadOnlyTask target) throws TaskNotFoundException { + taskManager.removeTask(target); + indicateTaskManagerChanged(); + } + + // @@author A0124797R + @Override + /** Deletes the given archived Task */ + public synchronized void deleteArchive(ReadOnlyTask target) throws TaskNotFoundException, ArchiveTaskList.TaskNotFoundException { + taskManager.removeArchive(target); + indicateTaskManagerChanged(); + } + + //@@author + @Override + public synchronized void addTask(Task task) throws UniqueTaskList.DuplicateTaskException { + taskManager.addTask(task); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + } + + // @@author A0124797R + @Override + public synchronized void markTask(Task target) throws TaskNotFoundException { + taskManager.markTask(target); + indicateTaskManagerChanged(); + } + + @Override + public synchronized void markDue(ArrayList targets) throws TaskNotFoundException { + for (Task t: targets) { + taskManager.markTask(t); + } + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + } + + @Override + public synchronized void unmarkTask(Task target) throws ArchiveTaskList.TaskNotFoundException, UniqueTaskList.DuplicateTaskException { + taskManager.unmarkTask(target); + indicateTaskManagerChanged(); + } + + @Override + public String getCurrentTab(){ + return this.currentTab; + } + + + // @@author A0124797R + //=========== Filtered List Accessors =============================================================== + + @Override + public UnmodifiableObservableList getFilteredFloatingTaskList() { + return new UnmodifiableObservableList<>(filteredFloatingTasks); + } + + @Override + public UnmodifiableObservableList getFilteredEventList() { + return new UnmodifiableObservableList<>(filteredEvents); + } + + @Override + public UnmodifiableObservableList getFilteredDeadlineList() { + return new UnmodifiableObservableList<>(filteredDeadlines); + } + + @Override + public UnmodifiableObservableList getFilteredArchiveList() { + return new UnmodifiableObservableList<>(filteredArchives); + } + + // =========== Methods for file access ================================ + + // @@author A0124797R-unused + //remove the use of importing txt file + @Override + public synchronized BufferedReader importFile(String fileToImport) throws FileNotFoundException { + BufferedReader br = new BufferedReader(new FileReader(fileToImport)); + + return br; + } + + // @@author: A0139194X + @Override + public synchronized void relocateSaveLocation(String newFilePath) throws FolderDoesNotExistException { + raise(new RelocateFilePathEvent(newFilePath)); + indicateTaskManagerChanged(); + } + + + // =========== Methods for Recurring Tasks============================= + + // @@author A0124797R + @Override + public synchronized void addNextTask(Task task) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException { + taskManager.addNextTask(task); + updateFilteredListToShowAll(); + indicateTaskManagerChanged(); + } + + //@@author + // =========== Filtered Task List Accessors =============================================================== + + @Override + public UnmodifiableObservableList getFilteredTaskList() { + return new UnmodifiableObservableList<>(filteredTasks); + } + + // @@author A0124797R + @Override + public ObservableList getListToMark() { + return getCurrentObservableList(); + } + + // @@author A0124797R + @Override + public void updateFilteredListToShow(String tab) { + switch (tab) { + case TAB_HOME: + currentTab = TAB_HOME; + break; + case TAB_TASKS: + currentTab = TAB_TASKS; + break; + case TAB_EVENTS: + currentTab = TAB_EVENTS; + break; + case TAB_DEADLINES: + currentTab = TAB_DEADLINES; + break; + case TAB_ARCHIVES: + currentTab = TAB_ARCHIVES; + break; + } + updateFilteredListToShowAll(); + } + + // @@author A0124797R + @Override + public void updateFilteredListToShowAll() { + switch (currentTab) { + case TAB_HOME: + filteredTasks.setPredicate(null); + break; + case TAB_TASKS: + filteredFloatingTasks.setPredicate(null); + break; + case TAB_EVENTS: + filteredEvents.setPredicate(null); + break; + case TAB_DEADLINES: + filteredDeadlines.setPredicate(null); + break; + case TAB_ARCHIVES: + filteredArchives.setPredicate(null); + break; + } + } + + @Override + public int getCurrentListSize() { + switch (currentTab) { + case TAB_HOME: + return filteredTasks.size(); + case TAB_TASKS: + return filteredFloatingTasks.size(); + case TAB_EVENTS: + return filteredEvents.size(); + case TAB_DEADLINES: + return filteredDeadlines.size(); + case TAB_ARCHIVES: + return filteredArchives.size(); + default: + //should not reach here + return FAIL; + } + } + + @Override + public void updateFilteredListToShowUpcoming(long time, String taskType) { + updateFilteredList(new PredicateExpression(new DateQualifier(time, taskType))); + } + + //@@author generated + @Override + public void updateFilteredList(Set keywords) { + updateFilteredList(new PredicateExpression(new NameQualifier(keywords))); + indicateTaskManagerChanged(); + } + + // @@author A0124797R + @Override + public void updateFilteredTagTaskList(Set keywords) { + updateFilteredList(new PredicateExpression(new TagQualifier(keywords))); + } + + /** + * update filtered list of specific tabs + * @param expression + */ + private void updateFilteredList(Expression expression) { + switch (currentTab) { + case TAB_HOME: + filteredTasks.setPredicate(expression::satisfies); + break; + case TAB_TASKS: + filteredFloatingTasks.setPredicate(expression::satisfies); + break; + case TAB_EVENTS: + filteredEvents.setPredicate(expression::satisfies); + break; + case TAB_DEADLINES: + filteredDeadlines.setPredicate(expression::satisfies); + break; + case TAB_ARCHIVES: + filteredArchives.setPredicate(expression::satisfies); + break; + } + } + + // @@author A0124797R + /** + * Returns an ObservableList of the filtered tasks in current Tab + */ + private ObservableList getCurrentObservableList() { + ObservableList list = filteredTasks; + + switch (currentTab) { + case "Home": + list = filteredTasks; + break; + case "Tasks": + list = filteredFloatingTasks; + break; + case "Events": + list = filteredEvents; + break; + case "Deadlines": + list = filteredDeadlines; + break; + case "Archives": + list = filteredArchives; + break; + } + + return list; + } + + // @@author A0124797R + @Override + /** Returns an UnmodifiableObservableList of filtered tasks in current Tab */ + public UnmodifiableObservableList getCurrentList() { + return new UnmodifiableObservableList(getCurrentObservableList()); + } + + //@@author + private void searchTask(String keyword, Memory memory) { + taskManager.searchTask(keyword, memory); + } + + // ========== Inner classes/interfaces used for filtering ================================================== + + interface Expression { + boolean satisfies(ReadOnlyTask task); + + String toString(); + } + + private class PredicateExpression implements Expression { + + private final Qualifier qualifier; + + PredicateExpression(Qualifier qualifier) { + this.qualifier = qualifier; + } + + @Override + public boolean satisfies(ReadOnlyTask task) { + return qualifier.run(task); + } + + @Override + public String toString() { + return qualifier.toString(); + } + } + + interface Qualifier { + boolean run(ReadOnlyTask task); + + String toString(); + } + + private class NameQualifier implements Qualifier { + private Set nameKeyWords; + + NameQualifier(Set nameKeyWords) { + this.nameKeyWords = nameKeyWords; + } + + @Override + public boolean run(ReadOnlyTask task) { + return nameKeyWords.stream().filter(keyword -> StringUtil.containsIgnoreCase(task.getName(), keyword)).findAny().isPresent(); + } + + @Override + public String toString() { + return "name=" + + String.join(", ", nameKeyWords); + } + } + + // @@author A0124797R + /** + * used as a qualifier to filter tasks by tags + */ + private class TagQualifier implements Qualifier { + private Set tagKeyWords; + + TagQualifier(Set tagKeyWords) { + this.tagKeyWords = tagKeyWords; + } + + @Override + public boolean run(ReadOnlyTask task) { + final Set tagList = task.getTags().toSet(); + + return !Collections.disjoint(tagList, tagKeyWords); + } + + @Override + public String toString() { + return "tags=" + + String.join(", ", tagKeyWords.toString()); + } + } + + /** + * used as a qualifier to filter dates to use in {@code UpcomingCommand} + */ + private class DateQualifier implements Qualifier { + private final String TYPE_DEADLINE = "deadlines"; + private final String TYPE_EVENT = "events"; + private final String TYPE_DUE = "due"; + + private long timeNow; + private long oneWeekFromNow; + private final long oneWeek = 604800000; + private String taskType; + + DateQualifier(long time, String taskType) { + this.timeNow = new Date().getTime(); + this.oneWeekFromNow = time + oneWeek; + this.taskType = taskType; + } + + @Override + public boolean run(ReadOnlyTask task) { + switch (taskType) { + case TYPE_DEADLINE: + return isUpcomingDeadline(task); + case TYPE_EVENT: + return isUpcomingEvent(task); + case TYPE_DUE: + return isTaskDue(task); + default: + return isUpcomingAll(task); + } + } + + @Override + public String toString() { + return "Date before:" + + oneWeekFromNow; + } + + private boolean isUpcomingAll(ReadOnlyTask task) { + if (task.isFloating()) { + return true; + } else { + return isUpcoming(task); + } + } + + private boolean isUpcomingEvent(ReadOnlyTask task) { + if (task.isFloating() || task.isDeadline()) { + return false; + } else { + return isUpcoming(task); + } + } + + private boolean isUpcomingDeadline(ReadOnlyTask task) { + if (task.isFloating() || task.isEvent()) { + return false; + } else { + return isUpcoming(task); + } + } + + private boolean isTaskDue(ReadOnlyTask task) { + if (task.isFloating()) { + return false; + } else { + return isDue(task); + } + } + + /** + * Checks if end date of task is within one week from now. + */ + private boolean isUpcoming(ReadOnlyTask task) { + long taskTime = task.getEndDate().getTime(); + boolean isUpcoming = taskTime < oneWeekFromNow && taskTime > timeNow; + return isUpcoming; + } + + /** + * Checks if tasks has already past + */ + private boolean isDue(ReadOnlyTask task) { + long taskTime = task.getEndDate().getTime(); + return taskTime < timeNow; + } + } + + + /** + * handle changing of tabs when using specific commands + * @param event + */ + @Subscribe + private void handleTabChangedEvent(TabChangedEvent event){ + this.updateCurrentTab(event.toTabId); + } + + //@@author + @Override + public void searchTask(String input) { + // implementing next milestone + } + + +} diff --git a/src/main/java/harmony/mastermind/model/ReadOnlyTaskManager.java b/src/main/java/harmony/mastermind/model/ReadOnlyTaskManager.java new file mode 100644 index 000000000000..95678924cfae --- /dev/null +++ b/src/main/java/harmony/mastermind/model/ReadOnlyTaskManager.java @@ -0,0 +1,53 @@ +package harmony.mastermind.model; + + +import java.util.List; + + +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.UniqueTaskList; + +//@@author A0124797R +/** + * Unmodifiable view of an task manager + * + */ +public interface ReadOnlyTaskManager { + + UniqueTagList getUniqueTagList(); + + UniqueTaskList getUniqueTaskList(); + + UniqueTaskList getUniqueFloatingTaskList(); + + UniqueTaskList getUniqueEventList(); + + UniqueTaskList getUniqueDeadlineList(); + + ArchiveTaskList getUniqueArchiveList(); + + /** Returns an unmodifiable view of tasks list */ + List getTaskList(); + + /** Returns an unmodifiable view of tasks list */ + List getFloatingTaskList(); + + /** Returns an unmodifiable view of tasks list*/ + List getEventList(); + + /** Returns an unmodifiable view of tasks list */ + List getDeadlineList(); + + /** Returns an unmodifiable view of archive list */ + List getArchiveList(); + + /** Returns an unmodifiable view of tags list */ + List getTagList(); + + + + +} diff --git a/src/main/java/harmony/mastermind/model/TaskManager.java b/src/main/java/harmony/mastermind/model/TaskManager.java new file mode 100644 index 000000000000..3a02e85064f1 --- /dev/null +++ b/src/main/java/harmony/mastermind/model/TaskManager.java @@ -0,0 +1,479 @@ +package harmony.mastermind.model; + +import javafx.collections.ObservableList; + +import java.util.*; +import java.util.stream.Collectors; + +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.exceptions.NotRecurringTaskException; +import harmony.mastermind.logic.parser.ParserSearch; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.TaskListComparator; +import harmony.mastermind.model.task.UniqueTaskList; +import harmony.mastermind.model.task.UniqueTaskList.DuplicateTaskException; +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; + +//@@author A0124797R +/** + * Wraps all data at the task-manager level + * Duplicates are not allowed (by .equals comparison) + */ +public class TaskManager implements ReadOnlyTaskManager { + private static final int INDEX_RECURRENCE_KEYWORD = 0; + private static final int INDEX_RECURRENCE_AMOUNT = 1; + + private static final int ONE_DAY = 1; + private static final int ONE_WEEK = 7; + private static final int ONE_MONTH = 1; + private static final int ONE_YEAR = 1; + + private final UniqueTaskList tasks; + private final UniqueTaskList floatingTasks; + private final UniqueTaskList events; + private final UniqueTaskList deadlines; + private final ArchiveTaskList archives; + private final UniqueTagList tags; + private final TaskListComparator comparator; + + { + tasks = new UniqueTaskList(); + floatingTasks = new UniqueTaskList(); + events = new UniqueTaskList(); + deadlines = new UniqueTaskList(); + archives = new ArchiveTaskList(); + tags = new UniqueTagList(); + comparator = new TaskListComparator(); + } + + public TaskManager() {} + + /** + * Tasks and Tags are copied into this TaskManager + */ + public TaskManager(ReadOnlyTaskManager toBeCopied) { + this(toBeCopied.getUniqueTaskList(), toBeCopied.getUniqueFloatingTaskList(), + toBeCopied.getUniqueEventList(), toBeCopied.getUniqueDeadlineList(), + toBeCopied.getUniqueTagList(), toBeCopied.getUniqueArchiveList()); + } + + /** + * Tasks and Tags are copied into this TaskManager + */ + public TaskManager(UniqueTaskList tasks, UniqueTaskList floatingTasks, UniqueTaskList events, UniqueTaskList deadlines, UniqueTagList tags, ArchiveTaskList archiveTasks) { + resetData(tasks.getInternalList(), floatingTasks.getInternalList(), events.getInternalList(), + deadlines.getInternalList(), tags.getInternalList(), archiveTasks.getInternalList()); + } + + //@@author + public static ReadOnlyTaskManager getEmptyTaskManager() { + return new TaskManager(); + } + + //list overwrite operations + + public ObservableList getTasks() { + return tasks.getInternalList(); + } + + //@@author A0124797R + /** returns an {@code ObservableList} of floating tasks*/ + public ObservableList getFloatingTasks() { + return floatingTasks.getInternalList(); + } + + /** returns an {@code ObservableList} of events*/ + public ObservableList getEvents() { + return events.getInternalList(); + } + + /** returns an {@code ObservableList} of deadlines*/ + public ObservableList getDeadlines() { + return deadlines.getInternalList(); + } + + /** returns an {@code ObservableList} of archives*/ + public ObservableList getArchives() { + return archives.getInternalList(); + } + + //@@author generated + public void setTasks(List tasks) { + this.tasks.getInternalList().sort(comparator); + this.tasks.getInternalList().setAll(tasks); + } + + //@@author A0124797R + public void setFloatingTasks(List floatingTasks) { + this.floatingTasks.getInternalList().setAll(floatingTasks); + } + + public void setEvents(List events) { + this.events.getInternalList().setAll(events); + } + + public void setDeadlines(List deadlines) { + this.deadlines.getInternalList().setAll(deadlines); + } + + public void setArchiveTasks(Collection archiveTasks) { + this.archives.getInternalList().setAll(archiveTasks); + } + + //@@author generated + public void setTags(Collection tags) { + this.tags.getInternalList().setAll(tags); + } + + //@@author A0124797R + public void resetData(Collection newTasks, + Collection newFloatingTasks, + Collection newEvents, + Collection newDeadlines, + Collection newTags, + Collection newArchiveTasks) { + + setTasks(newTasks.stream().map(Task::new).collect(Collectors.toList())); + setFloatingTasks(newFloatingTasks.stream().map(Task::new).collect(Collectors.toList())); + setEvents(newEvents.stream().map(Task::new).collect(Collectors.toList())); + setDeadlines(newDeadlines.stream().map(Task::new).collect(Collectors.toList())); + setTags(newTags); + setArchiveTasks(newArchiveTasks.stream().map(Task::new).map(Task::mark).collect(Collectors.toList())); + } + + public void resetData(ReadOnlyTaskManager newData) { + resetData(newData.getTaskList(), newData.getFloatingTaskList(), newData.getEventList(), + newData.getDeadlineList(), newData.getTagList(), newData.getArchiveList()); + } + + + +//// task-level operations + + /** + * Adds a task to the task manager and synchronize with all the tabs. + * Also checks the new task's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the task to point to those in {@link #tags}. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent task already exists. + */ + public void addTask(Task t) throws UniqueTaskList.DuplicateTaskException { + syncTagsWithMasterList(t); + tasks.add(t); + syncAddTask(t); + this.getUniqueTaskList().getInternalList().sort(comparator); + + } + + //@@author A0124797R + /** + * Adds the next recurring task to the task manager. + * Also checks the new task's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the task to point to those in {@link #tags}. + * + * @throws UniqueTaskList.DuplicateTaskException if an equivalent task already exists. + */ + public void addNextTask(Task t) throws UniqueTaskList.DuplicateTaskException, NotRecurringTaskException { + syncTagsWithMasterList(t); + Task newT = getNextTask(t); + tasks.add(newT); + syncAddTask(newT); + EventsCenter.getInstance().post(new HighlightLastActionedRowRequestEvent(newT)); + } + + + //@@author A0124797R + /** + * returns a Task with the next recurring date given a recurring Task + * @throws NotRecurringTaskException + */ + public Task getNextTask(Task t) throws NotRecurringTaskException { + if (t.isFloating() || t.getRecur() == null){ + throw new NotRecurringTaskException(); + } + + Task newT = null; + String[] recurVal = t.getRecur().split(" "); + String nextRecur = getNextRecur(t.getRecur()); + Date nextEndDate = getNextDate(t.getEndDate(),recurVal[INDEX_RECURRENCE_KEYWORD]); + + if (t.isDeadline()) { + newT = new Task(t.getName(), nextEndDate, t.getTags(), nextRecur, new Date()); + }else if (t.isEvent()) { + Date nextStartDate = getNextDate(t.getStartDate(), recurVal[INDEX_RECURRENCE_KEYWORD]); + newT = new Task(t.getName(), nextStartDate, nextEndDate, t.getTags(), nextRecur, new Date()); + } + + return newT; + + } + + //@@author A0124797R + /** + * returns the next date based on the type of recurring task + */ + private Date getNextDate(Date d, String recur) { + Calendar c = Calendar.getInstance(); + c.setTime(d); + int date; + switch (recur) { + case "daily" : + date = c.get(Calendar.DATE); + c.set(Calendar.DATE, date + ONE_DAY); + break; + case "weekly" : + date = c.get(Calendar.DATE); + c.set(Calendar.DATE, date + ONE_WEEK); + break; + case "monthly" : + date = c.get(Calendar.MONTH); + c.set(Calendar.MONTH, date + ONE_MONTH); + break; + case "yearly" : + date = c.get(Calendar.YEAR); + c.set(Calendar.YEAR, date + ONE_YEAR); + break; + default : + assert false; + } + + return c.getTime(); + } + + //@@author A0124797R + /** + * returns the next date based on the type of recurring task + */ + private String getNextRecur(String recur) { + String[] recurArr = recur.split(" "); + if (recurArr.length==1) { + return recur; + }else { + int counter = Integer.parseInt(recurArr[INDEX_RECURRENCE_AMOUNT]); + + if (counter>2) { + return recurArr[0] + " " + Integer.toString(counter-1); + } else { + return null; + } + } + } + + //@@author + /** + * Ensures that every tag in this task: + * - exists in the master list {@link #tags} + * - points to a Tag object in the master list + */ + private void syncTagsWithMasterList(Task task) { + final UniqueTagList taskTags = task.getTags(); + tags.mergeFrom(taskTags); + + // Create map with values = tag object references in the master list + final Map masterTagObjects = new HashMap<>(); + for (Tag tag : tags) { + masterTagObjects.put(tag, tag); + } + + // Rebuild the list of task tags using references from the master list + final Set commonTagReferences = new HashSet<>(); + for (Tag tag : taskTags) { + commonTagReferences.add(masterTagObjects.get(tag)); + } + task.setTags(new UniqueTagList(commonTagReferences)); + } + + public boolean removeTask(ReadOnlyTask key) throws UniqueTaskList.TaskNotFoundException { + if (tasks.remove(key)) { + this.getUniqueTaskList().getInternalList().sort(comparator); + syncRemoveTask(key); + return true; + } else { + throw new UniqueTaskList.TaskNotFoundException(); + } + } + + //@@author A0124797R + /** + * Removes an archived task as indicated + */ + public boolean removeArchive(ReadOnlyTask key) throws ArchiveTaskList.TaskNotFoundException { + if (archives.remove(key)) { + return true; + } else { + throw new ArchiveTaskList.TaskNotFoundException(); + } + } + + //@@author A0124797R + /** + * marks task as completed by + * removing the task from tasks and adds into archivedtasks + */ + public boolean markTask(Task key) throws UniqueTaskList.TaskNotFoundException { + if (tasks.remove(key)) { + archives.add(key.mark()); + this.getUniqueTaskList().getInternalList().sort(comparator); + syncRemoveTask(key); + return true; + } else { + throw new UniqueTaskList.TaskNotFoundException(); + } + } + + //@@author A0124797R + /** + * marks task as not completed by + * removing the task from archivedTasks and adds into tasks + */ + public boolean unmarkTask(Task key) throws DuplicateTaskException, ArchiveTaskList.TaskNotFoundException { + if (archives.remove(key)) { + tasks.add(key.unmark()); + this.getUniqueTaskList().getInternalList().sort(comparator); + syncAddTask(key.unmark()); + return true; + } else { + throw new ArchiveTaskList.TaskNotFoundException(); + } + } + + //@@author A0124797R + /** + * Synchronize adding of tasks across the tabs + */ + private void syncAddTask(Task task) throws DuplicateTaskException{ + if (task.isFloating()) { + floatingTasks.add(task); + } else if (task.isDeadline()) { + deadlines.add(task); + } else if (task.isEvent()) { + events.add(task); + } + } + + //@@author A0124797R + /** + * Synchronize removing of tasks across the tabs + */ + private void syncRemoveTask(ReadOnlyTask task) throws TaskNotFoundException{ + if (task.isFloating()) { + floatingTasks.remove(task); + } else if (task.isDeadline()) { + deadlines.remove(task); + } else if (task.isEvent()) { + events.remove(task); + } + } + + +//// tag-level operations + + //@@author + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } + +//// util methods + + @Override + public String toString() { + return tasks.getInternalList().size() + " tasks, " + tags.getInternalList().size() + " tags," + + archives.getInternalList().size(); + } + + @Override + public List getTaskList() { + return Collections.unmodifiableList(tasks.getInternalList()); + } + + //@@author A0124797R + @Override + public List getFloatingTaskList() { + return Collections.unmodifiableList(floatingTasks.getInternalList()); + } + + @Override + public List getEventList() { + return Collections.unmodifiableList(events.getInternalList()); + } + + @Override + public List getDeadlineList() { + return Collections.unmodifiableList(deadlines.getInternalList()); + } + + @Override + public List getArchiveList() { + return Collections.unmodifiableList(archives.getInternalList()); + } + + //@@author + @Override + public List getTagList() { + return Collections.unmodifiableList(tags.getInternalList()); + } + + @Override + public UniqueTaskList getUniqueTaskList() { + return this.tasks; + } + + //@@author A0124797R + @Override + public UniqueTaskList getUniqueFloatingTaskList() { + return this.floatingTasks; + } + + @Override + public UniqueTaskList getUniqueEventList() { + return this.events; + } + + @Override + public UniqueTaskList getUniqueDeadlineList() { + return this.deadlines; + } + + @Override + public ArchiveTaskList getUniqueArchiveList() { + return this.archives; + } + + //@@author + @Override + public UniqueTagList getUniqueTagList() { + return this.tags; + } + + //@@author A0124797R + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskManager // instanceof handles nulls + && this.tasks.equals(((TaskManager) other).tasks) + && this.floatingTasks.equals(((TaskManager) other).floatingTasks) + && this.events.equals(((TaskManager) other).events) + && this.deadlines.equals(((TaskManager) other).deadlines) + && this.tags.equals(((TaskManager) other).tags) + && this.archives.equals(((TaskManager) other).archives)); + } + + //@@author + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(tasks, tags); + } + + public void searchTask(String keyword, Memory memory) { + ParserSearch.run(keyword, memory); + + } + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/harmony/mastermind/model/UserPrefs.java similarity index 92% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/harmony/mastermind/model/UserPrefs.java index da9c8037f495..a68a91c96def 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/harmony/mastermind/model/UserPrefs.java @@ -1,9 +1,9 @@ -package seedu.address.model; - -import seedu.address.commons.core.GuiSettings; +package harmony.mastermind.model; import java.util.Objects; +import harmony.mastermind.commons.core.GuiSettings; + /** * Represents User's preferences. */ diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/harmony/mastermind/model/tag/Tag.java similarity index 90% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/harmony/mastermind/model/tag/Tag.java index 5bcffdb5ddf1..1f4f962f22a0 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/harmony/mastermind/model/tag/Tag.java @@ -1,10 +1,10 @@ -package seedu.address.model.tag; +package harmony.mastermind.model.tag; -import seedu.address.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.IllegalValueException; /** - * Represents a Tag in the address book. + * Represents a Tag in the task manager. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/harmony/mastermind/model/tag/UniqueTagList.java similarity index 89% rename from src/main/java/seedu/address/model/tag/UniqueTagList.java rename to src/main/java/harmony/mastermind/model/tag/UniqueTagList.java index 76fb7ff3dc5d..d086da7db9f7 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/harmony/mastermind/model/tag/UniqueTagList.java @@ -1,11 +1,13 @@ -package seedu.address.model.tag; +package harmony.mastermind.model.tag; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.DuplicateDataException; import java.util.*; +import java.util.stream.Collectors; + +import harmony.mastermind.commons.exceptions.DuplicateDataException; +import harmony.mastermind.commons.util.CollectionUtil; /** * A list of tags that enforces no nulls and uniqueness between its elements. @@ -140,4 +142,11 @@ public boolean equals(Object other) { public int hashCode() { return internalList.hashCode(); } + + @Override + //@@author A0138862W + public String toString(){ + // functional way to transform list of items into concatenated string joining with commas (JAVA 8) + return this.getInternalList().stream().map(Object::toString).collect(Collectors.joining(",")); + } } diff --git a/src/main/java/harmony/mastermind/model/task/ArchiveTaskList.java b/src/main/java/harmony/mastermind/model/task/ArchiveTaskList.java new file mode 100644 index 000000000000..612800bae1c2 --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/ArchiveTaskList.java @@ -0,0 +1,85 @@ +package harmony.mastermind.model.task; + +import java.util.Iterator; + +import harmony.mastermind.model.task.UniqueTaskList.TaskNotFoundException; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of completed tasks that does not allow nulls. + * + * Supports a minimal set of list operations. + */ +//@@author A0124797R +public class ArchiveTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + + /** + * Signals that an operation targeting a specified task in the list would fail because + * there is no such matching task in the list. + */ + public static class TaskNotFoundException extends Exception {} + + + /** + * Constructs empty ArchiveTaskList. + */ + public ArchiveTaskList() {} + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(ReadOnlyTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Adds an archived task to the list. + */ + public void add(Task toAdd) { + assert toAdd != null; + + internalList.add(toAdd); + } + + /** + * Removes the equivalent task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + public ObservableList getInternalList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ArchiveTaskList // instanceof handles nulls + && this.internalList.equals( + ((ArchiveTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} diff --git a/src/main/java/harmony/mastermind/model/task/ReadOnlyTask.java b/src/main/java/harmony/mastermind/model/task/ReadOnlyTask.java new file mode 100644 index 000000000000..07098161f503 --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/ReadOnlyTask.java @@ -0,0 +1,99 @@ +package harmony.mastermind.model.task; + +import java.time.Duration; +import java.util.Date; + +import harmony.mastermind.model.tag.UniqueTagList; + +//@@author A0124797R +public interface ReadOnlyTask { + //provide safe read, unmodifiable task object + final int INDEX_DAY = 0; + final int INDEX_MONTH = 1; + final int INDEX_DATE = 2; + final int INDEX_TIME = 3; + final int INDEX_YEAR = 5; + final int INDEX_HOUR = 0; + final int INDEX_MINUTE = 1; + + public String getName(); + public Date getStartDate(); + public Date getEndDate(); + public Date getCreatedDate(); + public UniqueTagList getTags(); + public String getRecur(); + + public boolean isMarked(); + public boolean isFloating(); + public boolean isDeadline(); + public boolean isEvent(); + public boolean isRecur(); + + public boolean isDue(); + public boolean isHappening(); + public Duration getDueDuration(); + public Duration getEventDuration(); + + default boolean isSameTask(ReadOnlyTask task) { + return task == this // short circuit if same object + || (this.toString().equals(task.toString())); // state check + } + + /** + * Formats the task as text, showing all task details. + */ + //@@author A0124797R + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + + if (getStartDate() != null) { + builder.append(" start:" + parseForConsole(getStartDate())); + } + + if (getEndDate() != null) { + builder.append(" end:" + parseForConsole(getEndDate())); + } + + if (!getTags().toString().isEmpty()) { + builder.append(" Tags: ") + .append(getTags().toString()); + } + + return builder.toString(); + } + + //@@author A0124797R + /** + * Formats the Date as text, showing Task's date. + */ + default String parse(Date date) { + String[] dateArr = date.toString().split(" "); + String[] timeArr = dateArr[INDEX_TIME].split(":"); + final StringBuilder builder = new StringBuilder(); + + builder.append(dateArr[INDEX_DAY] + " ").append(timeArr[INDEX_HOUR] + ":" + timeArr[INDEX_MINUTE] + " ") + .append(dateArr[INDEX_DATE] + " ").append(dateArr[INDEX_MONTH] + " ").append(dateArr[INDEX_YEAR]); + + return builder.toString(); + + } + + //@@author A0124797R + /** + * Formats the Date as text, showing Task's date. + */ + default String parseForConsole(Date date) { + String[] dateArr = date.toString().split(" "); + String[] timeArr = dateArr[INDEX_TIME].split(":"); + String year = dateArr[INDEX_YEAR].substring(2, 4); + final StringBuilder builder = new StringBuilder(); + + builder.append(timeArr[INDEX_HOUR] + ":" + timeArr[INDEX_MINUTE] + "|") + .append(dateArr[INDEX_DATE]).append(dateArr[INDEX_MONTH]).append(year); + + return builder.toString(); + + } + +} diff --git a/src/main/java/harmony/mastermind/model/task/Task.java b/src/main/java/harmony/mastermind/model/task/Task.java new file mode 100644 index 000000000000..e49bd679fb1b --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/Task.java @@ -0,0 +1,250 @@ +package harmony.mastermind.model.task; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Optional; + +import harmony.mastermind.commons.exceptions.DuplicateDataException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; + +public class Task implements ReadOnlyTask { + + private String name; + private Date startDate; + private Date endDate; + private Date createdDate; + private UniqueTagList tags; + private String recur; + private boolean isMarked; + + //@@author A0138862W + /* + * Initialize through taskBuilder (preferred way) + */ + protected Task(TaskBuilder taskBuilder){ + this.name = taskBuilder.getName(); + this.startDate = taskBuilder.getStartDate(); + this.endDate = taskBuilder.getEndDate(); + this.createdDate = taskBuilder.getCreatedDate(); + this.tags = taskBuilder.getTags(); + this.recur = taskBuilder.getRecur(); + this.isMarked = taskBuilder.isMarked(); + } + + // event + // @@author A0138862W + public Task(String name, Date startDate, Date endDate, UniqueTagList tags, String recur, Date createdDate) { + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + this.tags = tags; + this.isMarked = false; + this.recur = recur; + this.createdDate = createdDate; + } + + // deadline + // @@author A0138862W + public Task(String name, Date endDate, UniqueTagList tags, String recur, Date createdDate) { + this(name, null, endDate, tags, recur, createdDate); + } + + // floating + // @@author A0138862W + public Task(String name, UniqueTagList tags, Date createdDate) { + this(name, null, null, tags, null, createdDate); + } + + // @@author A0138862W + public Task(ReadOnlyTask source) { + this(source.getName(), source.getStartDate(), source.getEndDate(), source.getTags(), source.getRecur(), source.getCreatedDate()); + this.isMarked = source.isMarked(); + } + + @Override + // @@author generated + public String getName() { + return name; + } + + // @@author generated + public void setName(String name) { + this.name = name; + } + + @Override + // @@author generated + public UniqueTagList getTags() { + return tags; + } + + @Override + // @@author generated + public Date getStartDate() { + return startDate; + } + + // @@author generated + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + @Override + // @@author generated + public Date getEndDate() { + return endDate; + } + + // @@author generated + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + // @@author generated + public void setTags(UniqueTagList tags) { + this.tags = tags; + } + + @Override + //@@author A0124797R + public String getRecur() { + return this.recur; + } + + @Override + public boolean isRecur() { + return recur != null; + } + + @Override + // @@author A0138862W + public boolean isFloating() { + return startDate == null && endDate == null; + } + + @Override + // @@author A0138862W + public boolean isDeadline() { + return startDate == null && endDate != null; + } + + @Override + // @@author A0138862W + public boolean isEvent() { + return startDate != null && endDate != null; + } + + // @@author A0138862W + public Date getCreatedDate() { + return createdDate; + } + + //@@author A0124797R + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Task // instanceof handles nulls + && this.toString().equals(((Task) other).toString())); // state check + + } + + /** checks if task is marked */ + @Override + public boolean isMarked() { + return this.isMarked; + } + + //@@author A0124797R + /** set task as marked */ + public Task mark() { + this.isMarked = true; + return this; + } + + /** set task as not mark */ + public Task unmark() { + this.isMarked = false; + return this; + } + + @Override + public String toString() { + return getAsText(); + } + + //@@author A0138862W + /* + * + * Check if the current task is due. Applies to only deadline & event + * - deadline: true if and only if current date is after end date + * - event: true if and only if current date is after end date & start date + * + * @see harmony.mastermind.model.task.ReadOnlyTask#isDue() + */ + public boolean isDue(){ + Date now = new Date(); + if (isDeadline() && now.after(endDate)) { + return true; + } else if (isEvent() && now.after(startDate) && now.after(endDate)){ + return true; + } else { + return false; + } + } + + //@@author A0138862W + /* + * + * Check if current task is happening at the moment. + * Only applies to event, where the current date falls between start & end date + * + * @see harmony.mastermind.model.task.ReadOnlyTask#isHappening() + */ + public boolean isHappening(){ + Date now = new Date(); + if (isEvent() && now.after(startDate) && now.before(endDate)) { + return true; + } else { + return false; + } + } + + // @@author A0138862W + /* + * + * Calculate the duration of event. + * Applies to only event, return null otherwise. + * + * @see harmony.mastermind.model.task.ReadOnlyTask#getEventDuration() + */ + public Duration getEventDuration(){ + if(isEvent()){ + long differencel = endDate.getTime() - startDate.getTime(); + + return Duration.of(differencel, ChronoUnit.MILLIS); + }else{ + return null; + } + } + + //@@author A0138862W + /* + * calculate the duration until due date + * Applies to only deadlines, return null otherwise. + * + */ + public Duration getDueDuration(){ + if(endDate != null){ + long nowl = System.currentTimeMillis(); + long endDatel = endDate.getTime(); + + long differencel = endDatel - nowl; + + return Duration.of(differencel, ChronoUnit.MILLIS); + }else{ + return null; + } + } +} diff --git a/src/main/java/harmony/mastermind/model/task/TaskBuilder.java b/src/main/java/harmony/mastermind/model/task/TaskBuilder.java new file mode 100644 index 000000000000..a98866d5882a --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/TaskBuilder.java @@ -0,0 +1,170 @@ +package harmony.mastermind.model.task; + + +//@@author A0138862W +import java.util.Date; +import java.util.Set; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.exceptions.InvalidEventDateException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.tag.UniqueTagList.DuplicateTagException; + +/** + * The TaskBuilder is a safe way to create a task object instead of relying on constructors. + * The Task has numerous attributes which depends on the nature of the task (event, floating, deadline), + * it must be build independently. + *
+ * The task builder provide a systematic way to build a task object and is future proof. + * Task Builder mitigates the problem of having too many parameters in Task constructor. + * + */ +public class TaskBuilder { + + private final String name; + private Date startDate; + private Date endDate; + private Date createdDate; + private UniqueTagList tags; + private String recur; + private boolean isMarked; + + /** + * creation date will automatically assigned upon initializing. + * + * @param name is mandatory field. The task name. + * + */ + public TaskBuilder(String name){ + this.name = name; + this.createdDate = new Date(); + } + + /** + * build a floating task with both start and end dates are null + * + */ + public TaskBuilder asFloating(){ + startDate = null; + endDate = null; + return this; + } + + /** + * build an event with both valid start and end dates + * + * @param startDate is the start date of the event + * @param endDate is the end date of the event + * + * @throws InvalidEventDateException if start date is after end date + * + */ + public TaskBuilder asEvent(Date startDate, Date endDate) throws InvalidEventDateException{ + if (startDate.after(endDate)) { + throw new InvalidEventDateException(); + } + + this.startDate = startDate; + this.endDate = endDate; + + return this; + } + + /** + * build a deadline task with only end date + * + * @param endDate is the due date. + * + */ + public TaskBuilder asDeadline(Date endDate){ + this.startDate = null; + this.endDate = endDate; + return this; + } + + public TaskBuilder withTags(Set tagSet) throws IllegalValueException { + tags = new UniqueTagList(); + for (String tag: tagSet) { + this.tags.add(new Tag(tag)); + } + return this; + } + + public TaskBuilder withTags(UniqueTagList tags) throws IllegalValueException { + this.tags = tags; + return this; + } + + /** + * build a recurring task. + * + * @param recur can be daily, weekly, monthly, yearly + * + */ + public TaskBuilder asRecurring(String recur){ + this.recur = recur; + return this; + } + + /** + * Set a custom creation date. This will overwrite the auto assigned date. + * + * @param createdDate is the customized creation date + * + */ + public TaskBuilder withCreationDate(Date createdDate){ + this.createdDate = createdDate; + return this; + } + + /** + * build a task as marked task + * + */ + public TaskBuilder asMarked(){ + this.isMarked = true; + return this; + } + + + /** + * finalize build and return a Task object + * + */ + public Task build(){ + return new Task(this); + } + + //@@author generated + public String getName() { + return name; + } + //@@author generated + public Date getStartDate() { + return startDate; + } + //@@author generated + public Date getEndDate() { + return endDate; + } + //@@author generated + public Date getCreatedDate() { + return createdDate; + } + //@@author generated + public UniqueTagList getTags() { + return tags; + } + //@@author generated + public String getRecur() { + return recur; + } + //@@author generated + public boolean isMarked() { + return isMarked; + } + + + +} diff --git a/src/main/java/harmony/mastermind/model/task/TaskListComparator.java b/src/main/java/harmony/mastermind/model/task/TaskListComparator.java new file mode 100644 index 000000000000..ea6df56afdb5 --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/TaskListComparator.java @@ -0,0 +1,27 @@ +package harmony.mastermind.model.task; + +import java.util.Comparator; + +//@@author A0138862W +public class TaskListComparator implements Comparator { + + /* + * By default, compare by creation dates. + * This comparator use for sorting table view so edit/delete will not add to the bottom of the list + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(ReadOnlyTask o1, ReadOnlyTask o2) { + if(o1.getCreatedDate() == null || o2.getCreatedDate() == null){ + return 0; + } + + if (o1.getCreatedDate().after(o2.getCreatedDate())) { + return 1; + } else if (o1.getCreatedDate().before(o2.getCreatedDate())) { + return -1; + } else { + return 0; + } + } +} diff --git a/src/main/java/harmony/mastermind/model/task/UniqueTaskList.java b/src/main/java/harmony/mastermind/model/task/UniqueTaskList.java new file mode 100644 index 000000000000..dc53ecf89caa --- /dev/null +++ b/src/main/java/harmony/mastermind/model/task/UniqueTaskList.java @@ -0,0 +1,100 @@ +package harmony.mastermind.model.task; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.util.*; +import java.util.stream.Collectors; + +import harmony.mastermind.commons.exceptions.DuplicateDataException; +import harmony.mastermind.commons.util.CollectionUtil; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Task#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueTaskList implements Iterable { + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateTaskException extends DuplicateDataException { + protected DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } + } + + /** + * Signals that an operation targeting a specified task in the list would fail because + * there is no such matching task in the list. + */ + public static class TaskNotFoundException extends Exception {} + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty TaskList. + */ + public UniqueTaskList() {} + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(ReadOnlyTask toCheck) { + assert toCheck != null; + return internalList.contains(toCheck); + } + + /** + * Adds a task to the list. + * + * @throws DuplicateTaskException if the task to add is a duplicate of an existing task in the list. + */ + public void add(Task toAdd) throws DuplicateTaskException { + assert toAdd != null; + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException { + assert toRemove != null; + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + public ObservableList getInternalList() { + return internalList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && this.internalList.equals( + ((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefStorage.java b/src/main/java/harmony/mastermind/storage/JsonUserPrefStorage.java similarity index 75% rename from src/main/java/seedu/address/storage/JsonUserPrefStorage.java rename to src/main/java/harmony/mastermind/storage/JsonUserPrefStorage.java index 3a145ce35f15..fafeea8f5c97 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefStorage.java +++ b/src/main/java/harmony/mastermind/storage/JsonUserPrefStorage.java @@ -1,27 +1,32 @@ -package seedu.address.storage; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.UserPrefs; +package harmony.mastermind.storage; import java.io.File; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.util.FileUtil; +import harmony.mastermind.model.UserPrefs; + /** * A class to access UserPrefs stored in the hard disk as a json file */ public class JsonUserPrefStorage implements UserPrefsStorage{ private static final Logger logger = LogsCenter.getLogger(JsonUserPrefStorage.class); - + private final String SUCCESSFULLY_CHANGED_FILEPATH = "Successfully changed file path from " + + this.filePath + + " to " + + "%1$s"; + private String filePath; public JsonUserPrefStorage(String filePath){ this.filePath = filePath; } + @Override public Optional readUserPrefs() throws DataConversionException, IOException { @@ -70,4 +75,11 @@ public void saveUserPrefs(UserPrefs userPrefs, String prefsFilePath) throws IOEx FileUtil.serializeObjectToJsonFile(new File(prefsFilePath), userPrefs); } + + //@@author A0139194X + public void setFilePath(String filePath) { + assert filePath != null; + logger.fine(String.format(SUCCESSFULLY_CHANGED_FILEPATH, filePath)); + this.filePath = filePath; + } } diff --git a/src/main/java/harmony/mastermind/storage/Storage.java b/src/main/java/harmony/mastermind/storage/Storage.java new file mode 100644 index 000000000000..ad586703695e --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/Storage.java @@ -0,0 +1,59 @@ +package harmony.mastermind.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Optional; + +import harmony.mastermind.commons.core.Config; +import harmony.mastermind.commons.events.model.TaskManagerChangedEvent; +import harmony.mastermind.commons.events.storage.DataSavingExceptionEvent; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.exceptions.UnwrittableFolderException; +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; +import harmony.mastermind.model.ReadOnlyTaskManager; +import harmony.mastermind.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends TaskManagerStorage, UserPrefsStorage { + + //initializing variables + static String SAVE_FILE = "data.txt"; + static final String ERROR_READ = "Unable to read from file!\nTry checking " + SAVE_FILE + " or continue using to start over.\n\n"; + static final String ERROR_CREATE = "Problem creating file!"; + static final String ERROR_NOT_FOUND = "File not found"; + static final String NULL = "-"; + static final String SPACE = " "; + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(UserPrefs userPrefs) throws IOException; + + @Override + String getTaskManagerFilePath(); + + @Override + Optional readTaskManager() throws DataConversionException, FileNotFoundException; + + @Override + void saveTaskManager(ReadOnlyTaskManager taskManager) throws IOException; + + /** + * Saves the current version of the Mastermind to the hard disk. + * Creates the data file if it is missing. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleTaskManagerChangedEvent(TaskManagerChangedEvent tmce); +} diff --git a/src/main/java/harmony/mastermind/storage/StorageManager.java b/src/main/java/harmony/mastermind/storage/StorageManager.java new file mode 100644 index 000000000000..42307d27ea58 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/StorageManager.java @@ -0,0 +1,154 @@ +package harmony.mastermind.storage; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.ComponentManager; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.events.model.TaskManagerChangedEvent; +import harmony.mastermind.commons.events.storage.RelocateFilePathEvent; +import harmony.mastermind.commons.events.storage.DataSavingExceptionEvent; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.util.ConfigUtil; +import harmony.mastermind.commons.util.StringUtil; +import harmony.mastermind.model.ReadOnlyTaskManager; +import harmony.mastermind.model.UserPrefs; +import harmony.mastermind.commons.core.Config; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * Manages storage of TaskManager data in local storage. + */ +public class StorageManager extends ComponentManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private XmlTaskManagerStorage taskManagerStorage; + private JsonUserPrefStorage userPrefStorage; + + public StorageManager(XmlTaskManagerStorage addressBookStorage, JsonUserPrefStorage userPrefsStorage) { + super(); + this.taskManagerStorage = addressBookStorage; + this.userPrefStorage = userPrefsStorage; + } + + public StorageManager(String taskManagerFilePath, String userPrefsFilePath) { + super(); + this.taskManagerStorage = new XmlTaskManagerStorage(taskManagerFilePath); + this.userPrefStorage = new JsonUserPrefStorage(userPrefsFilePath); + } + + // ================ UserPrefs methods ============================== + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(UserPrefs userPrefs) throws IOException { + userPrefStorage.saveUserPrefs(userPrefs); + } + + + // ================ TaskManager methods ============================== + + @Override + public String getTaskManagerFilePath() { + return taskManagerStorage.getTaskManagerFilePath(); + } + + @Override + public Optional readTaskManager() throws DataConversionException, FileNotFoundException { + logger.fine("Attempting to read data from file: " + taskManagerStorage.getTaskManagerFilePath()); + + return taskManagerStorage.readTaskManager(taskManagerStorage.getTaskManagerFilePath()); + } + + @Override + public void saveTaskManager(ReadOnlyTaskManager taskManager) throws IOException { + taskManagerStorage.saveTaskManager(taskManager, taskManagerStorage.getTaskManagerFilePath()); + } + + + @Override + @Subscribe + public void handleTaskManagerChangedEvent(TaskManagerChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); + try { + saveTaskManager(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + + + //@@author A0139194X + /** + * Handles RelocateFilePathEvent by first changing taskManger's file path, then moving + * the file over and deleting the old one + */ + @Subscribe + public void handleRelocateEvent(RelocateFilePathEvent event) { + assert event != null; + assert event.getFilePath() != null; + String oldPath = taskManagerStorage.getTaskManagerFilePath(); + String newPath = correctFilePathFormat(event.getFilePath()); + taskManagerStorage.setTaskManagerFilePath(newPath); + updateConfig(newPath); + try { + logger.info("Trying to move into new file path."); + taskManagerStorage.migrateIntoNewFolder(oldPath, newPath); + } catch (IOException e) { + logger.warning("Error occured while handling relocate event."); + logger.warning("Reverting save location back to " + oldPath); + taskManagerStorage.setTaskManagerFilePath(oldPath); + updateConfig(oldPath); + } + } + + //@@author A0139194X + //Appends the '/' if it is not that for a valid file path + public String correctFilePathFormat(String newPath) { + assert newPath != null; + if (newPath.endsWith("/")) { + newPath = newPath + "mastermind.xml"; + } else { + newPath = newPath + "/mastermind.xml"; + } + return newPath; + } + + //@@author A0139194X + /** + * Updates the config.json file so that upon startup, the correct xml file will be + * loaded. + * @param newPath + */ + public void updateConfig(String newPath) { + assert newPath != null; + Config config; + String defaultConfigLocation = Config.DEFAULT_CONFIG_FILE; + + try { + Optional configOptional = ConfigUtil.readConfig(defaultConfigLocation); + config = configOptional.orElse(new Config()); + } catch (DataConversionException e) { + logger.warning("Config file at " + defaultConfigLocation + " is not in the correct format. " + + "Using default config properties"); + config = new Config(); + } + config.setTaskManagerFilePath(newPath); + logger.fine("Updated config's data save location."); + + //Update config file in case it was missing to begin with or there are new/unused fields + try { + ConfigUtil.saveConfig(config, defaultConfigLocation); + } catch (IOException e) { + logger.warning("Failed to save config file : " + StringUtil.getDetails(e)); + } + } +} diff --git a/src/main/java/harmony/mastermind/storage/StorageMemory.java b/src/main/java/harmony/mastermind/storage/StorageMemory.java new file mode 100644 index 000000000000..343fef8f8afb --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/StorageMemory.java @@ -0,0 +1,193 @@ +package harmony.mastermind.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import harmony.mastermind.memory.GenericMemory; +import harmony.mastermind.memory.Memory; + +/** + * API of the Storage component + */ +public abstract class StorageMemory implements Storage { + + //initializing variables + private static String SAVE_FILE = "data.txt"; + private static final String ERROR_READ = "Unable to read from file!\nTry checking " + SAVE_FILE + " or continue using to start over.\n\n"; + private static final String ERROR_CREATE = "Problem creating file!"; + private static final String ERROR_NOT_FOUND = "File not found"; + private static final String NULL = "-"; + private static final String SPACE = " "; + + //@@author A0143378Y + public static void saveToStorage(Memory memory) { + try { + PrintWriter pw = new PrintWriter(SAVE_FILE); + for (int i=0; i readTaskManager() throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyTaskManager} to the storage. + * @param taskManager cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveTaskManager(ReadOnlyTaskManager taskManager) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/harmony/mastermind/storage/UserPrefsStorage.java similarity index 69% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/harmony/mastermind/storage/UserPrefsStorage.java index ad2dc935187c..72b2c3fe7753 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/harmony/mastermind/storage/UserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +package harmony.mastermind.storage; import java.io.IOException; import java.util.Optional; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.model.UserPrefs; + /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link harmony.mastermind.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -20,7 +20,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * Saves the given {@link harmony.mastermind.model.UserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/harmony/mastermind/storage/XmlAdaptedArchive.java b/src/main/java/harmony/mastermind/storage/XmlAdaptedArchive.java new file mode 100644 index 000000000000..a294274065f2 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlAdaptedArchive.java @@ -0,0 +1,79 @@ +package harmony.mastermind.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; + +//@@author A0124797R +/** + * JAXB-friendly version of the archived Task. + * + */ +public class XmlAdaptedArchive { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date startDate; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedArchive() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + public XmlAdaptedArchive(ReadOnlyTask source) { + name = source.getName(); + startDate = source.getStartDate(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's ReadOnlyTask object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date startDate = this.startDate; + final Date endDate = this.endDate; + final String recur = this.recur; + final UniqueTagList tags = new UniqueTagList(taskTags); + final Date createdDate = this.createdDate; + + return new Task(name, startDate, endDate, tags, recur, createdDate).mark(); + } +} diff --git a/src/main/java/harmony/mastermind/storage/XmlAdaptedDeadline.java b/src/main/java/harmony/mastermind/storage/XmlAdaptedDeadline.java new file mode 100644 index 000000000000..53d9bc018954 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlAdaptedDeadline.java @@ -0,0 +1,73 @@ +package harmony.mastermind.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; + +//@@author A0124797R +/** + * JAXB-friendly version of the deadline type Task. + */ +public class XmlAdaptedDeadline { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedDeadline() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedDeadline + */ + public XmlAdaptedDeadline(ReadOnlyTask source) { + name = source.getName(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted deadline object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted deadline + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date endDate = this.endDate; + final String recur = this.recur; + final UniqueTagList tags = new UniqueTagList(taskTags); + + return new Task(name, endDate, tags, recur, createdDate); + } +} diff --git a/src/main/java/harmony/mastermind/storage/XmlAdaptedEvent.java b/src/main/java/harmony/mastermind/storage/XmlAdaptedEvent.java new file mode 100644 index 000000000000..c1d13f684ca8 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlAdaptedEvent.java @@ -0,0 +1,78 @@ +package harmony.mastermind.storage; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; + +//@@author A0124797R +/** + * JAXB-friendly version of the event type Task. + */ +public class XmlAdaptedEvent { + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private Date startDate; + @XmlElement(required = true) + private Date endDate; + @XmlElement(required = true) + private String recur; + @XmlElement(required = true) + private Date createdDate; + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedEvent() {} + + + /** + * Converts a given event into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedEvent + */ + public XmlAdaptedEvent(ReadOnlyTask source) { + name = source.getName(); + startDate = source.getStartDate(); + endDate = source.getEndDate(); + recur = source.getRecur(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted event object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Task toModelType() throws IllegalValueException { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final Date startDate = this.startDate; + final Date endDate = this.endDate; + final String recur = this.recur; + final Date createdDate = this.createdDate; + final UniqueTagList tags = new UniqueTagList(taskTags); + + return new Task(name, startDate, endDate, tags, recur, createdDate); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/harmony/mastermind/storage/XmlAdaptedTag.java similarity index 76% rename from src/main/java/seedu/address/storage/XmlAdaptedTag.java rename to src/main/java/harmony/mastermind/storage/XmlAdaptedTag.java index b9723fafbc67..070d38ae5cd0 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/harmony/mastermind/storage/XmlAdaptedTag.java @@ -1,11 +1,11 @@ -package seedu.address.storage; - -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +package harmony.mastermind.storage; import javax.xml.bind.annotation.XmlValue; +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.commons.util.CollectionUtil; +import harmony.mastermind.model.tag.Tag; + /** * JAXB-friendly adapted version of the Tag. */ @@ -31,7 +31,7 @@ public XmlAdaptedTag(Tag source) { /** * Converts this jaxb-friendly adapted tag object into the model's Tag object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person + * @throws IllegalValueException if there were any data constraints violated in the adapted task */ public Tag toModelType() throws IllegalValueException { return new Tag(tagName); diff --git a/src/main/java/harmony/mastermind/storage/XmlAdaptedTask.java b/src/main/java/harmony/mastermind/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..623205b267f9 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlAdaptedTask.java @@ -0,0 +1,69 @@ +package harmony.mastermind.storage; + +import javax.xml.bind.annotation.XmlElement; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +/** + * JAXB-friendly version of the Task. + */ +public class XmlAdaptedTask { + + @XmlElement(required = true) + private String name; + + @XmlElement(required = true) + private Date createdDate; + + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * No-arg constructor for JAXB use. + */ + public XmlAdaptedTask() {} + + + /** + * Converts a given Task into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedTask + */ + //@@author A0138862W + public XmlAdaptedTask(ReadOnlyTask source) { + name = source.getName(); + createdDate = source.getCreatedDate(); + + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + // @@author + + /** + * Converts this jaxb-friendly adapted task 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 { + final List taskTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + taskTags.add(tag.toModelType()); + } + + final String name = this.name; + final UniqueTagList tags = new UniqueTagList(taskTags); + + return new Task(name, tags, createdDate); + } +} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/harmony/mastermind/storage/XmlFileStorage.java similarity index 60% rename from src/main/java/seedu/address/storage/XmlFileStorage.java rename to src/main/java/harmony/mastermind/storage/XmlFileStorage.java index 27a5210cadaf..ce6176a6b9de 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/harmony/mastermind/storage/XmlFileStorage.java @@ -1,35 +1,36 @@ -package seedu.address.storage; - -import seedu.address.commons.util.XmlUtil; -import seedu.address.commons.exceptions.DataConversionException; +package harmony.mastermind.storage; import javax.xml.bind.JAXBException; + +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.util.XmlUtil; + import java.io.File; import java.io.FileNotFoundException; /** - * Stores addressbook data in an XML file + * Stores taskmanager data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given taskmanager data to the specified file. */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(File file, XmlSerializableTaskManager taskManager) throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, taskManager); } catch (JAXBException e) { assert false : "Unexpected exception " + e.getMessage(); } } /** - * Returns address book in the file or an empty address book + * Returns task manager in the file or an empty task manager */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, + public static XmlSerializableTaskManager loadDataFromSaveFile(File file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableTaskManager.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/harmony/mastermind/storage/XmlSerializableTaskManager.java b/src/main/java/harmony/mastermind/storage/XmlSerializableTaskManager.java new file mode 100644 index 000000000000..71fbc0bc2c05 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlSerializableTaskManager.java @@ -0,0 +1,235 @@ +package harmony.mastermind.storage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import harmony.mastermind.commons.exceptions.IllegalValueException; +import harmony.mastermind.model.ReadOnlyTaskManager; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.tag.UniqueTagList; +import harmony.mastermind.model.task.ArchiveTaskList; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.TaskListComparator; +import harmony.mastermind.model.task.UniqueTaskList; + +/** + * An Immutable TaskManager that is serializable to XML format + */ +@XmlRootElement(name = "taskmanager") +public class XmlSerializableTaskManager implements ReadOnlyTaskManager { + + @XmlElement + private List floatingTasks; + @XmlElement + private List events; + @XmlElement + private List deadlines; + @XmlElement + private List archives; + @XmlElement + private List tags; + + private TaskListComparator comparator; + + + { + floatingTasks = new ArrayList<>(); + events = new ArrayList<>(); + deadlines= new ArrayList<>(); + archives = new ArrayList<>(); + tags = new ArrayList<>(); + comparator = new TaskListComparator(); + } + + /** + * Empty constructor required for marshalling + */ + public XmlSerializableTaskManager() {} + + //@@author A0124797R + /** + * Conversion task manager to a JAXB-friendly version + */ + public XmlSerializableTaskManager(ReadOnlyTaskManager src) { + floatingTasks.addAll(src.getFloatingTaskList().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); + events.addAll(src.getEventList().stream().map(XmlAdaptedEvent::new).collect(Collectors.toList())); + deadlines.addAll(src.getDeadlineList().stream().map(XmlAdaptedDeadline::new).collect(Collectors.toList())); + tags = src.getTagList(); + archives.addAll(src.getArchiveList().stream() + .map(XmlAdaptedArchive::new).collect(Collectors.toList())); + } + + //@@author A0124797R + @Override + public UniqueTaskList getUniqueTaskList() { + UniqueTaskList lists = new UniqueTaskList(); + + for (XmlAdaptedDeadline xmld : deadlines) { + try { + lists.add(xmld.toModelType()); + } catch (IllegalValueException e) { + + } + } + for (XmlAdaptedEvent xmle : events) { + try { + lists.add(xmle.toModelType()); + } catch (IllegalValueException e) { + + } + } + for (XmlAdaptedTask xmlt : floatingTasks) { + try { + lists.add(xmlt.toModelType()); + } catch (IllegalValueException e) { + + } + } + lists.getInternalList().sort(comparator); + return lists; + } + + //@@author A0124797R + @Override + public UniqueTaskList getUniqueFloatingTaskList() { + UniqueTaskList lists = new UniqueTaskList(); + + for (XmlAdaptedTask xmlt : floatingTasks) { + try { + lists.add(xmlt.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + //@@author A0124797R + @Override + public UniqueTaskList getUniqueEventList() { + UniqueTaskList lists = new UniqueTaskList(); + for (XmlAdaptedEvent xmle : events) { + try { + lists.add(xmle.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + //@@author A0124797R + @Override + public UniqueTaskList getUniqueDeadlineList() { + UniqueTaskList lists = new UniqueTaskList(); + for (XmlAdaptedDeadline xmld : deadlines) { + try { + lists.add(xmld.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + //@@author A0124797R + @Override + public ArchiveTaskList getUniqueArchiveList() { + ArchiveTaskList lists = new ArchiveTaskList(); + for (XmlAdaptedArchive p : archives) { + try { + lists.add(p.toModelType()); + } catch (IllegalValueException e) { + + } + } + return lists; + } + + @Override + public UniqueTagList getUniqueTagList() { + try { + return new UniqueTagList(tags); + } catch (UniqueTagList.DuplicateTagException e) { + e.printStackTrace(); + return null; + } + } + + //@@author A0124797R + @Override + public List getTaskList() { + List tasks = getFloatingTaskList(); + List event = getEventList(); + List deadline = getDeadlineList(); + + tasks.addAll(event); + tasks.addAll(deadline); + + return tasks; + } + + //@@author A0124797R + @Override + public List getFloatingTaskList() { + return floatingTasks.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + //@@author A0124797R + @Override + public List getEventList() { + return events.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + //@@author A0124797R + @Override + public List getDeadlineList() { + return deadlines.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + //@@author A0124797R + @Override + public List getArchiveList() { + return archives.stream().map(p -> { + try { + return p.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + return null; + } + }).collect(Collectors.toCollection(ArrayList::new)); + } + + //@@author generated + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + +} \ No newline at end of file diff --git a/src/main/java/harmony/mastermind/storage/XmlTaskManagerStorage.java b/src/main/java/harmony/mastermind/storage/XmlTaskManagerStorage.java new file mode 100644 index 000000000000..c145595f9ed4 --- /dev/null +++ b/src/main/java/harmony/mastermind/storage/XmlTaskManagerStorage.java @@ -0,0 +1,120 @@ +package harmony.mastermind.storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Optional; +import java.util.logging.Logger; + +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.exceptions.DataConversionException; +import harmony.mastermind.commons.exceptions.FolderDoesNotExistException; +import harmony.mastermind.commons.util.FileUtil; +import harmony.mastermind.model.ReadOnlyTaskManager; + +/** + * A class to access TaskManager data stored as an xml file on the hard disk. + */ +public class XmlTaskManagerStorage implements TaskManagerStorage { + + private static final Logger logger = LogsCenter.getLogger(XmlTaskManagerStorage.class); + + private String filePath; + + public XmlTaskManagerStorage(String filePath){ + this.filePath = filePath; + } + + public String getTaskManagerFilePath(){ + return filePath; + } + + //@author A0139194X + public void setTaskManagerFilePath(String newFilePath){ + logger.fine("Changing new file path from \"" + this.filePath + "\" to " + newFilePath); + this.filePath = newFilePath; + } + + /**@@author + * Similar to {@link #readTaskManager()} + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readTaskManager(String filePath) throws DataConversionException, FileNotFoundException { + assert filePath != null; + + File taskManagerFile = new File(filePath); + + if (!taskManagerFile.exists()) { + logger.info("TaskManager file " + taskManagerFile + " not found"); + return Optional.empty(); + } + + ReadOnlyTaskManager taskManagerOptional = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + + return Optional.of(taskManagerOptional); + } + + /** + * Similar to {@link #saveTaskManager(ReadOnlyTaskManager)} + * @param filePath location of the data. Cannot be null + */ + public void saveTaskManager(ReadOnlyTaskManager taskManager, String filePath) throws IOException { + assert taskManager != null; + assert filePath != null; + + File file = new File(filePath); + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new XmlSerializableTaskManager(taskManager)); + } + + @Override + public Optional readTaskManager() throws DataConversionException, IOException { + return readTaskManager(filePath); + } + + @Override + public void saveTaskManager(ReadOnlyTaskManager taskManager) throws IOException { + saveTaskManager(taskManager, filePath); + } + + //@author A0139194X + /** + * Moves the contents from the old path to the new path + * @param oldPath + * @param newPath + * @throws IOException + */ + public void migrateIntoNewFolder(String oldPath, String newPath) throws IOException { + assert oldPath != null; + assert newPath != null; + Path oldDirectory = Paths.get(oldPath); + Path newDirectory = Paths.get(newPath); + File newFile = new File(newPath); + FileUtil.createIfMissing(newFile); + Files.copy(oldDirectory, newDirectory, StandardCopyOption.REPLACE_EXISTING); + deleteFile(oldPath); + } + + //@author A0139194X + /** + * Deletes the file specified in the file path + * @param filePath + * @return true if deleted, else false + */ + public boolean deleteFile(String filePath) { + assert filePath != null; + File toDelete = new File(filePath); + if (toDelete.delete()) { + logger.fine("Deleted " + filePath); + return true; + } else { + logger.warning("Failed to delete " + filePath); + return false; + } + } +} diff --git a/src/main/java/harmony/mastermind/ui/ActionHistoryEntry.java b/src/main/java/harmony/mastermind/ui/ActionHistoryEntry.java new file mode 100644 index 000000000000..323b276a7812 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/ActionHistoryEntry.java @@ -0,0 +1,84 @@ +package harmony.mastermind.ui; + +import java.util.Date; + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.HBox; + +//@@author A0138862W +public class ActionHistoryEntry extends UiPart{ + + private static final String FXML = "ActionHistoryEntry.fxml"; + + @FXML + private HBox actionHistoryEntry; + + @FXML + private Label title; + + @FXML + private Label date; + + public void setTitle(String title){ + this.title.setText(title); + } + + public void setDate(String date){ + this.date.setText(date); + } + + public Node getNode(){ + return actionHistoryEntry; + } + + public void setTypeFail(){ + this.title.setStyle("-fx-text-fill: -fx-quaternary;"); + this.date.setStyle("-fx-text-fill: -fx-quaternary;"); + } + + public void setTypeSuccess(){ + this.title.setStyle("-fx-text-fill: -fx-primary;"); + this.date.setStyle("-fx-text-fill: -fx-primary;"); + } + + @Override + public void setNode(Node node) { + actionHistoryEntry = (HBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + + +} + +//@@author A0138862W +class ActionHistory { + private final String title; + private final String description; + private final Date dateActioned; + + public ActionHistory(String title, String description){ + this.title = title; + this.description = description; + this.dateActioned = new Date(); + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public Date getDateActioned() { + return dateActioned; + } +} diff --git a/src/main/java/harmony/mastermind/ui/ActionHistoryPane.java b/src/main/java/harmony/mastermind/ui/ActionHistoryPane.java new file mode 100644 index 000000000000..fcfad62171fc --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/ActionHistoryPane.java @@ -0,0 +1,148 @@ +package harmony.mastermind.ui; + +import javafx.scene.Node; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.TextArea; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.events.ui.ExecuteCommandEvent; +import harmony.mastermind.commons.events.ui.NewResultAvailableEvent; +import harmony.mastermind.commons.events.ui.ToggleActionHistoryEvent; +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import javafx.fxml.FXML; + +public class ActionHistoryPane extends UiPart { + + + private static final String FXML = "ActionHistoryPane.fxml"; + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TitledPane actionHistoryPane; + + @FXML + private ListView actionHistory; + + @FXML + private TextArea consoleOutput; + + public static ActionHistoryPane load(Stage primaryStage, AnchorPane actionHistoryPanePlaceholder, Logic logic){ + ActionHistoryPane ui = UiPartLoader.loadUiPart(primaryStage, actionHistoryPanePlaceholder, new ActionHistoryPane()); + ui.configure(logic); + return ui; + } + + @FXML + private void initialize(){ + initActionHistory(); + } + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @Override + public void setNode(Node node) { + this.actionHistoryPane = (TitledPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + private void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(actionHistoryPane); + FxViewUtil.applyAnchorBoundaryParameters(actionHistoryPane, 0, 0, 0, 0); + } + + // @@author A0138862W + protected void initActionHistory() { + + actionHistory.setOnMouseClicked(value -> { + consoleOutput.setText(actionHistory.getSelectionModel().getSelectedItem().getDescription()); + }); + + actionHistory.setCellFactory(listView -> { + ListCell actionCell = new ListCell() { + + @Override + protected void updateItem(ActionHistory item, boolean isEmpty) { + super.updateItem(item, isEmpty); + + if (!isEmpty) { + + ActionHistoryEntry actionHistoryEntry = UiPartLoader.loadUiPart(new ActionHistoryEntry()); + + actionHistoryEntry.setTitle(item.getTitle().toUpperCase()); + actionHistoryEntry.setDate(item.getDateActioned().toString().toUpperCase()); + + if (item.getTitle().toUpperCase().equals("INVALID COMMAND")) { + actionHistoryEntry.setTypeFail(); + } else { + actionHistoryEntry.setTypeSuccess(); + } + + this.setGraphic(actionHistoryEntry.getNode()); + + this.setPrefHeight(50); + this.setPrefWidth(250); + + this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + } else { + this.setGraphic(null); + } + } + }; + + return actionCell; + }); + + + } + //@@author + + //@@author A0138862W + public void toggleActionHistory(){ + actionHistoryPane.setExpanded(!actionHistoryPane.isExpanded()); +} + + // @@A0138862W + protected void pushToActionHistory(String title, String description) { + ActionHistory aHistory = new ActionHistory(title, description); + + actionHistory.getItems().add(aHistory); + actionHistory.scrollTo(actionHistory.getItems().size()- 1); + } + // @@author + + @Subscribe + private void handleNewResultAvailableEvent(NewResultAvailableEvent event){ + this.consoleOutput.setText(event.message); + this.actionHistoryPane.setText(event.message); + this.pushToActionHistory(event.title, event.message); + } + + @Subscribe + private void handleToggleActionHistoryEvent(ToggleActionHistoryEvent event){ + toggleActionHistory(); + } + +} diff --git a/src/main/java/harmony/mastermind/ui/ArchivesTableView.java b/src/main/java/harmony/mastermind/ui/ArchivesTableView.java new file mode 100644 index 000000000000..c9890d3930f3 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/ArchivesTableView.java @@ -0,0 +1,165 @@ +package harmony.mastermind.ui; + +import org.ocpsoft.prettytime.PrettyTime; + +import harmony.mastermind.commons.util.AppUtil; +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class ArchivesTableView extends DefaultTableView { + private static final String RECUR_ICON = "/images/recur_white.png"; + + private static final String FXML = "ArchivesTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView archivesTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static ArchivesTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + ArchivesTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new ArchivesTableView()); + ui.configure(logic); + return ui; + } + + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } + + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @SuppressWarnings("unchecked") + @Override + public void setNode(Node node) { + this.archivesTableView = (TableView) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(archivesTableView); + FxViewUtil.applyAnchorBoundaryParameters(archivesTableView, 0, 0, 0, 0); + } + + public TableView getTableView(){ + return archivesTableView; + } + + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + + + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + + + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.prefWidthProperty().bind(archivesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView(AppUtil.getImage(RECUR_ICON))); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } +} + diff --git a/src/main/java/harmony/mastermind/ui/CommandBox.java b/src/main/java/harmony/mastermind/ui/CommandBox.java new file mode 100644 index 000000000000..dd907360ae26 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/CommandBox.java @@ -0,0 +1,261 @@ +package harmony.mastermind.ui; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Logger; + +import org.controlsfx.control.textfield.AutoCompletionBinding; +import org.controlsfx.control.textfield.TextFields; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.events.ui.IncorrectCommandAttemptedEvent; +import harmony.mastermind.commons.events.ui.NewResultAvailableEvent; +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.logic.commands.ListCommand; +import harmony.mastermind.logic.commands.UpcomingCommand; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class CommandBox extends UiPart { + + private static final String FXML = "CommandBox.fxml"; + + private static final KeyCombination CTRL_ONE = new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.CONTROL_DOWN); + private static final KeyCombination CTRL_TWO = new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.CONTROL_DOWN); + private static final KeyCombination CTRL_THREE = new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.CONTROL_DOWN); + private static final KeyCombination CTRL_FOUR = new KeyCodeCombination(KeyCode.DIGIT4, KeyCombination.CONTROL_DOWN); + private static final KeyCombination CTRL_FIVE = new KeyCodeCombination(KeyCode.DIGIT5, KeyCombination.CONTROL_DOWN); + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + private AnchorPane placeholder; + + private Logic logic; + + private String currCommandText; + + private CommandResult mostRecentResult; + + private Stack commandHistory = new Stack(); + private int commandIndex = 0; + + private AutoCompletionBinding autoCompletionBinding; + + // List of words for autocomplete + Set listOfWords = new HashSet<>(); + String[] words = { "add", "delete", "edit", "clear", "help", "undo", "mark", "find", "exit", "do", "delete" }; + + @FXML + private TextField commandField; + + @FXML + private void initialize(){ + Platform.runLater(new Runnable() { + @Override + public void run() { + commandField.requestFocus(); + } + }); + } + + public static CommandBox load(Stage primaryStage, AnchorPane commandBoxPlaceholder, Logic logic) { + CommandBox commandBox = UiPartLoader.loadUiPart(primaryStage, commandBoxPlaceholder, new CommandBox()); + commandBox.configure(logic); + return commandBox; + } + + @Override + public void setNode(Node node) { + this.commandField = (TextField) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane placeholder) { + this.placeholder = placeholder; + } + + private void configure(Logic logic) { + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder() { + placeholder.getChildren().add(commandField); + FxViewUtil.applyAnchorBoundaryParameters(commandField, 0, 0, 0, 0); + } + + @FXML + private void handleCommandInputChanged() { + // Take a copy of the command text + currCommandText = commandField.getText(); + + setStyleToIndicateCorrectCommand(); + /* + * We assume the command is correct. If it is incorrect, the command box + * will be changed accordingly in the event handling code {@link + * #handleIncorrectCommandAttempted} + */ + mostRecentResult = logic.execute(currCommandText); + + // adds current command into the stack + updateCommandHistory(currCommandText); + + this.raise(new NewResultAvailableEvent(mostRecentResult.title, mostRecentResult.feedbackToUser)); + + logger.info("Result: " + mostRecentResult.feedbackToUser); + } + + // @@author A0124797R + /** + * Sets the command box style to indicate a correct command. + */ + private void setStyleToIndicateCorrectCommand() { + commandField.setText(""); +} + + /** + * Handles any KeyPress in the commandField + */ + @FXML + private void handleKeyPressed(KeyEvent event) { + KeyCode key = event.getCode(); + switch (key) { + case UP: + restorePrevCommandText(); + return; + case DOWN: + restoreNextCommandText(); + return; + case ENTER: + learnWord(commandField.getText()); + return; + } + + if (CTRL_ONE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS)); + } else if (CTRL_TWO.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_TASKS)); + } else if (CTRL_THREE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_EVENTS)); + } else if (CTRL_FOUR.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_DEADLINES)); + } else if (CTRL_FIVE.match(event)) { + raise(new NewResultAvailableEvent(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_SUCCESS_ARCHIVES)); + } + } + + // @@author A0143378Y + @FXML + private void initAutoComplete() { + // Autocomplete function + Collections.addAll(listOfWords, words); + autoCompletionBinding = TextFields.bindAutoCompletion(commandField, listOfWords); + autoCompletionBinding.setPrefWidth(500); + autoCompletionBinding.setVisibleRowCount(5); + autoCompletionBinding.setHideOnEscape(true); + + } + + // @@author A0143378Y + // This function takes in whatever the user has "ENTER"-ed, and save in a + // dictionary of words + // These words will be in the autocomplete list of words + private void learnWord(String text) { + listOfWords.add(text); + + if (autoCompletionBinding != null) { + autoCompletionBinding.dispose(); + } + + autoCompletionBinding = TextFields.bindAutoCompletion(commandField, listOfWords); + } + + // @@author A0124797R + private void restoreCommandText() { + commandField.setText(currCommandText); + } + + private void restorePrevCommandText() { + String prevCommand = getPrevCommandHistory(); + if (prevCommand != null) { + // need to wrap in runLater due to concurrency threading in JavaFX + Platform.runLater(new Runnable() { + public void run() { + commandField.setText(prevCommand); + commandField.positionCaret(prevCommand.length()); + } + }); + } // else ignore + } + + private void restoreNextCommandText() { + String nextCommand = getNextCommandHistory(); + // need to wrap in runLater due to concurrency threading in JavaFX + Platform.runLater(new Runnable() { + public void run() { + if (nextCommand != null) { + commandField.setText(nextCommand); + commandField.positionCaret(nextCommand.length()); + } else { + commandField.setText(""); + } + } + }); +} + + /** + * Adds recent input into stack + */ + private void updateCommandHistory(String command) { + commandHistory.push(command); + commandIndex = commandHistory.size(); + } + + private String getPrevCommandHistory() { + if (commandHistory.empty()) { + return null; + } else if (commandIndex == 0) { + return null; + } else { + commandIndex--; + return commandHistory.get(commandIndex); + } + } + + private String getNextCommandHistory() { + if (commandHistory.empty()) { + return null; + } else if (commandIndex >= commandHistory.size() - 1) { + commandIndex = commandHistory.size(); + return null; + } else { + commandIndex++; + return commandHistory.get(commandIndex); + } + } + + @Subscribe + private void handleIncorrectCommand(IncorrectCommandAttemptedEvent event) { + restoreCommandText(); + } +} diff --git a/src/main/java/harmony/mastermind/ui/DeadlinesTableView.java b/src/main/java/harmony/mastermind/ui/DeadlinesTableView.java new file mode 100644 index 000000000000..80dab9c05bbc --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/DeadlinesTableView.java @@ -0,0 +1,166 @@ +package harmony.mastermind.ui; + +import org.ocpsoft.prettytime.PrettyTime; + +import harmony.mastermind.commons.util.AppUtil; +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class DeadlinesTableView extends DefaultTableView { + private static final String RECUR_ICON = "/images/recur_white.png"; + + private static final String FXML = "DeadlinesTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView deadlinesTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static DeadlinesTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + DeadlinesTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new DeadlinesTableView()); + ui.configure(logic); + return ui; + } + + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } + + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @SuppressWarnings("unchecked") + @Override + public void setNode(Node node) { + this.deadlinesTableView = (TableView) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(deadlinesTableView); + FxViewUtil.applyAnchorBoundaryParameters(deadlinesTableView, 0, 0, 0, 0); + } + + @Override + public TableView getTableView(){ + return deadlinesTableView; + } + + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + + + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + + + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.prefWidthProperty().bind(deadlinesTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView(AppUtil.getImage(RECUR_ICON))); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } +} + diff --git a/src/main/java/harmony/mastermind/ui/DefaultTableView.java b/src/main/java/harmony/mastermind/ui/DefaultTableView.java new file mode 100644 index 000000000000..add018eeb27b --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/DefaultTableView.java @@ -0,0 +1,276 @@ +package harmony.mastermind.ui; + +import java.util.Date; + +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.shade.org.apache.commons.lang.time.DurationFormatUtils; + +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.tag.Tag; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.DefaultProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import javafx.stage.Stage; + +public abstract class DefaultTableView extends UiPart { + + protected static final double WIDTH_MULTIPLIER_INDEX = 0.049; + protected static final double WIDTH_MULTIPLIER_NAME = 0.28; + protected static final double WIDTH_MULTIPLIER_STARTDATE = 0.19; + protected static final double WIDTH_MULTIPLIER_ENDDATE = 0.19; + protected static final double WIDTH_MULTIPLIER_TAGS = 0.175; + protected static final double WIDTH_MULTIPLIER_RECUR = 0.1; + + protected static final PrettyTime prettyTime = new PrettyTime(); + + public abstract TableView getTableView(); + + //@@author A0138862W + protected TableCell renderIndexCell() { + return new TableCell() { + @Override + public void updateIndex(int index) { + super.updateIndex(index); + + Text indexText = new Text(Integer.toString(index + 1)+ "."); + indexText.getStyleClass().add("index-column"); + + if (isEmpty() || index < 0) { + this.setGraphic(null); + } else { + this.setGraphic(indexText); + } + } + + }; + } + + //@@author A0138862W + protected TableCell renderNameCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + + if (!isEmpty()) { + + VBox vBox = new VBox(3); + + Text taskName = generateStyledText(readOnlyTask, readOnlyTask.getName()); + taskName.getStyleClass().add("task-name-column"); + vBox.getChildren().add(taskName); + + generateSpecialTag(readOnlyTask, vBox); + + this.setGraphic(vBox); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + + }; + } + + //@@author A0138862W + protected TableCell renderStartDateCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getStartDate() != null) { + + TextFlow textFlow = generateStyledDate(readOnlyTask, readOnlyTask.getStartDate()); + + this.setGraphic(textFlow); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + + }; + } + + //@@author A0138862W + protected TableCell renderEndDateCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getEndDate() != null) { + + TextFlow textFlow = generateStyledDate(readOnlyTask, readOnlyTask.getEndDate()); + + this.setGraphic(textFlow); + this.setPrefHeight(50); + + } else { + this.setGraphic(null); + } + + } + }; + } + + //@@author A0138862W + protected TableCell renderTagsCell() { + return new TableCell() { + + @Override + public void updateItem(ReadOnlyTask readOnlyTask, boolean isEmpty) { + super.updateItem(readOnlyTask, isEmpty); + if (!isEmpty() + && readOnlyTask.getTags() != null) { + + HBox tags = new HBox(5); + + for (Tag tag : readOnlyTask.getTags()) { + Button tagBubble = new Button(); + tagBubble.setText(tag.tagName); + tagBubble.getStyleClass().add("tag"); + tags.getChildren().add(tagBubble); + } + + this.setGraphic(tags); + } else { + this.setGraphic(null); + } + } + }; + } + + // @@author A0124797R + protected TableCell renderRecurCell() { + return new TableCell() { + + @Override + public void updateItem(Boolean isRecur, boolean isEmpty) { + super.updateItem(isRecur, isEmpty); + if (!isEmpty()) { + CheckBox box = new CheckBox(); + box.setSelected(isRecur); + box.setDisable(true); + box.setStyle("-fx-opacity: 1"); + + this.setAlignment(Pos.CENTER); + this.setGraphic(box); + } else { + this.setGraphic(null); + ; + } + } + }; + } + + // @@author A0138862W + /* + * Generate styled row base on the task status: due(red), happening(orange), + * normal(blue) + * + */ + protected Text generateStyledText(ReadOnlyTask readOnlyTask, String text) { + Text taskName = new Text(text); + + if (readOnlyTask.isHappening()) { + taskName.getStyleClass().add("happening"); + } else if (readOnlyTask.isDue()) { + taskName.getStyleClass().add("overdue"); + } else { + taskName.getStyleClass().add("normal"); + } + return taskName; + } + + // @@author A0138862W + /** + * THis method generate Due, Happening, and duration depend on the nature of the task + * + * @param readOnlyTask the task object + * @param vBox The container + */ + protected void generateSpecialTag(ReadOnlyTask readOnlyTask, VBox vBox) { + HBox hBox = new HBox(5); + + Button status = new Button(); + if (readOnlyTask.isHappening()) { + status.setText("HAPPENING"); + status.getStyleClass().add("tag-happening"); + hBox.getChildren().add(status); + } else if (readOnlyTask.isDue()) { + status.setText("DUE"); + status.getStyleClass().add("tag-overdue"); + hBox.getChildren().add(status); + } + + if (readOnlyTask.isEvent()) { + Button eventDuration = new Button(); + eventDuration.setText("DURATION: " + DurationFormatUtils.formatDurationWords(readOnlyTask.getEventDuration().toMillis(), true, true).toUpperCase()); + eventDuration.getStyleClass().add("tag-event-duration"); + hBox.getChildren().add(eventDuration); + } else if (readOnlyTask.isDeadline() + && !readOnlyTask.isDue()) { + Button dueDuration = new Button(); + dueDuration.setText("DUE IN " + DurationFormatUtils.formatDurationWords(readOnlyTask.getDueDuration().toMillis(), true, true)); + dueDuration.getStyleClass().add("tag-due-duration"); + hBox.getChildren().add(dueDuration); + } + + vBox.getChildren().add(hBox); + } + + + // @@author A0138862W + /** + * + * This method generate the double-line text for date column + * + * @param readOnlyTask the task object + * @return TextFlow the styled text + */ + protected TextFlow generateStyledDate(ReadOnlyTask readOnlyTask, Date date) { + TextFlow textFlow = new TextFlow(); + + Text prettyDate = generateStyledText(readOnlyTask, prettyTime.format(date)); + prettyDate.getStyleClass().add("pretty-date"); + + Text lineBreak = new Text("\n\n"); + lineBreak.setStyle("-fx-font-size:2px;"); + + Text uglyDate = generateStyledText(readOnlyTask, readOnlyTask.parse(date)); + uglyDate.getStyleClass().add("ugly-date"); + + textFlow.getChildren().add(prettyDate); + textFlow.getChildren().add(lineBreak); + textFlow.getChildren().add(uglyDate); + return textFlow; + } + +} + diff --git a/src/main/java/harmony/mastermind/ui/EventsTableView.java b/src/main/java/harmony/mastermind/ui/EventsTableView.java new file mode 100644 index 000000000000..9e48c3b7fd53 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/EventsTableView.java @@ -0,0 +1,164 @@ +package harmony.mastermind.ui; + +import org.ocpsoft.prettytime.PrettyTime; + +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class EventsTableView extends DefaultTableView { + + private static final String FXML = "EventsTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView eventsTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static EventsTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + EventsTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new EventsTableView()); + ui.configure(logic); + return ui; + } + + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } + + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @SuppressWarnings("unchecked") + @Override + public void setNode(Node node) { + this.eventsTableView = (TableView) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(eventsTableView); + FxViewUtil.applyAnchorBoundaryParameters(eventsTableView, 0, 0, 0, 0); + } + + @Override + public TableView getTableView(){ + return eventsTableView; + } + + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + + + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + + + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.prefWidthProperty().bind(eventsTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView("file:src/main/resources/images/recur_white.png")); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } +} + diff --git a/src/main/java/harmony/mastermind/ui/HelpPopup.java b/src/main/java/harmony/mastermind/ui/HelpPopup.java new file mode 100644 index 000000000000..70f18b4125b3 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/HelpPopup.java @@ -0,0 +1,177 @@ +package harmony.mastermind.ui; + +import java.util.ArrayList; +import java.util.logging.Logger; + +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.logic.HelpPopupEntry; +import javafx.scene.Node; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyEvent; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.stage.Popup; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; + +//@@author A0139194X +/** + * Class to initialise the Help Popup containing a table of information. + */ +public class HelpPopup extends UiPart { + + private static final String FXML = "HelpPopup.fxml"; + private static final Logger logger = LogsCenter.getLogger(HelpPopup.class); + + private final String COMMAND_COL_HEADER = "Command"; + private final String FORMAT_COL_HEADER = "Format"; + private final String DESCRIPTION_COL_HEADER = "Description"; + + private final int COMMAND_COL_MIN_WIDTH = 150; + private final int FORMAT_COL_MIN_WIDTH = 300; + private final int DESCRIPTION_COL_MIN_WIDTH = 400; + + private final int DEFAULT_X_POS = 200; + private final int DEFAULT_Y_POS = 100; + + private Popup popup; + private boolean isFirstKey; + private TableView table; + + TableColumn commandCol; + TableColumn formatCol; + TableColumn descriptionCol; + + ObservableList entries; + + //@@author A0139194X + public HelpPopup() { + initTable(); + initPopup(); + isFirstKey = true; + } + + //@@author A0139194X + public void show(Node node) { + assert node != null; + table.setItems(entries); + logger.fine("Displaying help Popup"); + popup.show(node, DEFAULT_X_POS, DEFAULT_Y_POS); + popup.centerOnScreen(); + } + + //@@author + @Override + public void setNode(Node node) { + } + + //@@author A0139194X + @Override + public String getFxmlPath() { + return FXML; + } + + //@@author A0139194X + @FXML + private void initPopup() { + popup = new Popup(); + + popup.getContent().add(table); + + popup.addEventHandler(KeyEvent.KEY_RELEASED, keyEventHandler); + } + + /**@@author A0139194X + * Initialise the table + */ + @FXML + private void initTable() { + logger.info("Initialising help popup's table"); + table = new TableView(); + table.setEditable(false); + + initCommandCol(); + initFormatCol(); + initDescriptionCol(); + + table.getColumns().addAll(commandCol, formatCol, descriptionCol); + } + + /**@@author A0139194X + * Initialise the Command word Column + */ + private void initCommandCol() { + commandCol = new TableColumn(COMMAND_COL_HEADER); + commandCol.setMinWidth(COMMAND_COL_MIN_WIDTH); + commandCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getCommandWord())); + } + + /**@@author A0139194X + * Initialise the format Column + */ + private void initFormatCol() { + formatCol = new TableColumn(FORMAT_COL_HEADER); + formatCol.setMinWidth(FORMAT_COL_MIN_WIDTH); + formatCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getFormat())); + } + + /**@@author A0139194X + * Initialise the description Column + */ + private void initDescriptionCol() { + descriptionCol = new TableColumn(DESCRIPTION_COL_HEADER); + descriptionCol.setMinWidth(DESCRIPTION_COL_MIN_WIDTH); + descriptionCol.setCellValueFactory(entry -> new ReadOnlyStringWrapper(entry.getValue().getDescription())); + } + + //@@author A0139194X + //Handles the closing of the popup + @FXML + EventHandler keyEventHandler = new EventHandler() { + public void handle(KeyEvent event) { + if (!isFirstKey && event.getCode() != null) { + popup.hide(); + } + isFirstKey = !isFirstKey; + } + }; + + + /** + * @@author A0143378Y-unused + * Styling for TextArea. Unused because we initially use a TextArea contained in a popup. + * But we changed it to a TableView instead. + */ + /* + public void properties() { + //Setting up the width and height + content.setPrefHeight(DEFAULT_HEIGHT); + content.setPrefWidth(DEFAULT_WIDTH); + + //Setting up wrapping of text in the content box + content.setWrapText(true); + + //Setting up the background, font and borders + content.setStyle("-fx-background-color: #00BFFF;" + + "-fx-padding:10px;" + + "-fx-text-fill: #000080;" + + "-fx-font-family: Fantasy;" + + "-fx-alignment: center" + + "-fx-font-size: 20px" + ); + } + */ + + //@@author A0139194X + //Sets the data to display + public void injectData(ArrayList helpEntries) { + entries = FXCollections.observableArrayList(); + for (int i = 0; i < helpEntries.size(); i++) { + entries.add(helpEntries.get(i)); + } + logger.fine("Help Popup table entries injected and initialised succesfully"); + } +} diff --git a/src/main/java/harmony/mastermind/ui/HomeTableView.java b/src/main/java/harmony/mastermind/ui/HomeTableView.java new file mode 100644 index 000000000000..0c3b515e5df0 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/HomeTableView.java @@ -0,0 +1,188 @@ +package harmony.mastermind.ui; + +import org.ocpsoft.prettytime.PrettyTime; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.util.AppUtil; +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +//@@author A0138862W +public class HomeTableView extends DefaultTableView { + private static final String RECUR_ICON = "/images/recur_white.png"; + private static final String FXML = "HomeTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView homeTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static HomeTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + HomeTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new HomeTableView()); + ui.configure(logic); + return ui; + } + + // @@author A0124797R + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } + //@@author + + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @SuppressWarnings("unchecked") + @Override + public void setNode(Node node) { + this.homeTableView = (TableView) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(homeTableView); + FxViewUtil.applyAnchorBoundaryParameters(homeTableView, 0, 0, 0, 0); + } + + @Override + public TableView getTableView(){ + return homeTableView; + } + + // @@author A0138862W + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.setSortable(false); + indexColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + + // @@author A0138862W + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.setSortable(false); + nameColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + + // @@author A0138862W + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.setSortable(false); + startDateColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + + // @@author A0138862W + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.setSortable(false); + endDateColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + + // @@author A0138862W + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.setSortable(false); + tagsColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + + // @@author A0124797R + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.setSortable(false); + recurColumn.prefWidthProperty().bind(homeTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView(AppUtil.getImage(RECUR_ICON))); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } + + // @@author A0138862W + @Subscribe + public void highlightLastActionedRow(HighlightLastActionedRowRequestEvent event){ + homeTableView.getSelectionModel().select(event.task); + homeTableView.scrollTo(event.task); + } +} + diff --git a/src/main/java/harmony/mastermind/ui/MainWindow.java b/src/main/java/harmony/mastermind/ui/MainWindow.java new file mode 100644 index 000000000000..a1817a7412ba --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/MainWindow.java @@ -0,0 +1,343 @@ +package harmony.mastermind.ui; + +import java.util.Comparator; +import java.util.logging.Logger; + +import javax.swing.plaf.basic.BasicInternalFrameTitlePane.RestoreAction; + +import org.ocpsoft.prettytime.PrettyTime; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.commons.core.Config; +import harmony.mastermind.commons.core.GuiSettings; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.events.model.TaskManagerChangedEvent; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.events.ui.IncorrectCommandAttemptedEvent; +import harmony.mastermind.commons.events.ui.NewResultAvailableEvent; +import harmony.mastermind.commons.events.ui.TabChangedEvent; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.logic.commands.CommandResult; +import harmony.mastermind.logic.commands.IncorrectCommand; +import harmony.mastermind.logic.commands.ListCommand; +import harmony.mastermind.logic.commands.UpcomingCommand; +import harmony.mastermind.model.UserPrefs; +import harmony.mastermind.model.task.ReadOnlyTask; +import harmony.mastermind.model.task.Task; +import harmony.mastermind.model.task.TaskListComparator; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.TextAlignment; +import javafx.stage.Stage; + +/** + * The Main Window. Provides the basic application layout containing a menu bar + * and space where other JavaFX elements can be placed. + */ +public class MainWindow extends UiPart { + private static final String ICON = "/images/to_do_list_32.png"; + private static final String FXML = "MainWindow.fxml"; + + private static final short INDEX_HOME = 0; + private static final short INDEX_TASKS = 1; + private static final short INDEX_EVENTS = 2; + private static final short INDEX_DEADLINES = 3; + private static final short INDEX_ARCHIVES = 4; + + private static final String[] NAME_TABS = { "Home", "Tasks", "Events", "Deadlines", "Archives" }; + + public static final int MIN_HEIGHT = 600; + public static final int MIN_WIDTH = 460; + + // @@author A0138862W + // init only one parser for all parsing, save memory and computation time + private static final PrettyTime prettyTime = new PrettyTime(); + // @@author + + private Logic logic; + + private Config config; + private UserPrefs userPrefs; + + // Handles to elements of this Ui container + private VBox rootLayout; + private Scene scene; + + private String taskManagerName; + + private final Logger logger = LogsCenter.getLogger(MainWindow.class); + + private CommandResult mostRecentResult; + private String currCommandText; + + ObservableList tabLst; + + // UI elements + /* + * @FXML private TextField commandField; + */ + + @FXML + private AnchorPane commandBoxPlaceholder; + private CommandBox commandBox; + + @FXML + private AnchorPane actionHistoryPanePlaceholder; + private ActionHistoryPane actionHistoryPane; + + @FXML + private AnchorPane homeTableViewPlaceholder; + private HomeTableView homeTableView; + + @FXML + private AnchorPane tasksTableViewPlaceholder; + private TasksTableView tasksTableView; + + @FXML + private AnchorPane eventsTableViewPlaceholder; + private EventsTableView eventsTableView; + + @FXML + private AnchorPane deadlinesTableViewPlaceholder; + private DeadlinesTableView deadlinesTableView; + + @FXML + private AnchorPane archivesTableViewPlaceholder; + private ArchivesTableView archivesTableView; + + @FXML + private TextArea consoleOutput; + + @FXML + private TabPane tabPane; + + @FXML + private ListView actionHistory; + + public MainWindow() { + super(); + } + + @Override + public void setNode(Node node) { + rootLayout = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + // @@author A0139194X + public Node getNode() { + return rootLayout; + } + + // @@author A0138862W + public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { + + MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); + mainWindow.fillInnerParts(logic); + mainWindow.configure(config.getAppTitle(), config.getTaskManagerName(), config, prefs, logic); + return mainWindow; + } + // @@author + + private void configure(String appTitle, String taskManagerName, Config config, UserPrefs prefs, Logic logic) { + + // Set dependencies + this.logic = logic; + this.taskManagerName = taskManagerName; + this.config = config; + this.userPrefs = prefs; + + // Configure the UI + setTitle(appTitle); + setIcon(ICON); + setWindowMinSize(); + setWindowDefaultSize(prefs); + scene = new Scene(rootLayout); + primaryStage.setScene(scene); + + tabLst = tabPane.getTabs(); + updateTabTitle(); + + configureComponents(logic); + + registerAsAnEventHandler(this); + } + + //@@author A0138862W + /** + * Configure Tables and Tabs + * @param logic Logic Manager instance + */ + private void configureComponents(Logic logic) { + + // Configure sorting algorithm for tables + SortedList sortedTasks = logic.getFilteredTaskList().sorted(); + Comparator comparator = new TaskListComparator(); + sortedTasks.setComparator(comparator); + sortedTasks.comparatorProperty().bind(homeTableView.getTableView().comparatorProperty()); + + // define placeholder label for empty table + Label placeholder = new Label("What's on your mind?\nTry adding a new task by executing \"add\" command!"); + placeholder.setAlignment(Pos.CENTER); + placeholder.setTextAlignment(TextAlignment.CENTER); + + homeTableView.getTableView().setPlaceholder(placeholder); + homeTableView.getTableView().setItems(sortedTasks); + + tasksTableView.getTableView().setPlaceholder(placeholder); + tasksTableView.getTableView().setItems(logic.getFilteredFloatingTaskList()); + + eventsTableView.getTableView().setPlaceholder(placeholder); + eventsTableView.getTableView().setItems(logic.getFilteredEventList()); + + deadlinesTableView.getTableView().setPlaceholder(placeholder); + deadlinesTableView.getTableView().setItems(logic.getFilteredDeadlineList()); + + archivesTableView.getTableView().setPlaceholder(placeholder); + archivesTableView.getTableView().setItems(logic.getFilteredArchiveList()); + + tabPane.getSelectionModel().selectedItemProperty().addListener((tabList, fromTab, toTab)->{ + this.raise(new TabChangedEvent(fromTab.getId(), toTab.getId())); + }); + } + + public void hide() { + primaryStage.hide(); + } + + private void setTitle(String appTitle) { + primaryStage.setTitle(appTitle); + } + + /** + * Sets the default size based on user preferences. + */ + protected void setWindowDefaultSize(UserPrefs prefs) { + primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); + primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); + if (prefs.getGuiSettings().getWindowCoordinates() != null) { + primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); + primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); + } + } + + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + //@@author A0138862W + private void fillInnerParts(Logic logic) { + commandBox = CommandBox.load(primaryStage, commandBoxPlaceholder, logic); + actionHistoryPane = ActionHistoryPane.load(primaryStage, actionHistoryPanePlaceholder, logic); + homeTableView = HomeTableView.load(primaryStage, homeTableViewPlaceholder, logic); + + tasksTableView = TasksTableView.load(primaryStage, tasksTableViewPlaceholder, logic); + + eventsTableView = EventsTableView.load(primaryStage, eventsTableViewPlaceholder, logic); + + deadlinesTableView = DeadlinesTableView.load(primaryStage, deadlinesTableViewPlaceholder, logic); + + archivesTableView = ArchivesTableView.load(primaryStage, archivesTableViewPlaceholder, logic); + } + //@@author + + /** + * Returns the current size and the position of the main Window. + */ + public GuiSettings getCurrentGuiSetting() { + return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), (int) primaryStage.getX(), (int) primaryStage.getY()); + } + + public void show() { + primaryStage.show(); + } + + // @@author A0124797R + /** + * update the number of task in each tab in the tab title + */ + protected void updateTabTitle() { + tabLst.get(INDEX_HOME).setText(NAME_TABS[INDEX_HOME] + + "(" + + logic.getFilteredTaskList().size() + + ")"); + tabLst.get(INDEX_TASKS).setText(NAME_TABS[INDEX_TASKS] + + "(" + + logic.getFilteredFloatingTaskList().size() + + ")"); + tabLst.get(INDEX_EVENTS).setText(NAME_TABS[INDEX_EVENTS] + + "(" + + logic.getFilteredEventList().size() + + ")"); + tabLst.get(INDEX_DEADLINES).setText(NAME_TABS[INDEX_DEADLINES] + + "(" + + logic.getFilteredDeadlineList().size() + + ")"); + tabLst.get(INDEX_ARCHIVES).setText(NAME_TABS[INDEX_ARCHIVES] + + "(" + + logic.getFilteredArchiveList().size() + + ")"); + } + + // @@author A0124797R + /** + * handle the switching of tabs when list/upcoming is used + */ + private void updateTab(String result) { + switch (result) { + case ListCommand.MESSAGE_SUCCESS: + tabPane.getSelectionModel().select(INDEX_HOME); + break; + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING: + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING_DEADLINE: + case UpcomingCommand.MESSAGE_SUCCESS_UPCOMING_EVENT: + tabPane.getSelectionModel().select(INDEX_HOME); + break; + case ListCommand.MESSAGE_SUCCESS_TASKS: + tabPane.getSelectionModel().select(INDEX_TASKS); + break; + case ListCommand.MESSAGE_SUCCESS_EVENTS: + tabPane.getSelectionModel().select(INDEX_EVENTS); + break; + case ListCommand.MESSAGE_SUCCESS_DEADLINES: + tabPane.getSelectionModel().select(INDEX_DEADLINES); + break; + case ListCommand.MESSAGE_SUCCESS_ARCHIVES: + tabPane.getSelectionModel().select(INDEX_ARCHIVES); + break; + } + } + + //@@author A0138862W + @Subscribe + private void handleNewResultAvailableEvent(NewResultAvailableEvent event){ + // updates the tab when a list command is called + this.updateTab(event.message); + logger.info("Update tab."); + } + + // @@author A0124797R + @Subscribe + private void handleTaskManagerChanged(TaskManagerChangedEvent event) { + updateTabTitle(); + logger.info("Update tab title."); + } + +} diff --git a/src/main/java/harmony/mastermind/ui/TasksTableView.java b/src/main/java/harmony/mastermind/ui/TasksTableView.java new file mode 100644 index 000000000000..c30bad394267 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/TasksTableView.java @@ -0,0 +1,164 @@ +package harmony.mastermind.ui; + +import org.ocpsoft.prettytime.PrettyTime; + +import harmony.mastermind.commons.util.FxViewUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.task.ReadOnlyTask; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class TasksTableView extends DefaultTableView { + + private static final String FXML = "TasksTableView.fxml"; + + private static final PrettyTime prettyTime = new PrettyTime(); + + private AnchorPane placeholder; + + private Logic logic; + + @FXML + private TableView tasksTableView; + + @FXML + private TableColumn indexColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn startDateColumn; + + @FXML + private TableColumn endDateColumn; + + @FXML + private TableColumn tagsColumn; + + @FXML + private TableColumn recurColumn; + + + public static TasksTableView load(Stage primaryStage, AnchorPane defaultTableViewPlaceholder, Logic logic){ + TasksTableView ui = UiPartLoader.loadUiPart(primaryStage, defaultTableViewPlaceholder, new TasksTableView()); + ui.configure(logic); + return ui; + } + + /** + * Initialize the displaying of tabs + */ + @FXML + private void initialize() { + this.initIndex(); + this.initName(); + this.initStartDate(); + this.initEndDate(); + this.initTags(); + this.initRecur(); + } + + + @Override + public void setPlaceholder(AnchorPane placeholder){ + this.placeholder = placeholder; + } + + @SuppressWarnings("unchecked") + @Override + public void setNode(Node node) { + this.tasksTableView = (TableView) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + protected void configure(Logic logic){ + this.logic = logic; + this.addToPlaceholder(); + this.registerAsAnEventHandler(this); + } + + private void addToPlaceholder(){ + placeholder.getChildren().add(tasksTableView); + FxViewUtil.applyAnchorBoundaryParameters(tasksTableView, 0, 0, 0, 0); + } + + @Override + public TableView getTableView(){ + return tasksTableView; + } + + /** + * Initializes the indexing of tasks + */ + protected void initIndex() { + indexColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_INDEX)); + indexColumn.setCellFactory(column -> renderIndexCell()); + } + + /** + * Initialize the Names of the tasks + */ + protected void initName() { + nameColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_NAME)); + nameColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + nameColumn.setCellFactory(col -> renderNameCell()); + } + + /** + * Initialize the start dates of the tasks + */ + protected void initStartDate() { + startDateColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_STARTDATE)); + startDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + startDateColumn.setCellFactory(col -> renderStartDateCell()); + + } + + /** + * Initialize the end dates of the tasks + */ + protected void initEndDate() { + endDateColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_ENDDATE)); + endDateColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + endDateColumn.setCellFactory(col -> renderEndDateCell()); + + } + + + /** + * Initialize the tags of the tasks + */ + protected void initTags() { + tagsColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_TAGS)); + tagsColumn.setCellValueFactory(cellValue -> new SimpleObjectProperty<>(cellValue.getValue())); + + tagsColumn.setCellFactory(col -> renderTagsCell()); + + } + + + /** + * Initialize a checkbox to determine whether task is recurring + */ + protected void initRecur() { + recurColumn.prefWidthProperty().bind(tasksTableView.widthProperty().multiply(WIDTH_MULTIPLIER_RECUR)); + recurColumn.setGraphic(new ImageView("file:src/main/resources/images/recur_white.png")); + recurColumn.setCellValueFactory(task -> new SimpleBooleanProperty(task.getValue().isRecur())); + recurColumn.setCellFactory(col -> renderRecurCell()); + } +} + diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/harmony/mastermind/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/harmony/mastermind/ui/Ui.java index e6a67fe8c027..eef19dcad0a2 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/harmony/mastermind/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package harmony.mastermind.ui; import javafx.stage.Stage; diff --git a/src/main/java/harmony/mastermind/ui/UiManager.java b/src/main/java/harmony/mastermind/ui/UiManager.java new file mode 100644 index 000000000000..95ca5be40b41 --- /dev/null +++ b/src/main/java/harmony/mastermind/ui/UiManager.java @@ -0,0 +1,93 @@ +package harmony.mastermind.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import harmony.mastermind.MainApp; +import harmony.mastermind.commons.core.ComponentManager; +import harmony.mastermind.commons.core.Config; +import harmony.mastermind.commons.core.LogsCenter; +import harmony.mastermind.commons.events.model.TaskManagerChangedEvent; +import harmony.mastermind.commons.events.storage.DataSavingExceptionEvent; +import harmony.mastermind.commons.events.ui.ExecuteCommandEvent; +import harmony.mastermind.commons.events.ui.HighlightLastActionedRowRequestEvent; +import harmony.mastermind.commons.events.ui.JumpToListRequestEvent; +import harmony.mastermind.commons.events.ui.ShowHelpRequestEvent; +import harmony.mastermind.commons.events.ui.ToggleActionHistoryEvent; +import harmony.mastermind.commons.util.StringUtil; +import harmony.mastermind.logic.Logic; +import harmony.mastermind.model.UserPrefs; +import javafx.scene.image.Image; +import javafx.stage.Stage; + +/** + * The manager of the UI component. + */ +public class UiManager extends ComponentManager implements Ui { + private static final Logger logger = LogsCenter.getLogger(UiManager.class); + private static final String ICON_APPLICATION = "/images/to_do_list_32.png"; + private final HelpPopup helpPopup; + + private Logic logic; + private Config config; + private UserPrefs prefs; + private MainWindow mainWindow; + + public UiManager(Logic logic, Config config, UserPrefs prefs) { + super(); + this.logic = logic; + this.config = config; + this.prefs = prefs; + helpPopup = new HelpPopup(); + } + + @Override + public void start(Stage primaryStage) { + logger.info("Starting UI..."); + primaryStage.setTitle(config.getAppTitle()); + + //Set the application icon. + primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + + try { + mainWindow = MainWindow.load(primaryStage, config, prefs, logic); + mainWindow.show(); //This should be called before creating other UI parts + + } catch (Throwable e) { + logger.severe(StringUtil.getDetails(e)); + } + } + + @Override + public void stop() { + prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); + //mainWindow.disposeAutoCompleteBinding(); + mainWindow.hide(); + } + + private Image getImage(String imagePath) { + return new Image(MainApp.class.getResourceAsStream(imagePath)); + } + + //==================== Event Handling Code ================================================================= + + @Subscribe + private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + } + + //@@author A0139194X + @Subscribe + private void handleShowHelpEvent(ShowHelpRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + helpPopup.injectData(event.getHelpEntries()); + helpPopup.show(mainWindow.getNode()); + } + + //@@author + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + } +} diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/harmony/mastermind/ui/UiPart.java similarity index 74% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/harmony/mastermind/ui/UiPart.java index 0a4ceb33e9b7..69909c66902f 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/harmony/mastermind/ui/UiPart.java @@ -1,13 +1,13 @@ -package seedu.address.ui; +package harmony.mastermind.ui; +import harmony.mastermind.commons.core.EventsCenter; +import harmony.mastermind.commons.events.BaseEvent; +import harmony.mastermind.commons.util.AppUtil; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.stage.Modality; import javafx.stage.Stage; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.BaseEvent; -import seedu.address.commons.util.AppUtil; /** * Base class for UI parts. @@ -57,22 +57,6 @@ public void setStage(Stage primaryStage) { } - /** - * Creates a modal dialog. - * @param title Title of the dialog. - * @param parentStage The owner stage of the dialog. - * @param scene The scene that will contain the dialog. - * @return the created dialog, not yet made visible. - */ - protected Stage createDialogStage(String title, Stage parentStage, Scene scene) { - Stage dialogStage = new Stage(); - dialogStage.setTitle(title); - dialogStage.initModality(Modality.WINDOW_MODAL); - dialogStage.initOwner(parentStage); - dialogStage.setScene(scene); - return dialogStage; - } - /** * Sets the given image as the icon for the primary stage of this UI Part. * @param iconSource e.g. {@code "/images/help_icon.png"} diff --git a/src/main/java/seedu/address/ui/UiPartLoader.java b/src/main/java/harmony/mastermind/ui/UiPartLoader.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPartLoader.java rename to src/main/java/harmony/mastermind/ui/UiPartLoader.java index f880685a5b15..2d7489d522e3 100644 --- a/src/main/java/seedu/address/ui/UiPartLoader.java +++ b/src/main/java/harmony/mastermind/ui/UiPartLoader.java @@ -1,10 +1,10 @@ -package seedu.address.ui; +package harmony.mastermind.ui; +import harmony.mastermind.MainApp; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import seedu.address.MainApp; /** * A utility class to load UiParts from FXML files. diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e4695..000000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -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_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 deleted file mode 100644 index 347a8359e0d5..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data){ - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index 95377b326fa6..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final ReadOnlyPerson newSelection; - - public PersonPanelSelectionChangedEvent(ReadOnlyPerson newSelection){ - this.newSelection = newSelection; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - public ReadOnlyPerson getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java deleted file mode 100644 index a7e40940b2c7..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; - -/** - * An event requesting to view the help page. - */ -public class ShowHelpRequestEvent extends BaseEvent { - - @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 deleted file mode 100644 index 4df1bc65cabb..000000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.logic.commands.CommandResult; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - */ - CommandResult execute(String commandText); - - /** Returns the filtered list of persons */ - ObservableList getFilteredPersonList(); - -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index ce4dc1903cff..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,41 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.parser.Parser; -import seedu.address.model.Model; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.storage.Storage; - -import java.util.logging.Logger; - -/** - * The main LogicManager of the app. - */ -public class LogicManager extends ComponentManager implements Logic { - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Parser parser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.parser = new Parser(); - } - - @Override - public CommandResult execute(String commandText) { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - Command command = parser.parseCommand(commandText); - command.setData(model); - return command.execute(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 2860a9ab2a85..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.HashSet; -import java.util.Set; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: NAME p/PHONE e/EMAIL a/ADDRESS [t/TAG]...\n" - + "Example: " + COMMAND_WORD - + " John Doe p/98765432 e/johnd@gmail.com a/311, Clementi Ave 2, #02-25 t/friends t/owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Convenience constructor using raw values. - * - * @throws IllegalValueException if any of the raw values are invalid - */ - public AddCommand(String name, String phone, String email, String address, Set tags) - throws IllegalValueException { - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(new Tag(tagName)); - } - this.toAdd = new Person( - new Name(name), - new Phone(phone), - new Email(email), - new Address(address), - new UniqueTagList(tagSet) - ); - } - - @Override - public CommandResult execute() { - assert model != null; - try { - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (UniquePersonList.DuplicatePersonException e) { - return new CommandResult(MESSAGE_DUPLICATE_PERSON); - } - - } - -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 522d57189f51..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,22 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.AddressBook; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - public ClearCommand() {} - - - @Override - public CommandResult execute() { - assert model != null; - model.resetData(AddressBook.getEmptyAddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java deleted file mode 100644 index 7c0ba2fd0161..000000000000 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.events.ui.IncorrectCommandAttemptedEvent; -import seedu.address.model.Model; - -/** - * Represents a command with hidden internal logic and the ability to be executed. - */ -public abstract class Command { - protected Model model; - - /** - * Constructs a feedback message to summarise an operation that displayed a listing of persons. - * - * @param displaySize used to generate summary - * @return summary message for persons displayed - */ - public static String getMessageForPersonListShownSummary(int displaySize) { - return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); - } - - /** - * Executes the command and returns the result message. - * - * @return feedback message of the operation result for display - */ - public abstract CommandResult execute(); - - /** - * Provides any needed dependencies to the command. - * Commands making use of any of these should override this method to gain - * access to the dependencies. - */ - public void setData(Model model) { - this.model = model; - } - - /** - * Raises an event to indicate an attempt to execute an incorrect command - */ - protected void indicateAttemptToExecuteIncorrectCommand() { - EventsCenter.getInstance().post(new IncorrectCommandAttemptedEvent(this)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java deleted file mode 100644 index f46f2f31353e..000000000000 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.commands; - -/** - * Represents the result of a command execution. - */ -public class CommandResult { - - public final String feedbackToUser; - - public CommandResult(String feedbackToUser) { - assert feedbackToUser != null; - this.feedbackToUser = feedbackToUser; - } - -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 1bfebe8912a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList.PersonNotFoundException; - -/** - * Deletes a person identified using it's last displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - public final int targetIndex; - - public DeleteCommand(int targetIndex) { - this.targetIndex = targetIndex; - } - - - @Override - public CommandResult execute() { - - UnmodifiableObservableList lastShownList = model.getFilteredPersonList(); - - if (lastShownList.size() < targetIndex) { - indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - ReadOnlyPerson personToDelete = lastShownList.get(targetIndex - 1); - - try { - model.deletePerson(personToDelete); - } catch (PersonNotFoundException pnfe) { - assert false : "The target person cannot be missing"; - } - - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index 1d61bf6cc857..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package seedu.address.logic.commands; - -import java.util.Set; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case sensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final Set keywords; - - public FindCommand(Set keywords) { - this.keywords = keywords; - } - - @Override - public CommandResult execute() { - model.updateFilteredPersonList(keywords); - return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java deleted file mode 100644 index 65af96940242..000000000000 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.logic.commands; - - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; - -/** - * Format full help instructions for every command for display. - */ -public class HelpCommand extends Command { - - public static final String COMMAND_WORD = "help"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; - - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; - - public HelpCommand() {} - - @Override - public CommandResult execute() { - EventsCenter.getInstance().post(new ShowHelpRequestEvent()); - return new CommandResult(SHOWING_HELP_MESSAGE); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 9bdd457a1b01..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package seedu.address.logic.commands; - - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - public ListCommand() {} - - @Override - public CommandResult execute() { - model.updateFilteredListToShowAll(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index 9ca0551f1951..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * Selects a person identified using it's last displayed index from the address book. - */ -public class SelectCommand extends Command { - - public final int targetIndex; - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - public SelectCommand(int targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute() { - - UnmodifiableObservableList lastShownList = model.getFilteredPersonList(); - - if (lastShownList.size() < targetIndex) { - indicateAttemptToExecuteIncorrectCommand(); - return new CommandResult(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex - 1)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex)); - - } - -} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java deleted file mode 100644 index 959b2cd0383c..000000000000 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ /dev/null @@ -1,192 +0,0 @@ -package seedu.address.logic.parser; - -import seedu.address.logic.commands.*; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.exceptions.IllegalValueException; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -/** - * Parses user input. - */ -public class Parser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - private static final Pattern PERSON_INDEX_ARGS_FORMAT = Pattern.compile("(?.+)"); - - private static final Pattern KEYWORDS_ARGS_FORMAT = - Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more keywords separated by whitespace - - private static final Pattern PERSON_DATA_ARGS_FORMAT = // '/' forward slashes are reserved for delimiter prefixes - Pattern.compile("(?[^/]+)" - + " (?p?)p/(?[^/]+)" - + " (?p?)e/(?[^/]+)" - + " (?p?)a/(?
[^/]+)" - + "(?(?: t/[^/]+)*)"); // variable number of tags - - public Parser() {} - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - */ - public Command parseCommand(String userInput) { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return prepareAdd(arguments); - - case SelectCommand.COMMAND_WORD: - return prepareSelect(arguments); - - case DeleteCommand.COMMAND_WORD: - return prepareDelete(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return prepareFind(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - return new IncorrectCommand(MESSAGE_UNKNOWN_COMMAND); - } - } - - /** - * Parses arguments in the context of the add person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareAdd(String args){ - final Matcher matcher = PERSON_DATA_ARGS_FORMAT.matcher(args.trim()); - // Validate arg string format - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - try { - return new AddCommand( - matcher.group("name"), - matcher.group("phone"), - matcher.group("email"), - matcher.group("address"), - getTagsFromArgs(matcher.group("tagArguments")) - ); - } catch (IllegalValueException ive) { - return new IncorrectCommand(ive.getMessage()); - } - } - - /** - * Extracts the new person's tags from the add command's tag arguments string. - * Merges duplicate tag strings. - */ - private static Set getTagsFromArgs(String tagArguments) throws IllegalValueException { - // no tags - if (tagArguments.isEmpty()) { - return Collections.emptySet(); - } - // replace first delimiter prefix, then split - final Collection tagStrings = Arrays.asList(tagArguments.replaceFirst(" t/", "").split(" t/")); - return new HashSet<>(tagStrings); - } - - /** - * Parses arguments in the context of the delete person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareDelete(String args) { - - Optional index = parseIndex(args); - if(!index.isPresent()){ - return new IncorrectCommand( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); - } - - return new DeleteCommand(index.get()); - } - - /** - * Parses arguments in the context of the select person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareSelect(String args) { - Optional index = parseIndex(args); - if(!index.isPresent()){ - return new IncorrectCommand( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - } - - return new SelectCommand(index.get()); - } - - /** - * Returns the specified index in the {@code command} IF a positive unsigned integer is given as the index. - * Returns an {@code Optional.empty()} otherwise. - */ - private Optional parseIndex(String command) { - final Matcher matcher = PERSON_INDEX_ARGS_FORMAT.matcher(command.trim()); - if (!matcher.matches()) { - return Optional.empty(); - } - - String index = matcher.group("targetIndex"); - if(!StringUtil.isUnsignedInteger(index)){ - return Optional.empty(); - } - return Optional.of(Integer.parseInt(index)); - - } - - /** - * Parses arguments in the context of the find person command. - * - * @param args full command args string - * @return the prepared command - */ - private Command prepareFind(String args) { - final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); - if (!matcher.matches()) { - return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, - FindCommand.MESSAGE_USAGE)); - } - - // keywords delimited by whitespace - final String[] keywords = matcher.group("keywords").split("\\s+"); - final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); - return new FindCommand(keywordSet); - } - -} \ No newline at end of file diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 298cc1b82ce8..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,163 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .equals comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - private final UniqueTagList tags; - - { - persons = new UniquePersonList(); - tags = new UniqueTagList(); - } - - public AddressBook() {} - - /** - * Persons and Tags are copied into this addressbook - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(toBeCopied.getUniquePersonList(), toBeCopied.getUniqueTagList()); - } - - /** - * Persons and Tags are copied into this addressbook - */ - public AddressBook(UniquePersonList persons, UniqueTagList tags) { - resetData(persons.getInternalList(), tags.getInternalList()); - } - - public static ReadOnlyAddressBook getEmptyAddressBook() { - return new AddressBook(); - } - -//// list overwrite operations - - public ObservableList getPersons() { - return persons.getInternalList(); - } - - public void setPersons(List persons) { - this.persons.getInternalList().setAll(persons); - } - - public void setTags(Collection tags) { - this.tags.getInternalList().setAll(tags); - } - - public void resetData(Collection newPersons, Collection newTags) { - setPersons(newPersons.stream().map(Person::new).collect(Collectors.toList())); - setTags(newTags); - } - - public void resetData(ReadOnlyAddressBook newData) { - resetData(newData.getPersonList(), newData.getTagList()); - } - -//// person-level operations - - /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. - * - * @throws UniquePersonList.DuplicatePersonException if an equivalent person already exists. - */ - public void addPerson(Person p) throws UniquePersonList.DuplicatePersonException { - syncTagsWithMasterList(p); - persons.add(p); - } - - /** - * Ensures that every tag in this person: - * - exists in the master list {@link #tags} - * - points to a Tag object in the master list - */ - private void syncTagsWithMasterList(Person person) { - final UniqueTagList personTags = person.getTags(); - tags.mergeFrom(personTags); - - // Create map with values = tag object references in the master list - final Map masterTagObjects = new HashMap<>(); - for (Tag tag : tags) { - masterTagObjects.put(tag, tag); - } - - // Rebuild the list of person tags using references from the master list - final Set commonTagReferences = new HashSet<>(); - for (Tag tag : personTags) { - commonTagReferences.add(masterTagObjects.get(tag)); - } - person.setTags(new UniqueTagList(commonTagReferences)); - } - - public boolean removePerson(ReadOnlyPerson key) throws UniquePersonList.PersonNotFoundException { - if (persons.remove(key)) { - return true; - } else { - throw new UniquePersonList.PersonNotFoundException(); - } - } - -//// tag-level operations - - public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { - tags.add(t); - } - -//// util methods - - @Override - public String toString() { - return persons.getInternalList().size() + " persons, " + tags.getInternalList().size() + " tags"; - // TODO: refine later - } - - @Override - public List getPersonList() { - return Collections.unmodifiableList(persons.getInternalList()); - } - - @Override - public List getTagList() { - return Collections.unmodifiableList(tags.getInternalList()); - } - - @Override - public UniquePersonList getUniquePersonList() { - return this.persons; - } - - @Override - public UniqueTagList getUniqueTagList() { - return this.tags; - } - - - @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.equals(((AddressBook) other).tags)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d14a27a93b5e..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,35 +0,0 @@ -package seedu.address.model; - -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; - -import java.util.Set; - -/** - * The API of the Model component. - */ -public interface Model { - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** Deletes the given person. */ - void deletePerson(ReadOnlyPerson target) throws UniquePersonList.PersonNotFoundException; - - /** Adds the given person */ - void addPerson(Person person) throws UniquePersonList.DuplicatePersonException; - - /** Returns the filtered person list as an {@code UnmodifiableObservableList} */ - UnmodifiableObservableList getFilteredPersonList(); - - /** Updates the filter of the filtered person list to show all persons */ - void updateFilteredListToShowAll(); - - /** Updates the filter of the filtered person list to filter by the given keywords*/ - void updateFilteredPersonList(Set keywords); - -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 869226d02bf1..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,153 +0,0 @@ -package seedu.address.model; - -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.UnmodifiableObservableList; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.core.ComponentManager; -import seedu.address.model.person.Person; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.person.UniquePersonList.PersonNotFoundException; - -import java.util.Set; -import java.util.logging.Logger; - -/** - * 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 filteredPersons; - - /** - * Initializes a ModelManager with the given AddressBook - * AddressBook and its variables should not be null - */ - public ModelManager(AddressBook src, UserPrefs userPrefs) { - super(); - assert src != null; - assert userPrefs != null; - - logger.fine("Initializing with address book: " + src + " and user prefs " + userPrefs); - - addressBook = new AddressBook(src); - filteredPersons = new FilteredList<>(addressBook.getPersons()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - public ModelManager(ReadOnlyAddressBook initialData, UserPrefs userPrefs) { - addressBook = new AddressBook(initialData); - filteredPersons = new FilteredList<>(addressBook.getPersons()); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - addressBook.resetData(newData); - indicateAddressBookChanged(); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(addressBook)); - } - - @Override - public synchronized void deletePerson(ReadOnlyPerson target) throws PersonNotFoundException { - addressBook.removePerson(target); - indicateAddressBookChanged(); - } - - @Override - public synchronized void addPerson(Person person) throws UniquePersonList.DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredListToShowAll(); - indicateAddressBookChanged(); - } - - //=========== Filtered Person List Accessors =============================================================== - - @Override - public UnmodifiableObservableList getFilteredPersonList() { - return new UnmodifiableObservableList<>(filteredPersons); - } - - @Override - public void updateFilteredListToShowAll() { - filteredPersons.setPredicate(null); - } - - @Override - public void updateFilteredPersonList(Set keywords){ - updateFilteredPersonList(new PredicateExpression(new NameQualifier(keywords))); - } - - private void updateFilteredPersonList(Expression expression) { - filteredPersons.setPredicate(expression::satisfies); - } - - //========== Inner classes/interfaces used for filtering ================================================== - - interface Expression { - boolean satisfies(ReadOnlyPerson person); - String toString(); - } - - private class PredicateExpression implements Expression { - - private final Qualifier qualifier; - - PredicateExpression(Qualifier qualifier) { - this.qualifier = qualifier; - } - - @Override - public boolean satisfies(ReadOnlyPerson person) { - return qualifier.run(person); - } - - @Override - public String toString() { - return qualifier.toString(); - } - } - - interface Qualifier { - boolean run(ReadOnlyPerson person); - String toString(); - } - - private class NameQualifier implements Qualifier { - private Set nameKeyWords; - - NameQualifier(Set nameKeyWords) { - this.nameKeyWords = nameKeyWords; - } - - @Override - public boolean run(ReadOnlyPerson person) { - return nameKeyWords.stream() - .filter(keyword -> StringUtil.containsIgnoreCase(person.getName().fullName, keyword)) - .findAny() - .isPresent(); - } - - @Override - public String toString() { - return "name=" + String.join(", ", nameKeyWords); - } - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index bfca099b1e81..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,30 +0,0 @@ -package seedu.address.model; - - -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import java.util.List; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - UniqueTagList getUniqueTagList(); - - UniquePersonList getUniquePersonList(); - - /** - * Returns an unmodifiable view of persons list - */ - List getPersonList(); - - /** - * Returns an unmodifiable view of tags list - */ - List getTagList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a2bd109c005e..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.person; - - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = "Person addresses can be in any format"; - public static final String ADDRESS_VALIDATION_REGEX = ".+"; - - public final String value; - - /** - * Validates given address. - * - * @throws IllegalValueException if given address string is invalid. - */ - public Address(String address) throws IllegalValueException { - assert address != null; - if (!isValidAddress(address)) { - throw new IllegalValueException(MESSAGE_ADDRESS_CONSTRAINTS); - } - this.value = address; - } - - /** - * Returns true if a given string is a valid person email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && this.value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} \ No newline at end of file diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 5da4d1078236..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,56 +0,0 @@ -package seedu.address.model.person; - - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - public static final String MESSAGE_EMAIL_CONSTRAINTS = - "Person emails should be 2 alphanumeric/period strings separated by '@'"; - public static final String EMAIL_VALIDATION_REGEX = "[\\w\\.]+@[\\w\\.]+"; - - public final String value; - - /** - * Validates given email. - * - * @throws IllegalValueException if given email address string is invalid. - */ - public Email(String email) throws IllegalValueException { - assert email != null; - email = email.trim(); - if (!isValidEmail(email)) { - throw new IllegalValueException(MESSAGE_EMAIL_CONSTRAINTS); - } - this.value = email; - } - - /** - * Returns if a given string is a valid person email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && this.value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 4f30033e70fe..000000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_NAME_CONSTRAINTS = "Person names should be spaces or alphanumeric characters"; - public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum} ]+"; - - public final String fullName; - - /** - * Validates given name. - * - * @throws IllegalValueException if given name string is invalid. - */ - public Name(String name) throws IllegalValueException { - assert name != null; - name = name.trim(); - if (!isValidName(name)) { - throw new IllegalValueException(MESSAGE_NAME_CONSTRAINTS); - } - this.fullName = name; - } - - /** - * Returns true if a given string is a valid person name. - */ - public static boolean isValidName(String test) { - return test.matches(NAME_VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && this.fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 03ffce7d2e79..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,90 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.util.CollectionUtil; -import seedu.address.model.tag.UniqueTagList; - -import java.util.Objects; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated. - */ -public class Person implements ReadOnlyPerson { - - private Name name; - private Phone phone; - private Email email; - private Address address; - - private UniqueTagList tags; - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, UniqueTagList tags) { - assert !CollectionUtil.isAnyNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags = new UniqueTagList(tags); // protect internal tags from changes in the arg list - } - - /** - * Copy constructor. - */ - public Person(ReadOnlyPerson source) { - this(source.getName(), source.getPhone(), source.getEmail(), source.getAddress(), source.getTags()); - } - - @Override - public Name getName() { - return name; - } - - @Override - public Phone getPhone() { - return phone; - } - - @Override - public Email getEmail() { - return email; - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public UniqueTagList getTags() { - return new UniqueTagList(tags); - } - - /** - * Replaces this person's tags with the tags in the argument tag list. - */ - public void setTags(UniqueTagList replacement) { - tags.setTags(replacement); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof ReadOnlyPerson // instanceof handles nulls - && this.isSameStateAs((ReadOnlyPerson) other)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - return getAsText(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index d27b2244b727..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.commons.exceptions.IllegalValueException; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - public static final String MESSAGE_PHONE_CONSTRAINTS = "Person phone numbers should only contain numbers"; - public static final String PHONE_VALIDATION_REGEX = "\\d+"; - - public final String value; - - /** - * Validates given phone number. - * - * @throws IllegalValueException if given phone string is invalid. - */ - public Phone(String phone) throws IllegalValueException { - assert phone != null; - phone = phone.trim(); - if (!isValidPhone(phone)) { - throw new IllegalValueException(MESSAGE_PHONE_CONSTRAINTS); - } - this.value = phone; - } - - /** - * Returns true if a given string is a valid person phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && this.value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/ReadOnlyPerson.java b/src/main/java/seedu/address/model/person/ReadOnlyPerson.java deleted file mode 100644 index d45be4b5fe36..000000000000 --- a/src/main/java/seedu/address/model/person/ReadOnlyPerson.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.model.person; - -import seedu.address.model.tag.UniqueTagList; - -/** - * A read-only immutable interface for a Person in the addressbook. - * Implementations should guarantee: details are present and not null, field values are validated. - */ -public interface ReadOnlyPerson { - - Name getName(); - Phone getPhone(); - Email getEmail(); - Address getAddress(); - - /** - * The returned TagList is a deep copy of the internal TagList, - * changes on the returned list will not affect the person's internal tags. - */ - UniqueTagList getTags(); - - /** - * Returns true if both have the same state. (interfaces cannot override .equals) - */ - default boolean isSameStateAs(ReadOnlyPerson other) { - return other == this // short circuit if same object - || (other != null // this is first to avoid NPE below - && other.getName().equals(this.getName()) // state checks here onwards - && other.getPhone().equals(this.getPhone()) - && other.getEmail().equals(this.getEmail()) - && other.getAddress().equals(this.getAddress())); - } - - /** - * Formats the person as text, showing all contact details. - */ - default String getAsText() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - - /** - * Returns a string representation of this Person's tags - */ - default String tagsString() { - final StringBuffer buffer = new StringBuffer(); - final String separator = ", "; - getTags().forEach(tag -> buffer.append(tag).append(separator)); - if (buffer.length() == 0) { - return ""; - } else { - return buffer.substring(0, buffer.length() - separator.length()); - } - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 263f1fcc7dd5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.model.person; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.exceptions.DuplicateDataException; - -import java.util.*; - -/** - * A list of persons 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 UniquePersonList implements Iterable { - - /** - * Signals that an operation would have violated the 'no duplicates' property of the list. - */ - public static class DuplicatePersonException extends DuplicateDataException { - protected DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } - } - - /** - * Signals that an operation targeting a specified person in the list would fail because - * there is no such matching person in the list. - */ - public static class PersonNotFoundException extends Exception {} - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Constructs empty PersonList. - */ - public UniquePersonList() {} - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(ReadOnlyPerson toCheck) { - assert toCheck != null; - return internalList.contains(toCheck); - } - - /** - * Adds a person to the list. - * - * @throws DuplicatePersonException if the person to add is a duplicate of an existing person in the list. - */ - public void add(Person toAdd) throws DuplicatePersonException { - assert toAdd != null; - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Removes the equivalent person from the list. - * - * @throws PersonNotFoundException if no such person could be found in the list. - */ - public boolean remove(ReadOnlyPerson toRemove) throws PersonNotFoundException { - assert toRemove != null; - final boolean personFoundAndDeleted = internalList.remove(toRemove); - if (!personFoundAndDeleted) { - throw new PersonNotFoundException(); - } - return personFoundAndDeleted; - } - - public ObservableList getInternalList() { - return internalList; - } - - @Override - public Iterator 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(); - } -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index ffe589ac3c4c..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -import java.io.IOException; -import java.util.Optional; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - String getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index 767490dbf8e2..000000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(UserPrefs userPrefs) throws IOException; - - @Override - String getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, FileNotFoundException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. - * Raises {@link DataSavingExceptionEvent} if there was an error during saving. - */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index a6f2eddcc802..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -import com.google.common.eventbus.Subscribe; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager extends ComponentManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private XmlAddressBookStorage addressBookStorage; - private JsonUserPrefStorage userPrefStorage; - - - public StorageManager(String addressBookFilePath, String userPrefsFilePath) { - super(); - this.addressBookStorage = new XmlAddressBookStorage(addressBookFilePath); - this.userPrefStorage = new JsonUserPrefStorage(userPrefsFilePath); - } - - // ================ UserPrefs methods ============================== - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(UserPrefs userPrefs) throws IOException { - userPrefStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public String getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, FileNotFoundException { - logger.fine("Attempting to read data from file: " + addressBookStorage.getAddressBookFilePath()); - - return addressBookStorage.readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - addressBookStorage.saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - - @Override - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); - try { - saveAddressBook(event.data); - } catch (IOException e) { - raise(new DataSavingExceptionEvent(e)); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index f2167ec201b4..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,68 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.*; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -import javax.xml.bind.annotation.XmlElement; -import java.util.ArrayList; -import java.util.List; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - @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 tagged = new ArrayList<>(); - - /** - * No-arg constructor for JAXB use. - */ - public XmlAdaptedPerson() {} - - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(ReadOnlyPerson 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 person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - final Name name = new Name(this.name); - final Phone phone = new Phone(this.phone); - final Email email = new Email(this.email); - final Address address = new Address(this.address); - final UniqueTagList tags = new UniqueTagList(personTags); - return new Person(name, phone, email, address, tags); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index 30cb00270cc4..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,73 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private String filePath; - - public XmlAddressBookStorage(String filePath){ - this.filePath = filePath; - } - - public String getAddressBookFilePath(){ - return filePath; - } - - /** - * Similar to {@link #readAddressBook()} - * @param filePath location of the data. Cannot be null - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(String filePath) throws DataConversionException, FileNotFoundException { - assert filePath != null; - - File addressBookFile = new File(filePath); - - if (!addressBookFile.exists()) { - logger.info("AddressBook file " + addressBookFile + " not found"); - return Optional.empty(); - } - - ReadOnlyAddressBook addressBookOptional = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); - - return Optional.of(addressBookOptional); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - assert addressBook != null; - assert filePath != null; - - File file = new File(filePath); - FileUtil.createIfMissing(file); - XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index 2a496e1494f8..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,86 +0,0 @@ -package seedu.address.storage; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.model.person.UniquePersonList; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook implements ReadOnlyAddressBook { - - @XmlElement - private List persons; - @XmlElement - private List tags; - - { - persons = new ArrayList<>(); - tags = new ArrayList<>(); - } - - /** - * Empty constructor required for marshalling - */ - public XmlSerializableAddressBook() {} - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - tags = src.getTagList(); - } - - @Override - public UniqueTagList getUniqueTagList() { - try { - return new UniqueTagList(tags); - } catch (UniqueTagList.DuplicateTagException e) { - e.printStackTrace(); - return null; - } - } - - @Override - public UniquePersonList getUniquePersonList() { - UniquePersonList lists = new UniquePersonList(); - for (XmlAdaptedPerson p : persons) { - try { - lists.add(p.toModelType()); - } catch (IllegalValueException e) { - - } - } - return lists; - } - - @Override - public List getPersonList() { - return persons.stream().map(p -> { - try { - return p.toModelType(); - } catch (IllegalValueException e) { - e.printStackTrace(); - return null; - } - }).collect(Collectors.toCollection(ArrayList::new)); - } - - @Override - public List getTagList() { - return Collections.unmodifiableList(tags); - } - -} 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 54b88318019b..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,68 +0,0 @@ -package seedu.address.ui; - -import javafx.event.Event; -import javafx.scene.Node; -import javafx.scene.layout.AnchorPane; -import javafx.scene.web.WebView; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart{ - - private static Logger logger = LogsCenter.getLogger(BrowserPanel.class); - private WebView browser; - - /** - * Constructor is kept private as {@link #load(AnchorPane)} is the only way to create a BrowserPanel. - */ - private BrowserPanel() { - - } - - @Override - public void setNode(Node node) { - //not applicable - } - - @Override - public String getFxmlPath() { - return null; //not applicable - } - - /** - * Factory method for creating a Browser Panel. - * This method should be called after the FX runtime is initialized and in FX application thread. - * @param placeholder The AnchorPane where the BrowserPanel must be inserted - */ - public static BrowserPanel load(AnchorPane placeholder){ - logger.info("Initializing browser"); - BrowserPanel browserPanel = new BrowserPanel(); - browserPanel.browser = new WebView(); - placeholder.setOnKeyPressed(Event::consume); // To prevent triggering events for typing inside the loaded Web page. - FxViewUtil.applyAnchorBoundaryParameters(browserPanel.browser, 0.0, 0.0, 0.0, 0.0); - placeholder.getChildren().add(browserPanel.browser); - return browserPanel; - } - - public void loadPersonPage(ReadOnlyPerson person) { - loadPage("https://www.google.com.sg/#safe=off&q=" + person.getName().fullName.replaceAll(" ", "+")); - } - - public void loadPage(String url){ - browser.getEngine().load(url); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java deleted file mode 100644 index 2e1409a3016c..000000000000 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ /dev/null @@ -1,114 +0,0 @@ -package seedu.address.ui; - -import com.google.common.eventbus.Subscribe; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.SplitPane; -import javafx.scene.control.TextField; -import javafx.scene.layout.AnchorPane; -import javafx.stage.Stage; -import seedu.address.commons.events.ui.IncorrectCommandAttemptedEvent; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.*; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -public class CommandBox extends UiPart { - private final Logger logger = LogsCenter.getLogger(CommandBox.class); - private static final String FXML = "CommandBox.fxml"; - - private AnchorPane placeHolderPane; - private AnchorPane commandPane; - private ResultDisplay resultDisplay; - String previousCommandTest; - - private Logic logic; - - @FXML - private TextField commandTextField; - private CommandResult mostRecentResult; - - public static CommandBox load(Stage primaryStage, AnchorPane commandBoxPlaceholder, - ResultDisplay resultDisplay, Logic logic) { - CommandBox commandBox = UiPartLoader.loadUiPart(primaryStage, commandBoxPlaceholder, new CommandBox()); - commandBox.configure(resultDisplay, logic); - commandBox.addToPlaceholder(); - return commandBox; - } - - public void configure(ResultDisplay resultDisplay, Logic logic) { - this.resultDisplay = resultDisplay; - this.logic = logic; - registerAsAnEventHandler(this); - } - - private void addToPlaceholder() { - SplitPane.setResizableWithParent(placeHolderPane, false); - placeHolderPane.getChildren().add(commandTextField); - FxViewUtil.applyAnchorBoundaryParameters(commandPane, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); - } - - @Override - public void setNode(Node node) { - commandPane = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Override - public void setPlaceholder(AnchorPane pane) { - this.placeHolderPane = pane; - } - - - @FXML - private void handleCommandInputChanged() { - //Take a copy of the command text - previousCommandTest = commandTextField.getText(); - - /* We assume the command is correct. If it is incorrect, the command box will be changed accordingly - * in the event handling code {@link #handleIncorrectCommandAttempted} - */ - setStyleToIndicateCorrectCommand(); - mostRecentResult = logic.execute(previousCommandTest); - resultDisplay.postMessage(mostRecentResult.feedbackToUser); - logger.info("Result: " + mostRecentResult.feedbackToUser); - } - - - /** - * Sets the command box style to indicate a correct command. - */ - private void setStyleToIndicateCorrectCommand() { - commandTextField.getStyleClass().remove("error"); - commandTextField.setText(""); - } - - @Subscribe - private void handleIncorrectCommandAttempted(IncorrectCommandAttemptedEvent event){ - logger.info(LogsCenter.getEventHandlingLogMessage(event,"Invalid command: " + previousCommandTest)); - setStyleToIndicateIncorrectCommand(); - restoreCommandText(); - } - - /** - * Restores the command box text to the previously entered command - */ - private void restoreCommandText() { - commandTextField.setText(previousCommandTest); - } - - /** - * Sets the command box style to indicate an error - */ - private void setStyleToIndicateIncorrectCommand() { - commandTextField.getStyleClass().add("error"); - } - -} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java deleted file mode 100644 index 45b765ab6a0c..000000000000 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.ui; - -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.layout.AnchorPane; -import javafx.scene.web.WebView; -import javafx.stage.Stage; -import seedu.address.commons.util.FxViewUtil; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -/** - * Controller for a help page - */ -public class HelpWindow extends UiPart { - - private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); - private static final String ICON = "/images/help_icon.png"; - private static final String FXML = "HelpWindow.fxml"; - private static final String TITLE = "Help"; - private static final String USERGUIDE_URL = - "https://github.com/se-edu/addressbook-level4/blob/master/docs/UserGuide.md"; - - private AnchorPane mainPane; - - private Stage dialogStage; - - public static HelpWindow load(Stage primaryStage) { - logger.fine("Showing help page about the application."); - HelpWindow helpWindow = UiPartLoader.loadUiPart(primaryStage, new HelpWindow()); - helpWindow.configure(); - return helpWindow; - } - - @Override - public void setNode(Node node) { - mainPane = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - private void configure(){ - Scene scene = new Scene(mainPane); - //Null passed as the parent stage to make it non-modal. - dialogStage = createDialogStage(TITLE, null, scene); - dialogStage.setMaximized(true); //TODO: set a more appropriate initial size - setIcon(dialogStage, ICON); - - WebView browser = new WebView(); - browser.getEngine().load(USERGUIDE_URL); - FxViewUtil.applyAnchorBoundaryParameters(browser, 0.0, 0.0, 0.0, 0.0); - mainPane.getChildren().add(browser); - } - - public void show() { - dialogStage.showAndWait(); - } -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 2c76aced3b04..000000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,196 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.control.MenuItem; -import javafx.scene.input.KeyCombination; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.ReadOnlyPerson; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String ICON = "/images/address_book_32.png"; - private static final String FXML = "MainWindow.fxml"; - public static final int MIN_HEIGHT = 600; - public static final int MIN_WIDTH = 450; - - private Logic logic; - - // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private StatusBarFooter statusBarFooter; - private CommandBox commandBox; - private Config config; - private UserPrefs userPrefs; - - // Handles to elements of this Ui container - private VBox rootLayout; - private Scene scene; - - private String addressBookName; - - @FXML - private AnchorPane browserPlaceholder; - - @FXML - private AnchorPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private AnchorPane personListPanelPlaceholder; - - @FXML - private AnchorPane resultDisplayPlaceholder; - - @FXML - private AnchorPane statusbarPlaceholder; - - - public MainWindow() { - super(); - } - - @Override - public void setNode(Node node) { - rootLayout = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { - - MainWindow mainWindow = UiPartLoader.loadUiPart(primaryStage, new MainWindow()); - mainWindow.configure(config.getAppTitle(), config.getAddressBookName(), config, prefs, logic); - return mainWindow; - } - - private void configure(String appTitle, String addressBookName, Config config, UserPrefs prefs, - Logic logic) { - - //Set dependencies - this.logic = logic; - this.addressBookName = addressBookName; - this.config = config; - this.userPrefs = prefs; - - //Configure the UI - setTitle(appTitle); - setIcon(ICON); - setWindowMinSize(); - setWindowDefaultSize(prefs); - scene = new Scene(rootLayout); - primaryStage.setScene(scene); - - setAccelerators(); - } - - private void setAccelerators() { - helpMenuItem.setAccelerator(KeyCombination.valueOf("F1")); - } - - void fillInnerParts() { - browserPanel = BrowserPanel.load(browserPlaceholder); - personListPanel = PersonListPanel.load(primaryStage, getPersonListPlaceholder(), logic.getFilteredPersonList()); - resultDisplay = ResultDisplay.load(primaryStage, getResultDisplayPlaceholder()); - statusBarFooter = StatusBarFooter.load(primaryStage, getStatusbarPlaceholder(), config.getAddressBookFilePath()); - commandBox = CommandBox.load(primaryStage, getCommandBoxPlaceholder(), resultDisplay, logic); - } - - private AnchorPane getCommandBoxPlaceholder() { - return commandBoxPlaceholder; - } - - private AnchorPane getStatusbarPlaceholder() { - return statusbarPlaceholder; - } - - private AnchorPane getResultDisplayPlaceholder() { - return resultDisplayPlaceholder; - } - - public AnchorPane getPersonListPlaceholder() { - return personListPanelPlaceholder; - } - - public void hide() { - primaryStage.hide(); - } - - private void setTitle(String appTitle) { - primaryStage.setTitle(appTitle); - } - - /** - * Sets the default size based on user preferences. - */ - protected void setWindowDefaultSize(UserPrefs prefs) { - primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); - primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); - if (prefs.getGuiSettings().getWindowCoordinates() != null) { - primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); - primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); - } - } - - private void setWindowMinSize() { - primaryStage.setMinHeight(MIN_HEIGHT); - primaryStage.setMinWidth(MIN_WIDTH); - } - - /** - * Returns the current size and the position of the main Window. - */ - public GuiSettings getCurrentGuiSetting() { - return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - } - - @FXML - public void handleHelp() { - HelpWindow helpWindow = HelpWindow.load(primaryStage); - helpWindow.show(); - } - - public void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - raise(new ExitAppRequestEvent()); - } - - public PersonListPanel getPersonListPanel() { - return this.personListPanel; - } - - public void loadPersonPage(ReadOnlyPerson person) { - browserPanel.loadPersonPage(person); - } - - public void releaseResources() { - browserPanel.freeResources(); - } -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 259e9ad0d333..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import seedu.address.model.person.ReadOnlyPerson; - -public class PersonCard extends UiPart{ - - private static final String FXML = "PersonListCard.fxml"; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private Label tags; - - private ReadOnlyPerson person; - private int displayedIndex; - - public PersonCard(){ - - } - - public static PersonCard load(ReadOnlyPerson person, int displayedIndex){ - PersonCard card = new PersonCard(); - card.person = person; - card.displayedIndex = displayedIndex; - return UiPartLoader.loadUiPart(card); - } - - @FXML - public void initialize() { - name.setText(person.getName().fullName); - id.setText(displayedIndex + ". "); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - tags.setText(person.tagsString()); - } - - public HBox getLayout() { - return cardPane; - } - - @Override - public void setNode(Node node) { - cardPane = (HBox)node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 27d9381c47b5..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,108 +0,0 @@ -package seedu.address.ui; - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.control.SplitPane; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.ReadOnlyPerson; -import seedu.address.commons.core.LogsCenter; - -import java.util.logging.Logger; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - private static final String FXML = "PersonListPanel.fxml"; - private VBox panel; - private AnchorPane placeHolderPane; - - @FXML - private ListView personListView; - - public PersonListPanel() { - super(); - } - - @Override - public void setNode(Node node) { - panel = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Override - public void setPlaceholder(AnchorPane pane) { - this.placeHolderPane = pane; - } - - public static PersonListPanel load(Stage primaryStage, AnchorPane personListPlaceholder, - ObservableList personList) { - PersonListPanel personListPanel = - UiPartLoader.loadUiPart(primaryStage, personListPlaceholder, new PersonListPanel()); - personListPanel.configure(personList); - return personListPanel; - } - - private void configure(ObservableList personList) { - setConnections(personList); - addToPlaceholder(); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void addToPlaceholder() { - SplitPane.setResizableWithParent(placeHolderPane, false); - placeHolderPane.getChildren().add(panel); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - public void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - class PersonListViewCell extends ListCell { - - public PersonListViewCell() { - } - - @Override - protected void updateItem(ReadOnlyPerson person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(PersonCard.load(person, getIndex() + 1).getLayout()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java deleted file mode 100644 index 37284ee6c696..000000000000 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.ui; - -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.scene.Node; -import javafx.scene.control.TextArea; -import javafx.scene.layout.AnchorPane; -import javafx.stage.Stage; -import seedu.address.commons.util.FxViewUtil; - -/** - * A ui for the status bar that is displayed at the header of the application. - */ -public class ResultDisplay extends UiPart { - public static final String RESULT_DISPLAY_ID = "resultDisplay"; - private static final String STATUS_BAR_STYLE_SHEET = "result-display"; - private TextArea resultDisplayArea; - private final StringProperty displayed = new SimpleStringProperty(""); - - private static final String FXML = "ResultDisplay.fxml"; - - private AnchorPane placeHolder; - - private AnchorPane mainPane; - - public static ResultDisplay load(Stage primaryStage, AnchorPane placeHolder) { - ResultDisplay statusBar = UiPartLoader.loadUiPart(primaryStage, placeHolder, new ResultDisplay()); - statusBar.configure(); - return statusBar; - } - - public void configure() { - resultDisplayArea = new TextArea(); - resultDisplayArea.setEditable(false); - resultDisplayArea.setId(RESULT_DISPLAY_ID); - resultDisplayArea.getStyleClass().removeAll(); - resultDisplayArea.getStyleClass().add(STATUS_BAR_STYLE_SHEET); - resultDisplayArea.setText(""); - resultDisplayArea.textProperty().bind(displayed); - FxViewUtil.applyAnchorBoundaryParameters(resultDisplayArea, 0.0, 0.0, 0.0, 0.0); - mainPane.getChildren().add(resultDisplayArea); - FxViewUtil.applyAnchorBoundaryParameters(mainPane, 0.0, 0.0, 0.0, 0.0); - placeHolder.getChildren().add(mainPane); - } - - @Override - public void setNode(Node node) { - mainPane = (AnchorPane) node; - } - - @Override - public void setPlaceholder(AnchorPane placeholder) { - this.placeHolder = placeholder; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - public void postMessage(String message) { - displayed.setValue(message); - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java deleted file mode 100644 index f74f66be6fc9..000000000000 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.ui; - -import com.google.common.eventbus.Subscribe; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.stage.Stage; -import org.controlsfx.control.StatusBar; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.util.FxViewUtil; - -import java.util.Date; -import java.util.logging.Logger; - -/** - * A ui for the status bar that is displayed at the footer of the application. - */ -public class StatusBarFooter extends UiPart { - private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); - private StatusBar syncStatus; - private StatusBar saveLocationStatus; - - private GridPane mainPane; - - @FXML - private AnchorPane saveLocStatusBarPane; - - @FXML - private AnchorPane syncStatusBarPane; - - private AnchorPane placeHolder; - - private static final String FXML = "StatusBarFooter.fxml"; - - public static StatusBarFooter load(Stage stage, AnchorPane placeHolder, String saveLocation) { - StatusBarFooter statusBarFooter = UiPartLoader.loadUiPart(stage, placeHolder, new StatusBarFooter()); - statusBarFooter.configure(saveLocation); - return statusBarFooter; - } - - public void configure(String saveLocation) { - addMainPane(); - addSyncStatus(); - setSyncStatus("Not updated yet in this session"); - addSaveLocation(); - setSaveLocation("./" + saveLocation); - registerAsAnEventHandler(this); - } - - private void addMainPane() { - FxViewUtil.applyAnchorBoundaryParameters(mainPane, 0.0, 0.0, 0.0, 0.0); - placeHolder.getChildren().add(mainPane); - } - - private void setSaveLocation(String location) { - this.saveLocationStatus.setText(location); - } - - private void addSaveLocation() { - this.saveLocationStatus = new StatusBar(); - FxViewUtil.applyAnchorBoundaryParameters(saveLocationStatus, 0.0, 0.0, 0.0, 0.0); - saveLocStatusBarPane.getChildren().add(saveLocationStatus); - } - - private void setSyncStatus(String status) { - this.syncStatus.setText(status); - } - - private void addSyncStatus() { - this.syncStatus = new StatusBar(); - FxViewUtil.applyAnchorBoundaryParameters(syncStatus, 0.0, 0.0, 0.0, 0.0); - syncStatusBarPane.getChildren().add(syncStatus); - } - - @Override - public void setNode(Node node) { - mainPane = (GridPane) node; - } - - @Override - public void setPlaceholder(AnchorPane placeholder) { - this.placeHolder = placeholder; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { - String lastUpdated = (new Date()).toString(); - logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); - setSyncStatus("Last Updated: " + lastUpdated); - } -} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java deleted file mode 100644 index 4a4dba3a2f6e..000000000000 --- a/src/main/java/seedu/address/ui/UiManager.java +++ /dev/null @@ -1,126 +0,0 @@ -package seedu.address.ui; - -import com.google.common.eventbus.Subscribe; -import javafx.application.Platform; -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; -import javafx.scene.image.Image; -import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; - -import java.util.logging.Logger; - -/** - * The manager of the UI component. - */ -public class UiManager extends ComponentManager implements Ui { - private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; - - private Logic logic; - private Config config; - private UserPrefs prefs; - private MainWindow mainWindow; - - public UiManager(Logic logic, Config config, UserPrefs prefs) { - super(); - this.logic = logic; - this.config = config; - this.prefs = prefs; - } - - @Override - public void start(Stage primaryStage) { - logger.info("Starting UI..."); - primaryStage.setTitle(config.getAppTitle()); - - //Set the application icon. - primaryStage.getIcons().add(getImage(ICON_APPLICATION)); - - try { - mainWindow = MainWindow.load(primaryStage, config, prefs, logic); - mainWindow.show(); //This should be called before creating other UI parts - mainWindow.fillInnerParts(); - - } catch (Throwable e) { - logger.severe(StringUtil.getDetails(e)); - showFatalErrorDialogAndShutdown("Fatal error during initializing", e); - } - } - - @Override - public void stop() { - prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); - mainWindow.hide(); - mainWindow.releaseResources(); - } - - private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { - final String content = details + ":\n" + cause.toString(); - showAlertDialogAndWait(AlertType.ERROR, "File Op Error", description, content); - } - - private Image getImage(String imagePath) { - return new Image(MainApp.class.getResourceAsStream(imagePath)); - } - - void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { - showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); - } - - private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, - String contentText) { - final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); - alert.initOwner(owner); - alert.setTitle(title); - alert.setHeaderText(headerText); - alert.setContentText(contentText); - - alert.showAndWait(); - } - - private void showFatalErrorDialogAndShutdown(String title, Throwable e) { - logger.severe(title + " " + e.getMessage() + StringUtil.getDetails(e)); - showAlertDialogAndWait(Alert.AlertType.ERROR, title, e.getMessage(), e.toString()); - Platform.exit(); - System.exit(1); - } - - //==================== Event Handling Code ================================================================= - - @Subscribe - private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - showFileOperationAlertAndWait("Could not save data", "Could not save data to file", event.exception); - } - - @Subscribe - private void handleShowHelpEvent(ShowHelpRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.handleHelp(); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getPersonListPanel().scrollTo(event.targetIndex); - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event){ - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.loadPersonPage(event.getNewSelection()); - } - -} diff --git a/src/main/resources/images/recur_white.png b/src/main/resources/images/recur_white.png new file mode 100644 index 000000000000..1c841aaa71e2 Binary files /dev/null and b/src/main/resources/images/recur_white.png differ diff --git a/src/main/resources/images/recurring.png b/src/main/resources/images/recurring.png new file mode 100644 index 000000000000..053da53155c8 Binary files /dev/null and b/src/main/resources/images/recurring.png differ diff --git a/src/main/resources/images/to_do_list_32.png b/src/main/resources/images/to_do_list_32.png new file mode 100644 index 000000000000..1116add38b94 Binary files /dev/null and b/src/main/resources/images/to_do_list_32.png differ diff --git a/src/main/resources/view/ActionHistoryEntry.fxml b/src/main/resources/view/ActionHistoryEntry.fxml new file mode 100644 index 000000000000..125cf303f50c --- /dev/null +++ b/src/main/resources/view/ActionHistoryEntry.fxml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ActionHistoryPane.fxml b/src/main/resources/view/ActionHistoryPane.fxml new file mode 100644 index 000000000000..368a6a6e9732 --- /dev/null +++ b/src/main/resources/view/ActionHistoryPane.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + +