Skip to content

Commit

Permalink
Merge pull request #162 from Cary-Xx/finalDevelop
Browse files Browse the repository at this point in the history
Final develop
  • Loading branch information
Cary-Xx committed Nov 11, 2019
2 parents f9e5617 + 0b94110 commit 6a6599b
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 85 deletions.
128 changes: 44 additions & 84 deletions docs/DeveloperGuide.adoc
Expand Up @@ -155,88 +155,6 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa

This section describes some noteworthy details on how certain features are implemented.

// tag::undoredo[]
=== [Proposed] Undo/Redo feature
==== Proposed Implementation

The undo/redo mechanism is facilitated by `VersionedAddressBook`.
It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`.
Additionally, it implements the following operations:

* `VersionedAddressBook#commit()` -- Saves the current address book state in its history.
* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history.
* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history.

These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.

Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.

image::UndoRedoState0.png[]

Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.

image::UndoRedoState1.png[]

Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.

image::UndoRedoState2.png[]

[NOTE]
If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.

Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.

image::UndoRedoState3.png[]

[NOTE]
If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.

The following sequence diagram shows how the undo operation works:

image::UndoSequenceDiagram.png[]

NOTE: The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.

[NOTE]
If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.

image::UndoRedoState4.png[]

Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow.

image::UndoRedoState5.png[]

The following activity diagram summarizes what happens when a user executes a new command:

image::CommitActivityDiagram.png[]

==== Design Considerations

===== Aspect: How undo & redo executes

* **Alternative 1 (current choice):** Saves the entire address book.
** Pros: Easy to implement.
** Cons: May have performance issues in terms of memory usage.
* **Alternative 2:** Individual command knows how to undo/redo by itself.
** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
** Cons: We must ensure that the implementation of each individual command are correct.

===== Aspect: Data structure to support the undo/redo commands

* **Alternative 1 (current choice):** Use a list to store the history of address book states.
** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`.
* **Alternative 2:** Use `HistoryManager` for undo/redo
** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.
** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things.
// end::undoredo[]

=== AutoComplete Feature
==== Implementation
Autocomplete is facilitated by several parts.
Expand All @@ -250,7 +168,7 @@ The Ui part is implemented through `java.seedu.address.ui.QueryCard` on top of `
Given below is an example usage scenario and how the autocomplete mechanism behaves at each step. (p.s. details are
omitted)

Step 1. The user launch MYMorise and the user will be prompted to enter a command as shown in the command box.
Step 1. The user launches MYMorise and the user will be prompted to enter a command as shown in the command box.

Step 2. User enter `a` and the listener is triggered. Then `AutoComplete#initAc()` and
`AutoComplete#getSuggestions()` is invoked.
Expand All @@ -260,7 +178,7 @@ the database and then construct an `AutocompleteModel` with the vocabulary read.

Step 4. `getSuggestions(input)` calls `AutocompleteModel#allMatches()` which utilises the improved version of binary
search algorithm `BinarySearch`. The algorithm will return the first and last index of potential matched results.
Since the result is based on a pre-order for sorting, all the words inside this range will be the qualify ones.
Since the result is based on a pre-order for sorting, all the words inside this range will be the qualified ones.

Step 5. The listview of `QueryCard` will be updated based on the words and weights given and attached to the
`TextField`.
Expand All @@ -273,6 +191,48 @@ The following activity diagram summarizes what happens when a user enter somethi

image::AutocompleteActivityDiagram.png[]

==== Design Considerations
===== Aspect: How Autocomplete works
In terms of the retrieving suggestion list, an enhanced binary search algorithm is used. Since a word (which
represents a autocomplete term) has a name and weight, the default "vocabulary" will be first sort based on weight
and for same weight, sort alphanumerically.

In terms of replacing the target term to the `TextField`, 2 approaches are proposed

* **Approach 1 (current choice)**: 2 listeners were added. 1) 1st listener for `TextFormatter`, this is especially
utilised
to retrieving the updated cursor position, which is where it outperforms. 2) 2nd listener for
`TextInputControl`
to detect the text change of `TextField`. Then combined with previously returned caret position, we can replace the
corresponding position with target term (the one user selected)

* **Approach 2**. 1 listener for `caretProperty` is added to detect the change of position of caret (i.e., cursor).
This approach is simpler with regard to logic but more complicated during implementation. Since caret position change
does not necessarily mean textField change, therefore extra check would be required, also resulting in
potential inaccuracy.

In terms of user interaction, different listeners are added for different valid actions (e.g. press TAB, ENTER and
navigate using UP and DOWN) to make user benefit by typing faster.

=== History Feature
==== Implementation
History is mainly facilitated by `CommandHistory` with `HistoryPointer`. When the app starts, a `CommandHistory`
instance is created and any command executed (no matter valid or not) will be saved to a list of history commands.
And when the user calls the `history`, the overall history list will display on the `resultDisplayPanel`. And when
user press *F3* and *F4* to navigate through the history list, the `HistoryPointer` will point to corresponding
history.

Similar to Section 3.1, the history displayed on `textField` is facilitated by `KeyEventListener`. When the keyinput
event of *F3* or *F4* is triggered, it will navigate to previous input and next input correspondingly.

The following class diagram illustrates the interaction between `historyCommand` and other parts:

image::HistoryClassDiagram.png[]

The following sequence diagram illustrates the flow of how history commands works:



=== Currency Conversion

The Currency conversion is achieved by having a default base currency that all expenses and budgets use if one is not specified. The expenses that are stored
Expand Down
2 changes: 1 addition & 1 deletion docs/UserGuide.adoc
Expand Up @@ -45,7 +45,7 @@ functionalities. More importantly, MYMorise is *optimized for those who prefer t
. Copy the jar file to the folder you want to use as the home folder for MYMorise.
. Run `java -jar path_to_folder/MYMorise.jar` in your CLI. The GUI should appear in a few seconds.
+
image::Ui.png[width="790"]
image::UIUG.png[width="790"]
+
. Type the command in the command box and press kbd:[Enter] to execute it. +
e.g. typing *`help`* and pressing kbd:[Enter] will open the help window.
Expand Down
40 changes: 40 additions & 0 deletions docs/diagrams/HistoryClassDiagram.puml
@@ -0,0 +1,40 @@
@startuml
!include style.puml
skinparam arrowThickness 1.1
skinparam arrowColor LOGIC_COLOR_T4
skinparam classBackgroundColor LOGIC_COLOR

package Logic {


package Command {
Class CommandResult
Class "{abstract}\nCommand" as Command
}

Interface Logic <<Interface>>
Class LogicManager
Class CommandHistory

LogicManager --> CommandHistory
}

package Model{
Class HiddenModel #FFFFFF
}

Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic

LogicManager .up.|> Logic

HistoryCommand -up-|> Command
LogicManager .left.> Command : executes >

LogicManager --> Model
Command .right.> Model

Logic ..> CommandResult
LogicManager .down.> CommandResult
Command .up.> CommandResult
@enduml
34 changes: 34 additions & 0 deletions docs/diagrams/HistorySequenceDiagram.puml
@@ -0,0 +1,34 @@
@startuml
!include style.puml

box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
participant "h:HistoryCommand" as HistoryCommand LOGIC_COLOR
participant "c:CommandHistory" as CommandHistory LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
end box

[-> LogicManager : execute("history")
activate LogicManager

LogicManager -> HistoryCommand : execute(model, history)
activate HistoryCommand

HistoryCommand -> CommandHistory : getHistory(history)
activate CommandHistory

CommandHistory -> HistoryCommand : list<History>
deactivate CommandHistory

HistoryCommand -> CommandResult : list<History>
activate CommandResult

CommandResult --> HistoryCommand : commandResult
deactivate CommandResult

HistoryCommand --> LogicManager : commandResult
deactivate HistoryCommand

[<--LogicManager
deactivate LogicManager
@enduml
Binary file modified docs/images/AutocompleteActivityDiagram.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/AutocompleteSequenceDiagram.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/HistoryClassDiagram.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/HistorySequenceDiagram.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/UIUG.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6a6599b

Please sign in to comment.