From f13f1a751fbb5243d6a3f4b06772ea6583dacece Mon Sep 17 00:00:00 2001 From: li-kai Date: Mon, 7 Nov 2016 23:58:47 +0800 Subject: [PATCH] Build collated --- collated/A0092382A.md | 750 ---- collated/A0135805H.md | 3765 ---------------- collated/A0135805Hreuse.md | 23 - collated/A0135805Hreused.md | 351 -- collated/A0135805Hunused.md | 15 - collated/A0135817B.md | 3798 ----------------- collated/A0139021U.md | 790 ---- collated/A0315805H.md | 239 -- collated/docs/A0092382A.md | 353 ++ collated/docs/A0135805H.md | 420 ++ collated/docs/A0135805Hreused.md | 17 + collated/docs/A0135817B.md | 1480 +++++++ collated/docs/A0135817Breused.md | 1061 +++++ collated/docs/A0139021U.md | 310 ++ collated/docs/A0139021Ureused.md | 857 ++++ collated/generated.md | 793 ---- collated/main/A0092382A.md | 501 +++ collated/main/A0135805H.md | 3235 ++++++++++++++ collated/main/A0135805Hreused.md | 514 +++ collated/main/A0135817B.md | 2107 +++++++++ .../A0135817Breused.md} | 43 +- collated/main/A0139021U.md | 532 +++ collated/{ => main}/A0139021Ureused.md | 100 +- collated/main/generated.md | 348 ++ collated/main/reused.md | 202 + collated/reused.md | 894 ---- collated/test/A0092382A.md | 261 ++ collated/test/A0135805H.md | 2602 +++++++++++ collated/test/A0135805Hreused.md | 131 + collated/test/A0135805Hunused.md | 47 + collated/test/A0135817B.md | 1944 +++++++++ collated/test/A0135817Breused.md | 676 +++ collated/test/A0139021U.md | 487 +++ collated/test/reused.md | 247 ++ src/test/data/ManualTesting/SampleData.xml | 559 +++ src/test/data/ManualTesting/TestScript.md | 164 + 36 files changed, 19189 insertions(+), 11427 deletions(-) delete mode 100644 collated/A0092382A.md delete mode 100644 collated/A0135805H.md delete mode 100644 collated/A0135805Hreuse.md delete mode 100644 collated/A0135805Hreused.md delete mode 100644 collated/A0135805Hunused.md delete mode 100644 collated/A0135817B.md delete mode 100644 collated/A0139021U.md delete mode 100644 collated/A0315805H.md create mode 100644 collated/docs/A0092382A.md create mode 100644 collated/docs/A0135805H.md create mode 100644 collated/docs/A0135805Hreused.md create mode 100644 collated/docs/A0135817B.md create mode 100644 collated/docs/A0135817Breused.md create mode 100644 collated/docs/A0139021U.md create mode 100644 collated/docs/A0139021Ureused.md delete mode 100644 collated/generated.md create mode 100644 collated/main/A0092382A.md create mode 100644 collated/main/A0135805H.md create mode 100644 collated/main/A0135805Hreused.md create mode 100644 collated/main/A0135817B.md rename collated/{A0135817Breuse.md => main/A0135817Breused.md} (56%) create mode 100644 collated/main/A0139021U.md rename collated/{ => main}/A0139021Ureused.md (53%) create mode 100644 collated/main/generated.md create mode 100644 collated/main/reused.md delete mode 100644 collated/reused.md create mode 100644 collated/test/A0092382A.md create mode 100644 collated/test/A0135805H.md create mode 100644 collated/test/A0135805Hreused.md create mode 100644 collated/test/A0135805Hunused.md create mode 100644 collated/test/A0135817B.md create mode 100644 collated/test/A0135817Breused.md create mode 100644 collated/test/A0139021U.md create mode 100644 collated/test/reused.md create mode 100644 src/test/data/ManualTesting/SampleData.xml create mode 100644 src/test/data/ManualTesting/TestScript.md diff --git a/collated/A0092382A.md b/collated/A0092382A.md deleted file mode 100644 index 3d41b67c9b25..000000000000 --- a/collated/A0092382A.md +++ /dev/null @@ -1,750 +0,0 @@ -# A0092382A -###### \build\resources\main\style\DefaultStyle.css -``` css -/*Ongoing*/ -.ongoing { - -fx-background-color: #388E3C; -} - -.ongoing .label { - -fx-text-fill: #FFFFFF; -} - -.ongoing .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #388E3C; -} - -.ongoing .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.ongoing .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.ongoing .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \classes\production\main\style\DefaultStyle.css -``` css -/*Ongoing*/ -.ongoing { - -fx-background-color: #388E3C; -} - -.ongoing .label { - -fx-text-fill: #FFFFFF; -} - -.ongoing .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #388E3C; -} - -.ongoing .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.ongoing .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.ongoing .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \src\main\java\seedu\todo\commons\util\TimeUtil.java -``` java - public boolean isOngoing(LocalDateTime startTime, LocalDateTime endTime) { - if (endTime == null) { - logger.log(Level.WARNING, "endTime in isOngoing(..., ...) is null."); - return false; - } - - if (startTime == null) { - logger.log(Level.WARNING, "startTime in isOngoing(..., ...) is null"); - return false; - } - - return LocalDateTime.now(clock).isAfter(startTime) && LocalDateTime.now(clock).isBefore(endTime); - } - -``` -###### \src\main\java\seedu\todo\logic\commands\CompleteCommand.java -``` java -public class CompleteCommand extends BaseCommand { - private static final String VERB_COMPLETE = "marked complete"; - private static final String VERB_INCOMPLETE = "marked incomplete"; - - private Argument index = new IntArgument("index"); - - private Argument updateAllFlag = new StringArgument("all").flag("all"); - - @Override - protected Parameter[] getArguments() { - return new Parameter[] { index, updateAllFlag }; - } - - @Override - public String getCommandName() { - return "complete"; - } - - @Override - protected void validateArguments() { - if (updateAllFlag.hasBoundValue() && index.hasBoundValue()) { - errors.put("You must either specify an index or an /all flag, not both!"); - } else if (!index.hasBoundValue() && !updateAllFlag.hasBoundValue()) { - errors.put("You must specify an index or a /all flag. You have specified none!"); - } - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Mark task as completed", getCommandName(), getArgumentSummary())); - } - - @Override - public CommandResult execute() throws ValidationException { - if (index.hasBoundValue()) { - ImmutableTask task = this.model.update(index.getValue(), t -> t.setCompleted(!t.isCompleted())); - eventBus.post(new HighlightTaskEvent(task)); - String feedback = task.isCompleted() ? CompleteCommand.VERB_COMPLETE : CompleteCommand.VERB_INCOMPLETE; - return taskSuccessfulResult(task.getTitle(), feedback); - } else { - this.model.updateAll(t -> t.setCompleted(true)); - return new CommandResult("All tasks marked as completed"); - } - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\EditCommand.java -``` java -public class EditCommand extends BaseCommand { - private static final String VERB = "edited"; - - // These parameters will be sorted out manually by overriding setPositionalArgument - private Argument index = new IntArgument("index").required(); - private Argument title = new StringArgument("title"); - - private Argument description = new StringArgument("description") - .flag("m"); - - private Argument pin = new FlagArgument("pin") - .flag("p"); - - private Argument location = new StringArgument("location") - .flag("l"); - - private Argument date = new DateRangeArgument("date") - .flag("d"); - - @Override - protected Parameter[] getArguments() { - return new Parameter[] { index, title, date, description, pin, location }; - } - - @Override - public String getCommandName() { - return "edit"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Edit task", getCommandName(), - getArgumentSummary())); - - } - - @Override - protected void setPositionalArgument(String argument) { - String[] tokens = argument.trim().split("\\s+", 2); - Parameter[] positionals = new Parameter[]{ index, title }; - - for (int i = 0; i < tokens.length; i++) { - try { - positionals[i].setValue(tokens[i].trim()); - } catch (IllegalValueException e) { - errors.put(positionals[i].getName(), e.getMessage()); - } - } - } - - @Override - public CommandResult execute() throws ValidationException { - ImmutableTask editedTask = this.model.update(index.getValue(), task -> { - if (title.hasBoundValue()) { - task.setTitle(title.getValue()); - } - - if (description.hasBoundValue()) { - task.setDescription(description.getValue()); - } - - if (pin.hasBoundValue()) { - task.setPinned(pin.getValue()); - } - - if (location.hasBoundValue()) { - task.setLocation(location.getValue()); - } - - if (date.hasBoundValue()) { - task.setStartTime(date.getValue().getStartTime()); - task.setEndTime(date.getValue().getEndTime()); - } - }); - eventBus.post(new HighlightTaskEvent(editedTask)); - if (description.hasBoundValue()) { - eventBus.post(new ExpandCollapseTaskEvent(editedTask)); - } - return taskSuccessfulResult(editedTask.getTitle(), EditCommand.VERB); - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\PinCommand.java -``` java -public class PinCommand extends BaseCommand { - static private final String PIN = "pinned"; - static private final String UNPIN = "unpinned"; - - private Argument index = new IntArgument("index").required(); - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ index }; - } - - @Override - public String getCommandName() { - return "pin"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Pin task to top of list", getCommandName(), - getArgumentSummary())); - } - - @Override - public CommandResult execute() throws ValidationException { - ImmutableTask task = this.model.update(index.getValue(), t -> t.setPinned(!t.isPinned())); - String verb = task.isPinned() ? PinCommand.PIN : PinCommand.UNPIN; - eventBus.post(new HighlightTaskEvent(task)); - return taskSuccessfulResult(task.getTitle(), verb); - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\ViewCommand.java -``` java -public class ViewCommand extends BaseCommand { - private static final String FEEDBACK_FORMAT = "Displaying %s view"; - - private Argument view = new StringArgument("view").required(); - - private TaskViewFilter viewSpecified; - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ view }; - } - - @Override - public String getCommandName() { - return "view"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Switch tabs", getCommandName(), getArgumentSummary())); - } - - @Override - protected void validateArguments(){ - TaskViewFilter[] viewArray = TaskViewFilter.all(); - String viewSpecified = view.getValue().trim().toLowerCase(); - - for (TaskViewFilter filter : viewArray) { - String viewName = filter.name; - char shortcut = viewName.charAt(filter.shortcutCharPosition); - boolean matchesShortcut = viewSpecified.length() == 1 && viewSpecified.charAt(0) == shortcut; - - if (viewName.contentEquals(viewSpecified) || matchesShortcut) { - this.viewSpecified = filter; - return; - } - } - - String error = String.format("The view %s does not exist", view.getValue()); - errors.put("view", error); - } - - @Override - public CommandResult execute() throws ValidationException { - model.view(viewSpecified); - String feedback = String.format(ViewCommand.FEEDBACK_FORMAT, viewSpecified); - return new CommandResult(feedback); - } - -} -``` -###### \src\main\java\seedu\todo\model\property\TaskViewFilter.java -``` java -public class TaskViewFilter { - private static final Comparator CHRONOLOGICAL = (a, b) -> ComparisonChain.start() - .compare(a.getEndTime().orElse(null), b.getEndTime().orElse(null), Ordering.natural().nullsLast()) - .result(); - - private static final Comparator LAST_UPDATED = (a, b) -> - b.getCreatedAt().compareTo(a.getCreatedAt()); - - public static final TaskViewFilter DEFAULT = new TaskViewFilter("all", - null, LAST_UPDATED); - - public static final TaskViewFilter INCOMPLETE = new TaskViewFilter("incomplete", - task -> !task.isCompleted(), CHRONOLOGICAL); - - public static final TaskViewFilter DUE_SOON = new TaskViewFilter("due soon", - task -> !task.isCompleted() && !task.isEvent() && task.getEndTime().isPresent(), CHRONOLOGICAL); - - public static final TaskViewFilter EVENTS = new TaskViewFilter("events", - ImmutableTask::isEvent, CHRONOLOGICAL); - - public static final TaskViewFilter COMPLETED = new TaskViewFilter("completed", - ImmutableTask::isCompleted, LAST_UPDATED); - - public final String name; - - public final Predicate filter; - - public final Comparator sort; - - public final int shortcutCharPosition; - - public TaskViewFilter(String name, Predicate filter, Comparator sort) { - this(name, filter, sort, 0); - } - - public TaskViewFilter(String name, Predicate filter, Comparator sort, int underlineCharPosition) { - this.name = name; - this.filter = filter; - this.sort = sort; - this.shortcutCharPosition = underlineCharPosition; - } - - public static TaskViewFilter[] all() { - return new TaskViewFilter[]{ - DEFAULT, COMPLETED, INCOMPLETE, EVENTS, DUE_SOON, - }; - } - - @Override - public String toString() { - return name; - } -} -``` -###### \src\main\java\seedu\todo\model\TodoList.java -``` java - @Override - public void updateAll(List indexes, Consumer update) throws ValidationException { - for (Integer x: indexes) { - MutableTask task = tasks.get(x); - ValidationTask validationTask = new ValidationTask(task); - update.accept(validationTask); - validationTask.validate(); - } - - for (Integer i : indexes) { - MutableTask task = tasks.get(i); - update.accept(task); - } - - saveTodoList(); - - } - - - @Override - public void save(String location) throws ValidationException { - try { - storage.save(this, location); - } catch (IOException e) { - String message = String.format(TodoList.FILE_SAVE_ERROR_FORMAT, e.getMessage()); - throw new ValidationException(message); - } - } - - @Override - public void load(String location) throws ValidationException { - try { - setTasks(storage.read(location).getTasks()); - } catch (DataConversionException e) { - throw new ValidationException(TodoList.INCORRECT_FILE_FORMAT_FORMAT); - } catch (FileNotFoundException e) { - String message = String.format(TodoList.FILE_NOT_FOUND_FORMAT, location); - throw new ValidationException(message); - } - } - - @Override - public void setTasks(List todoList) { - setTasks(todoList, true); - } - - /** - * We have a private version of setTasks because we also need to setTask during initialization, - * but we don't want the list to be save during init (where we presumably got the data from) - */ - private void setTasks(List todoList, boolean persistToStorage) { - this.tasks.clear(); - this.tasks.addAll(todoList.stream().map(Task::new).collect(Collectors.toList())); - - if (persistToStorage) { - saveTodoList(); - } - } - - @Override - public ObservableList getObservableList() { - return new UnmodifiableObservableList<>(tasks); - } - - @Override - public List getTasks() { - return Collections.unmodifiableList(tasks); - } - - private class UpdateEventTask extends TimerTask { - @Override - public void run() { - updateEventStatus(); - } - } - -} -``` -###### \src\main\java\seedu\todo\model\TodoListModel.java -``` java - /** - * Carries out the specified update in the fields of all visible tasks. Mutation of all {@link Task} - * objects should only be done in the update lambda. The lambda takes in a single parameter, - * a {@link MutableTask}, and does not expect any return value, as per the {@link update} command. Note that - * the 'All' in this case refers to all the indices specified by the accompanying list of indices. - * - *
todo.updateAll (List tasks, t -> {
-     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline of all specified tasks back by 2h
-     *     t.setPin(true); // Pin all tasks specified
-     * });
- * - * @throws ValidationException if any updates on any of the task objects are considered invalid - */ - void updateAll(List indexes, Consumer update) throws ValidationException; - -``` -###### \src\main\java\seedu\todo\model\TodoModel.java -``` java - @Override - public void updateAll(Consumer update) throws ValidationException { - saveUndoState(); - Map uuidMap = new HashMap<>(); - for (int i = 0; i < tasks.size(); i++) { - uuidMap.put(tasks.get(i).getUUID(), i); - } - List indexes = new ArrayList<>(); - for (ImmutableTask task : getObservableList()) { - indexes.add(uuidMap.get(task.getUUID())); - } - todoList.updateAll(indexes, update); - } - -``` -###### \src\main\resources\style\DefaultStyle.css -``` css -/*Ongoing*/ -.ongoing { - -fx-background-color: #388E3C; -} - -.ongoing .label { - -fx-text-fill: #FFFFFF; -} - -.ongoing .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #388E3C; -} - -.ongoing .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.ongoing .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.ongoing .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \src\test\java\seedu\todo\logic\commands\AddCommandTest.java -``` java -public class AddCommandTest extends CommandTest { - @Override - protected BaseCommand commandUnderTest() { - return new AddCommand(); - } - - @Test - public void testAddTask() throws Exception { - setParameter("Hello World"); - EventsCollector eventsCollector = new EventsCollector(); - execute(true); - ImmutableTask addedTask = getTaskAt(1); - assertEquals("Hello World", addedTask.getTitle()); - assertFalse(addedTask.isPinned()); - assertFalse(addedTask.getDescription().isPresent()); - assertFalse(addedTask.getLocation().isPresent()); - assertThat(eventsCollector.get(0), instanceOf(HighlightTaskEvent.class)); - assertThat(eventsCollector.get(1), instanceOf(ExpandCollapseTaskEvent.class)); - } - - @Test - public void testAddTaskWithLocation() throws Exception { - setParameter("Hello NUS"); - setParameter("l", "NUS"); - execute(true); - - ImmutableTask taskWithLocation = getTaskAt(1); - - assertEquals("Hello NUS", taskWithLocation.getTitle()); - assertFalse(taskWithLocation.isPinned()); - assertFalse(taskWithLocation.getDescription().isPresent()); - assertEquals("NUS", taskWithLocation.getLocation().get()); - } - - @Test - public void testAddTaskWithDescription() throws Exception { - setParameter("Destroy World"); - setParameter("m", "Remember to get Dynamites on sale!"); - execute(true); - - ImmutableTask taskWithDescription = getTaskAt(1); - - assertEquals("Destroy World", taskWithDescription.getTitle()); - assertEquals("Remember to get Dynamites on sale!", taskWithDescription.getDescription().get()); - assertFalse(taskWithDescription.isPinned()); - assertFalse(taskWithDescription.getLocation().isPresent()); - } - - @Test - public void testAddPinnedTask() throws Exception { - setParameter("Li Kai's Presentation"); - setParameter("p", null); - execute(true); - - ImmutableTask pinnedAddedTask = getTaskAt(1); - - assertEquals("Li Kai's Presentation", pinnedAddedTask.getTitle()); - assertTrue(pinnedAddedTask.isPinned()); - assertFalse(pinnedAddedTask.getDescription().isPresent()); - assertFalse(pinnedAddedTask.getLocation().isPresent()); - } - - @Test - public void testAddSingleDate() throws Exception { - setParameter("Test Task"); - setParameter("d", "tomorrow 9am"); - execute(true); - - ImmutableTask task = getTaskAt(1); - assertFalse(task.isEvent()); - assertEquals(TimeUtil.tomorrow().withHour(9), task.getEndTime().get()); - } - - @Test - public void testAddDateRange() throws Exception { - setParameter("Test Event"); - setParameter("d", "tomorrow 6 to 8pm"); - execute(true); - - ImmutableTask task = getTaskAt(1); - assertTrue(task.isEvent()); - assertEquals(TimeUtil.tomorrow().withHour(18), task.getStartTime().get()); - assertEquals(TimeUtil.tomorrow().withHour(20), task.getEndTime().get()); - } - - @Test - public void testAddMultipleParameters() throws Exception { - setParameter("Task 1"); - setParameter("p", null); - setParameter("l", "COM1"); - setParameter("m", "Useless task"); - execute(true); - - ImmutableTask taskWithParams = getTaskAt(1); - - assertEquals("Task 1", taskWithParams.getTitle()); - assertTrue(taskWithParams.isPinned()); - assertEquals("COM1", taskWithParams.getLocation().get()); - assertEquals("Useless task", taskWithParams.getDescription().get()); - } - - @Test - public void testAdd_switchViewsNecessary() throws Exception { - model.view(TaskViewFilter.COMPLETED); - assertTotalTaskCount(0); - setParameter("Task 1"); - setParameter("p", null); - setParameter("l", "COM1"); - setParameter("m", "Useless task"); - execute(true); - assertEquals(model.getViewFilter().get(), TaskViewFilter.DEFAULT); - assertTotalTaskCount(1); - assertVisibleTaskCount(1); - } - - @Test - public void testAdd_switchViewsUnnecessary() throws Exception { - model.view(TaskViewFilter.INCOMPLETE); - assertTotalTaskCount(0); - setParameter("Task 1"); - setParameter("p", null); - setParameter("l", "COM1"); - setParameter("m", "Useless task"); - execute(true); - assertEquals(model.getViewFilter().get(), TaskViewFilter.INCOMPLETE); - assertTotalTaskCount(1); - assertVisibleTaskCount(1); - } - - -} -``` -###### \src\test\java\seedu\todo\logic\commands\FindCommandTest.java -``` java -public class FindCommandTest extends CommandTest { - - @Override - protected BaseCommand commandUnderTest() { - return new FindCommand(); - } - - @Before - public void setUp() throws Exception { - model.add("CS2101 Project Task"); - model.add("CS2103T project"); - model.add("Unrelated task"); - model.add("Unrelated CS2101 that expands"); - } - - @Test - public void testFindSuccessful() throws ValidationException { - assertNull(model.getSearchStatus().getValue()); - assertVisibleTaskCount(4); - setParameter("CS2101"); - execute(true); - assertVisibleTaskCount(2); - assertNotNull(model.getSearchStatus().getValue()); - } - - @Test - public void testCaseInsensitive() throws ValidationException { - setParameter("project"); - execute(true); - assertVisibleTaskCount(2); - } - - @Test - public void testMultipleParameters() throws ValidationException { - setParameter("task expands"); - execute(true); - assertVisibleTaskCount(3); - } - - @Test - public void testUnsuccessfulFind() throws ValidationException { - setParameter("team"); - execute(true); - assertVisibleTaskCount(0); - } - -``` -###### \src\test\java\seedu\todo\logic\commands\PinCommandTest.java -``` java -public class PinCommandTest extends CommandTest { - - @Override - protected BaseCommand commandUnderTest() { - return new PinCommand(); - } - - @Before - public void setUp() throws Exception { - model.add("Task 3"); - model.add("Task 2"); - model.add("Task 1", task -> task.setPinned(true)); - } - - private long getPinnedCount() { - return model.getObservableList().stream().filter(ImmutableTask::isPinned).count(); - } - - @Test - public void testPinFirst() throws Exception { - setParameter("3"); - EventsCollector eventsCollector = new EventsCollector(); - execute(true); - - assertEquals(2, getPinnedCount()); - assertThat(eventsCollector.get(0), instanceOf(HighlightTaskEvent.class)); - } - - @Test - public void testUnpinFirst() throws Exception { - setParameter("1"); - execute(true); - - assertEquals(0, getPinnedCount()); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\ShowCommandTest.java -``` java -public class ShowCommandTest extends CommandTest { - - @Override - protected BaseCommand commandUnderTest() { - return new ShowCommand(); - } - - @Before - public void setUp() throws Exception { - model.add("Task 1"); - model.add("Task 2"); - model.add("Task 3"); - - } - - @Test - public void test() throws ValidationException { - EventsCollector eventCollector = new EventsCollector(); - setParameter("2"); - execute(true); - assertThat(eventCollector.get(0), instanceOf(ExpandCollapseTaskEvent.class)); - assertEquals("Task 2", ((ExpandCollapseTaskEvent) eventCollector.get(0)).task.getTitle()); - } - -} -``` diff --git a/collated/A0135805H.md b/collated/A0135805H.md deleted file mode 100644 index 9695bd5c965a..000000000000 --- a/collated/A0135805H.md +++ /dev/null @@ -1,3765 +0,0 @@ -# A0135805H -###### \build\resources\main\style\DefaultStyle.css -``` css -.main { - -fx-background-color: #2D2D2D; - -fx-border-color: #00A4FF; - -fx-border-width: 1px; - -fx-padding: 12; - -fx-spacing: 4; -} - -.text1 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Black"; - -fx-font-size: 17pt; -} - -.text2 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 15pt; -} - -.text3 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 13pt; -} - -.text4 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; -} - -.code { - -fx-font-family: "Consolas"; -} - -.bolder { - -fx-font-weight: bolder; -} - -.underline { - -fx-underline: true; -} - -.gridPanel { - -fx-hgap: 16pt; - -fx-vgap: 2pt; - -fx-background-color: #3D3D3D; - -fx-padding: 8 8 8 8; -} - -.spacingBig { - -fx-spacing: 16px; - -fx-hgap: 16px; - -fx-vgap: 16px; -} - -.spacing { - -fx-spacing: 8px; - -fx-hgap: 8px; - -fx-vgap: 8px; -} - -.spacingSmall { - -fx-spacing: 2px; - -fx-hgap: 2px; - -fx-vgap: 2px; -} - -.subheadingPadding { - -fx-padding: 0 0 0 2; -} - - -.commandFeedback{ - -fx-text-fill: #FFFFFF; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; -} - -.commandFeedback.error { - -fx-text-fill: #FF6464; -} - -.commandInput { - -fx-font-family: "Consolas"; - -fx-font-size: 20px; -} - -.commandInput.error { - -fx-border-color: #FF6464; - -fx-border-width: 2px; - -fx-text-fill: #FF6464; -} - -.commandError { - -fx-text-fill: #FFFFFF; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; - -fx-wrap-text: true; -} - -.roundLabel { - -fx-background-radius: 10; - -fx-text-fill: #ffffff; - -fx-background-color: #2D2D2D; - -fx-font-size: 10pt; - -fx-font-family: "Segoe UI Semilight"; -} - -/***View Filter Styles Start***/ -.viewFilter .label{ - -fx-font-family: "Segoe UI"; - -fx-font-size: 14pt; - -fx-text-fill: #FFFFFF; -} - -.viewFilter .selected { - -fx-background-color: #FFFFFF; - -fx-background-radius: 100; - -fx-padding: 0 8 0 8; -} - -.viewFilter .selected .label { - -fx-text-fill: #2D2D2D; - -fx-font-family: "Segoe UI Semibold"; -} - -``` -###### \build\resources\main\style\DefaultStyle.css -``` css -/***TaskCardView Styles Start***/ -/*Default and Base*/ -.taskCard .label { - -fx-font-smoothing-type: lcd; -} - -.taskCard .titleLabel { - -fx-font-size: 16pt; - -fx-font-family: "Segoe UI Semibold"; -} - -.taskCard .descriptionLabel { - -fx-font-size: 12pt; - -fx-font-family: "Segoe UI"; -} - -.taskCard .footnoteLabel { - -fx-font-size: 10pt; - -fx-font-family: "Segoe UI Semilight"; - -fx-font-style: italic; -} - -.taskCard .highlightedBackground { - -fx-background-color: #00A4FF; -} - -.taskCard .lightBackground { - -fx-background-color: #d2d2d2; -} - -.taskCard .pinImage { - -fx-image: url("../images/star_gold.png"); - -fx-fit-to-width: 30px; - -fx-fit-to-height: 30px; -} - -.taskCard .dateImage { - -fx-image: url("../images/clock_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -.taskCard .locationImage { - -fx-image: url("../images/location_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -/*Completed*/ -.completed .label{ - -fx-text-fill: #7F7F7F; -} - -.completed .titleLabel .text { - -fx-strikethrough: true; -} - -.completed .roundLabel { - -fx-background-color: #7F7F7F; - -fx-text-fill: #ffffff; -} - -.completed .pinImage { - -fx-image: url("../images/star_grey.png"); -} - -.completed .dateImage { - -fx-image: url("../images/clock_grey.png"); -} - -.completed .locationImage { - -fx-image: url("../images/location_grey.png"); -} - -/*Overdue*/ -.overdue { - -fx-background-color: #FF6464; -} - -.overdue .label { - -fx-text-fill: #FFFFFF; -} - -.overdue .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #FF6464; -} - -.overdue .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.overdue .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.overdue .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \build\resources\main\style\DefaultStyle.css -``` css -/*Selected*/ -.selected { - -fx-background-color: #00A4FF; -} - -.selected .label { - -fx-text-fill: #FFFFFF; -} - -.selected .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #00A4FF; -} - -.selected .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.selected .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.selected .locationImage { - -fx-image: url("../images/location_white.png"); -} - -/*Collapse*/ -.collapsed .collapsible { - visibility: collapse; - -fx-pref-height: 0px; - -fx-min-height: 0px; -} - -/***TaskCardView Styles End***/ - -``` -###### \docs\DeveloperGuide.md -``` md -### UI component - - - -
The relation between the UI subcomponents
- -The UI component handles the interaction between the user and application. In particular, the UI is responsible for passing the textual command input from the user to the `Logic` for execution, and displaying the outcome of the execution to the user via the GUI. - - - -
Visual identification of view elements in the UI
- -**API** : [`Ui.java`](../src/main/java/seedu/todo/ui/Ui.java) - -The UI mainly consists of a `MainWindow`, as shown in the diagram above. This is where most of the interactions between the user and the application happen here. The `MainWindow` contains several major view elements that are discussed in greater detail below: - -#### Command Line Interface -The UI aims to imitate the Command Line Interface (CLI) closely by accepting textual commands from users, and displaying textual feedback back to the users. The CLI consists of: - -- `CommandInputView` - a text box for users to key in their commands -- `CommandFeedbackView` - a single line text that provides a response to their commands -- `CommandErrorView` - a detailed breakdown of any erroneous commands presented with a table - -These view classes are represented by the `CommandXView` class in the UML diagram above. - -The `CommandController` class is introduced to link the three classes together, so they can work and communicate with each other. The `CommandController`: - -1. Obtains a user-supplied command string from the `CommandInputView` -2. Submits the command string to `Logic` for execution -3. Receives a `CommandResult` from `Logic` after the execution -4. Displays the execution outcome via the `CommandResult` to the `CommandFeedbackView` and `CommandErrorView` - -#### To-do List Display -A to-do list provides a richer representation of the tasks than the CLI to the users. The To-do List Display consists of: - -- `TodoListView` - a [`ListView`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html) that displays a list of `TaskCard`. -- `TaskCard` - an item in the `TodoListView` that displays details of a specific task. - -Specifically, the `TodoListView` attaches an `ObservableList` of `ImmutableTask` from the `Model` and listens to any changes that are made to the `ObservableList`. If there are any modifications made, the `TaskCard` and `TodoListView` are updated automatically. - -#### Additional Information -All these view classes, including the `MainWindow`, inherit from the abstract `UiPart` class. They can be loaded using the utility class `UiPartLoader`. - -The UI component uses [JavaFX](http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#JFXST784) 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 [`CommandInputView`](../src/main/java/seedu/todo/ui/view/CommandInputView.java) is specified in [`CommandInputView.fxml`](../src/main/resources/view/CommandInputView.fxml) - -Other than through `CommandResult` and `ObservableList`, you may also invoke changes to the GUI outside the scope of UI components by raising an event. `UiManager` will then call specific view elements to update the GUI accordingly. For example, you may show the `HelpView` by raising a `ShowHelpPanel` via the `EventsCentre`. - -``` -###### \docs\UserGuide.md -``` md -## Introduction - -In today's hectic society, our lives feel like a never-ending procession of tasks, deadlines, events and anniversaries to keep up with. Tracking these daily activities on a to-do list can be daunting. Many task management applications today have too many buttons that you have to click through just to add a task, and user interfaces that are so cumbersome it is hard for you to make it a habit to use. - -Ever wished for a tool that can manage all your daily activities in distinct categories, and suggest to you which one you want to complete first? Well, look no further as Uncle Jim's Discount To-do List is here to save your day. - -Uncle Jim's Discount To-do List (Uncle Jim in short) is a revolutionary mouse-free personal task manager that helps you to keep track of your daily activities through the power of your keyboard. Gone are the days when you had to click through several pages of menus just to add a simple task to your schedule. Our command line interface is not only flexible but remarkably easy to use. Just type the command and hit enter! - -Moreover, we know that you understand your activities better. So Uncle Jim allows you to create your very own categories to organise your activities. Uncle Jim is also capable of managing both tasks *and* event so you don't have to use two different applications to be productive. Our unique product will intelligently sieve out urgent deadlines and serve up reminders for you so you will no longer overlook another significant activity. - -Sounds exciting? Then let's get started! - -``` -###### \src\main\java\seedu\todo\commons\events\ui\ExpandCollapseTaskEvent.java -``` java -/** - * An event to tell the Ui to collapse or expand a given task in the to-do list. - */ -public class ExpandCollapseTaskEvent extends BaseEvent{ - - public final ImmutableTask task; - - /** - * Construct an event that tells the Ui to collapse or expend a given task in the to-do list. - * @param task a single index of the task that is matching to the Ui (index 1 to num of tasks, inclusive) - */ - public ExpandCollapseTaskEvent(ImmutableTask task) { - this.task = task; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } -} -``` -###### \src\main\java\seedu\todo\commons\util\StringUtil.java -``` java - /** - * Partitions the string into three parts: - * string[0 .. position - 1], string[position], string[position + 1 .. length - 1], all index inclusive - * @param string to be partitioned - * @param position location where the string should be partitioned - * @return a String array containing the three elements stated above, - * where each element must not be null, but can have empty string. - */ - public static String[] partitionStringAtPosition(String string, int position) { - String[] stringArray = new String[3]; - if (string == null || string.isEmpty() || position < 0 || position >= string.length()) { - stringArray[0] = ""; - stringArray[1] = ""; - stringArray[2] = ""; - } else { - stringArray[0] = string.substring(0, position); - stringArray[1] = string.substring(position, position + 1); - stringArray[2] = string.substring(position + 1, string.length()); - } - return stringArray; - } - - /** - * Splits string at only space and comma. - * @return Returns a String array with all the split components of the string. - */ - public static String[] splitString(String string) { - if (string == null || string.isEmpty()) { - return new String[0]; - } else { - return string.trim().split("([, ])+"); - } - } - - /** - * Given a string list, gets the text from the list in the following manner: - * apple, pear, pineapple - */ - public static String convertListToString(String[] stringList) { - if (stringList == null || stringList.length == 0) { - return ""; - } - StringJoiner stringJoiner = new StringJoiner(", "); - for (String string : stringList) { - stringJoiner.add(string); - } - return stringJoiner.toString(); - } - -``` -###### \src\main\java\seedu\todo\commons\util\TimeUtil.java -``` java -/** - * Utility methods that deals with time. - */ -public class TimeUtil { - - /* Constants */ - private static final Logger logger = LogsCenter.getLogger(TimeUtil.class); - - private static final String WORD_IN = "in"; - private static final String WORD_BY = "by"; - private static final String WORD_SINCE = "since"; - private static final String WORD_AGO = "ago"; - private static final String WORD_FROM = "from"; - private static final String WORD_TO = "to"; - private static final String WORD_TOMORROW = "tomorrow"; - private static final String WORD_YESTERDAY = "yesterday"; - private static final String WORD_TODAY = "today"; - private static final String WORD_TONIGHT = "tonight"; - private static final String WORD_COMMA = ","; - private static final String WORD_SPACE = " "; - - private static final String DUE_NOW = "due now"; - private static final String DUE_LESS_THAN_A_MINUTE = "in less than a minute"; - - private static final String UNIT_MINUTES = "minutes"; - private static final String VALUE_ONE_MINUTE = "1 minute"; - - private static final String FORMAT_DATE_WITH_YEAR = "d MMMM yyyy"; - private static final String FORMAT_DATE_NO_YEAR = "d MMMM"; - private static final String FORMAT_TIME = "h:mm a"; - - private static final Pattern DATE_REGEX = Pattern.compile("\\b([0123]?\\d)([/-])([01]?\\d)(?=\\2\\d{2,4}|\\s|$)"); - - /* Variables */ - protected Clock clock = Clock.systemDefaultZone(); - - /** - * Gets the task deadline expression for the UI. - * @param endTime ending time - * @return a formatted deadline String - */ - public String getTaskDeadlineText(LocalDateTime endTime) { - if (endTime == null) { - logger.log(Level.WARNING, "endTime in getTaskDeadlineText(...) is missing."); - return ""; - } - - LocalDateTime currentTime = LocalDateTime.now(clock); - if (endTime.isAfter(currentTime)) { - return getDeadlineNotOverdueText(currentTime, endTime); - } else { - return getDeadlineOverdueText(currentTime, endTime); - } - } - - /** - * Helper method of {@link #getTaskDeadlineText(LocalDateTime)} to get deadline text - * when it is still not overdue (currentTime < endTime). - * @param currentTime the time now - * @param endTime the due date and time - * @return a formatted deadline string - */ - private String getDeadlineNotOverdueText(LocalDateTime currentTime, LocalDateTime endTime) { - Duration durationCurrentToEnd = Duration.between(currentTime, endTime); - long minutesToDeadline = durationCurrentToEnd.toMinutes(); - long secondsToDeadline = durationCurrentToEnd.getSeconds(); - - StringJoiner stringJoiner = new StringJoiner(WORD_SPACE); - - if (secondsToDeadline <= 59) { - return DUE_LESS_THAN_A_MINUTE; - } else if (minutesToDeadline <= 59) { - stringJoiner.add(WORD_IN).add(getMinutesText(currentTime, endTime)); - } else { - stringJoiner.add(WORD_BY).add(getDateText(currentTime, endTime) + WORD_COMMA) - .add(getTimeText(endTime)); - } - return stringJoiner.toString(); - } - - /** - * Helper method of {@link #getTaskDeadlineText(LocalDateTime)} to get deadline text - * when it is overdue (currentTime > endTime). - * @param currentTime the time now - * @param endTime the due date and time - * @return a formatted deadline string - */ - private String getDeadlineOverdueText(LocalDateTime currentTime, LocalDateTime endTime) { - Duration durationCurrentToEnd = Duration.between(currentTime, endTime); - long minutesToDeadline = durationCurrentToEnd.toMinutes(); - long secondsToDeadline = durationCurrentToEnd.getSeconds(); - - StringJoiner stringJoiner = new StringJoiner(WORD_SPACE); - - if (secondsToDeadline >= -59) { - return DUE_NOW; - } else if (minutesToDeadline >= -59) { - stringJoiner.add(getMinutesText(currentTime, endTime)).add(WORD_AGO); - } else { - stringJoiner.add(WORD_SINCE).add(getDateText(currentTime, endTime) + WORD_COMMA) - .add(getTimeText(endTime)); - } - return stringJoiner.toString(); - } - - /** - * Gets the event date and time text for the UI - * @param startTime of the event - * @param endTime of the event - * @return a formatted event duration string - */ - public String getEventTimeText(LocalDateTime startTime, LocalDateTime endTime) { - if (startTime == null || endTime == null) { - logger.log(Level.WARNING, "Either startTime or endTime is missing in getEventTimeText(...)"); - return ""; - } else if (startTime.isAfter(endTime)) { - logger.log(Level.WARNING, "Start time is after end time in getEventTimeText(...)"); - return ""; - } - - LocalDateTime currentTime = LocalDateTime.now(clock); - StringJoiner joiner = new StringJoiner(WORD_SPACE); - if (isSameDay(startTime, endTime)) { - joiner.add(getDateText(currentTime, startTime) + WORD_COMMA) - .add(WORD_FROM).add(getTimeText(startTime)) - .add(WORD_TO).add(getTimeText(endTime)); - } else { - joiner.add(WORD_FROM).add(getDateText(currentTime, startTime) + WORD_COMMA).add(getTimeText(startTime)) - .add(WORD_TO).add(getDateText(currentTime, endTime) + WORD_COMMA).add(getTimeText(endTime)); - } - return joiner.toString(); - } - - - /** - * Gives a formatted text of the dateTime based on the current system time, and then returns one of the following: - * "Yesterday", "Today", "Tonight", "Tomorrow", - * full date without year if this year, - * full date with year if other year. - * - * @param dateTime to format the date with - * @return a formatted date text described above - */ - private String getDateText(LocalDateTime currentTime, LocalDateTime dateTime) { - if (isYesterday(currentTime, dateTime)) { - return WORD_YESTERDAY; - } else if (isTonight(currentTime, dateTime)) { - return WORD_TONIGHT; - } else if (isToday(currentTime, dateTime)) { - return WORD_TODAY; - } else if (isTomorrow(currentTime, dateTime)) { - return WORD_TOMORROW; - } else if (isSameYear(currentTime, dateTime)) { - return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_DATE_NO_YEAR)); - } else { - return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_DATE_WITH_YEAR)); - } - } - - /** - * Returns a formatted string of the time component of dateTime - * @param dateTime to format the time with - * @return a formatted time text (HH:MM A/PM) - */ - private String getTimeText(LocalDateTime dateTime) { - return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_TIME)); - } - - /** - * Counts the number of minutes between the two dateTimes and prints out either: - * "1 minute" or "X minutes", for X != 1, X >= 0. - * @param dateTime1 the first time instance - * @param dateTime2 the other time instance - * @return a formatted string to tell number of minutes left (as above) - */ - private String getMinutesText(LocalDateTime dateTime1, LocalDateTime dateTime2) { - Duration duration = Duration.between(dateTime1, dateTime2); - long minutesToDeadline = Math.abs(duration.toMinutes()); - - if (minutesToDeadline == 1){ - return VALUE_ONE_MINUTE; - } else { - return minutesToDeadline + WORD_SPACE + UNIT_MINUTES; - } - } - - public boolean isTomorrow(LocalDateTime dateTimeToday, LocalDateTime dateTimeTomorrow) { - LocalDate dayBefore = dateTimeToday.toLocalDate(); - LocalDate dayAfter = dateTimeTomorrow.toLocalDate(); - return dayBefore.plusDays(1).equals(dayAfter); - } - - public boolean isToday(LocalDateTime dateTime1, LocalDateTime dateTime2) { - LocalDate date1 = dateTime1.toLocalDate(); - LocalDate date2 = dateTime2.toLocalDate(); - return date1.equals(date2); - } - - private boolean isTonight(LocalDateTime dateTimeToday, LocalDateTime dateTimeTonight) { - return isToday(dateTimeToday, dateTimeTonight) - && dateTimeTonight.toLocalTime().isAfter(LocalTime.of(17, 59, 59)); - } - - private boolean isYesterday(LocalDateTime dateTimeToday, LocalDateTime dateTimeYesterday) { - return isTomorrow(dateTimeYesterday, dateTimeToday); - } - - private boolean isSameDay(LocalDateTime dateTime1, LocalDateTime dateTime2) { - return dateTime1.toLocalDate().equals(dateTime2.toLocalDate()); - } - - private boolean isSameYear(LocalDateTime dateTime1, LocalDateTime dateTime2) { - return dateTime1.getYear() == dateTime2.getYear(); - } - - public boolean isOverdue(LocalDateTime endTime) { - if (endTime == null) { - logger.log(Level.WARNING, "endTime in isOverdue(...) is null."); - return false; - } - return endTime.isBefore(LocalDateTime.now(clock)); - } - -``` -###### \src\main\java\seedu\todo\logic\commands\TagCommand.java -``` java -/** - * This class handles all tagging command - */ -public class TagCommand extends BaseCommand { - /* Constants */ - private static final String ERROR_INCOMPLETE_PARAMETERS - = "You have not supplied sufficient parameters to run a Tag command."; - private static final String ERROR_INPUT_INDEX_REQUIRED - = "A task index is required."; - private static final String ERROR_INPUT_ADD_TAGS_REQUIRED - = "A list of tags \"tag1, tag2, ...\" to add is required."; - private static final String ERROR_INPUT_DELETE_TAGS_REQUIRED - = "A list of tags \"tag1, tag2, ...\" to delete is required."; - private static final String ERROR_TAGS_DUPLICATED - = "You might have keyed in duplicated tag names."; - private static final String ERROR_TAGS_ILLEGAL_CHAR - = "Tags may only include alphanumeric characters, including dashes and underscores."; - - private static final String SUCCESS_ADD_TAGS = " - tagged successfully"; - private static final String SUCCESS_DELETE_TAGS = " - removed successfully"; - - private static final String DESCRIPTION_ADD_TAGS = "Add tags to a task"; - private static final String DESCRIPTION_DELETE_TAGS = "Delete tags from tasks"; - private static final String ARGUMENTS_ADD_TAGS = "index /a tag1 [, tag2, ...]"; - private static final String ARGUMENTS_DELETE_TAGS = "[index] /d tag1 [, tag2, ...]"; - - private static final Pattern TAG_VALIDATION_REGEX = Pattern.compile("^[\\w\\d_-]+$"); - - /* Variables */ - private Argument index = new IntArgument("index"); - - private Argument addTags = new StringArgument("/a") - .flag("a"); - - private Argument deleteTags = new StringArgument("/d") - .flag("d"); - - @Override - public Parameter[] getArguments() { - return new Parameter[] { - index, deleteTags, addTags - }; - } - - @Override - protected void setPositionalArgument(String argument) { - String[] tokens = argument.trim().split(" ", 2); - boolean isFirstArgNumber = StringUtil.isUnsignedInteger(tokens[0]); - - if (isFirstArgNumber) { - try { - index.setValue(tokens[0]); - } catch (IllegalValueException e) { - errors.put(index.getName(), e.getMessage()); - } - } - } - - @Override - public String getCommandName() { - return "tag"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of( - new CommandSummary(DESCRIPTION_ADD_TAGS, getCommandName(), ARGUMENTS_ADD_TAGS), - new CommandSummary(DESCRIPTION_DELETE_TAGS, getCommandName(), ARGUMENTS_DELETE_TAGS) - ); - } - - @Override - protected void validateArguments() { - //Check if we have enough input arguments - if (!isInputParametersAvailable()) { - handleUnavailableInputParameters(); - } - - //Check arguments for add tags case - if (isAddTagsToTask()) { - String[] tagsToAdd = StringUtil.splitString(addTags.getValue()); - checkForIllegalCharInTagNames(addTags.getName(), tagsToAdd); - checkForDuplicatedTagNames(addTags.getName(), tagsToAdd); - } - - //Check arguments for delete tags case - if (isDeleteTagsFromTask()) { - String[] tagsToDelete = StringUtil.splitString(deleteTags.getValue()); - checkForDuplicatedTagNames(deleteTags.getName(), tagsToDelete); - } - super.validateArguments(); - } - - @Override - public CommandResult execute() throws ValidationException { - //Obtain values for manipulation - Integer displayedIndex = index.getValue(); - String[] tagsToAdd = StringUtil.splitString(addTags.getValue()); - String[] tagsToDelete = StringUtil.splitString(deleteTags.getValue()); - - //Performs the actual execution with the data - if (isAddTagsToTask()) { - model.addTagsToTask(displayedIndex, tagsToAdd); - return new CommandResult(StringUtil.convertListToString(tagsToAdd) + SUCCESS_ADD_TAGS); - - } else if (isDeleteTagsFromTask()) { - model.deleteTagsFromTask(displayedIndex, tagsToDelete); - return new CommandResult(StringUtil.convertListToString(tagsToDelete) + SUCCESS_DELETE_TAGS); - - } else { - //Invalid case, should not happen, as we have checked it validateArguments. - //However, for completeness, a command result is returned. - throw new ValidationException(ERROR_INCOMPLETE_PARAMETERS); - } - } - - /* Input Parameters Validation */ - /** - * Returns true if the command matches the action of adding tag(s) to a task. - */ - private boolean isAddTagsToTask() { - return index.hasBoundValue() && addTags.hasBoundValue(); - } - - /** - * Returns true if the command matches the action of deleting tag(s) from a task. - */ - private boolean isDeleteTagsFromTask() { - return index.hasBoundValue() && deleteTags.hasBoundValue(); - } - - /** - * Returns true if the command matches the action of deleting tag(s) from all tasks. - */ - private boolean isDeleteTagsFromAllTasks() { - return !index.hasBoundValue() && deleteTags.hasBoundValue(); - } - - /** - * Returns true if the correct input parameters are available. - * This method do not check validity of each input. - */ - private boolean isInputParametersAvailable() { - return BooleanUtils.xor( - new boolean[] {isAddTagsToTask(), isDeleteTagsFromTask(), isDeleteTagsFromAllTasks()}); - } - - /** - * Sets error messages for insufficient input parameters, dependent on input parameters supplied. - */ - private void handleUnavailableInputParameters() { - boolean hasIndex = index.hasBoundValue(); - boolean hasAddTags = addTags.hasBoundValue(); - boolean hasDeleteTags = deleteTags.hasBoundValue(); - - //Validation for all inputs. - if (!hasIndex && !hasAddTags && !hasDeleteTags) { - errors.put(index.getName(), ERROR_INPUT_INDEX_REQUIRED); - errors.put(addTags.getName(), ERROR_INPUT_ADD_TAGS_REQUIRED); - errors.put(deleteTags.getName(), ERROR_INPUT_DELETE_TAGS_REQUIRED); - - } else if (!hasIndex && hasAddTags) { - errors.put(index.getName(), ERROR_INPUT_INDEX_REQUIRED); - - } else if (hasIndex && !hasAddTags && !hasDeleteTags) { - errors.put(addTags.getName(), ERROR_INPUT_ADD_TAGS_REQUIRED); - errors.put(deleteTags.getName(), ERROR_INPUT_DELETE_TAGS_REQUIRED); - } - } - - /** - * Checks if the given tag names have duplicated entries. - */ - private void checkForDuplicatedTagNames(String argumentName, String[] tagNames) { - if (!CollectionUtil.elementsAreUnique(Arrays.asList(tagNames))) { - errors.put(argumentName, ERROR_TAGS_DUPLICATED); - } - } - - /** - * Check if the given tag names are alphanumeric, which also can contain dashes and underscores. - */ - private void checkForIllegalCharInTagNames(String argumentName, String[] tagNames) { - for (String tagName : tagNames) { - if (!isValidTagName(tagName)) { - errors.put(argumentName, ERROR_TAGS_ILLEGAL_CHAR); - } - } - } - - /* Helper Methods */ - /** - * Returns true if a given string is a valid tag name (alphanumeric, can contain dashes and underscores) - * Originated from {@link Tag} - */ - private static boolean isValidTagName(String test) { - return TAG_VALIDATION_REGEX.matcher(test).matches(); - } -} -``` -###### \src\main\java\seedu\todo\model\Model.java -``` java - /** - * Adds the supplied list of tags (using tag names) to the specified task. - * - * @param index The task displayed index. - * @param tagNames The list of tag names to be added. - * @throws ValidationException when the given index is invalid, or the given {@code tagNames} contain - * illegal characters. - */ - void addTagsToTask(int index, String[] tagNames) throws ValidationException; - - /** - * Deletes a list of tags (using tag names) from the specified task. - * - * @param index The task displayed index. - * @param tagNames The list of tag names to be deleted. - * @throws ValidationException when the given index is invalid, or when there is duplicates. - */ - void deleteTagsFromTask(int index, String[] tagNames) throws ValidationException; -} -``` -###### \src\main\java\seedu\todo\model\tag\Tag.java -``` java -/** - * Represents a Tag in a task. - * - * Guarantees: immutable, only {@link UniqueTagCollection} can modify - * the package private {@link Tag#rename(String)}. - * - * However, since alphanumeric name is not critical to {@link Tag}, - * the validation is done at {@link seedu.todo.model.TodoModel} - */ -public class Tag { - /* Variables */ - //Stores a unique tag name, that is alphanumeric, and contains dashes and underscores. - private String tagName; - - /** - * Constructs a new tag with the given tag name. - * - * This class is intentional to be package private, so only {@link UniqueTagCollection} - * can construct new tags. - */ - public Tag(String name) { - this.tagName = name; - } - - /* Methods */ -``` -###### \src\main\java\seedu\todo\model\tag\UniqueTagCollection.java -``` java -/** - * A list of tags that enforces no nulls and uniqueness between its elements. - * Also supports minimal set of list operations for the app's features. - * - * Note: This class will disallow external access to {@link #uniqueTagsToTasksMap} so to - * maintain uniqueness of the tag names. - */ -public class UniqueTagCollection implements Iterable, UniqueTagCollectionModel { - /* - Stores a list of tags with unique tag names. - TODO: ImmutableTask does not have consistent hashing. - TODO: So, duplicated ImmutableTask may be found in the set of each Tag. - */ - private final Map> uniqueTagsToTasksMap = new HashMap<>(); - - /* Interfacing Methods */ - @Override - public void update(ObservableList globalTaskList) { - uniqueTagsToTasksMap.clear(); - globalTaskList.forEach(task -> task.getTags().forEach(tag -> associateTaskToTag(task, tag))); - } - - @Override - public void notifyTaskDeleted(ImmutableTask task) { - task.getTags().forEach(tag -> dissociateTaskFromTag(task, tag)); - } - - @Override - public Tag registerTagWithTask(ImmutableTask task, String tagName) { - Tag tag = getTagWithName(tagName); - associateTaskToTag(task, tag); - return tag; - } - - @Override - public Tag unregisterTagWithTask(ImmutableTask task, String tagName) { - //TODO: Throw an error if the tag is not found. - Tag tag = getTagWithName(tagName); - dissociateTaskFromTag(task, tag); - return tag; - } - - @Override - public void renameTag(String originalName, String newName) { - Tag tag = getTagWithName(originalName); - Set setOfTasks = uniqueTagsToTasksMap.remove(tag); - tag.rename(newName); - uniqueTagsToTasksMap.put(tag, setOfTasks); - } - - /* Helper Methods */ - /** - * Links a {@code task} to the {@code tag} in the {@link #uniqueTagsToTasksMap}. - */ - private void associateTaskToTag(ImmutableTask task, Tag tag) { - Set setOfTasks = uniqueTagsToTasksMap.get(tag); - if (setOfTasks == null) { - setOfTasks = new HashSet<>(); - uniqueTagsToTasksMap.put(tag, setOfTasks); - } - setOfTasks.add(task); - } - - /** - * Removes the association between the {@code task} from the {@code tag} in - * the {@link #uniqueTagsToTasksMap}. - */ - private void dissociateTaskFromTag(ImmutableTask task, Tag tag) { - Set setOfTasks = uniqueTagsToTasksMap.get(tag); - if (setOfTasks != null) { - setOfTasks.remove(task); - } - } - - /** - * Obtains an instance of {@link Tag} with the supplied {@code tagName} from the - * {@link #uniqueTagsToTasksMap}. - * - * Note: If such an instance is not found, a new {@link Tag} instance will be added to the - * {@link #uniqueTagsToTasksMap}. - * - * @param tagName The name of the {@link Tag}. - * @return A {@link Tag} object that has the name {@code tagName}. - */ - private Tag getTagWithName(String tagName) { - Optional possibleTag = uniqueTagsToTasksMap.keySet().stream() - .filter(tag -> tag.getTagName().equals(tagName)).findAny(); - - Tag targetTag; - if (possibleTag.isPresent()) { - targetTag = possibleTag.get(); - } else { - targetTag = new Tag(tagName); - uniqueTagsToTasksMap.put(targetTag, new HashSet<>()); - } - return targetTag; - } - - /** - * Simply finds a tag with the {@code tagName}. - */ - private Optional findTagWithName(String tagName) { - return uniqueTagsToTasksMap.keySet().stream() - .filter(tag -> tag.getTagName().equals(tagName)).findAny(); - } - - /* Interfacing Getters */ - @Override - public List getUniqueTagList() { - return new ArrayList<>(uniqueTagsToTasksMap.keySet()); - } - - @Override - public List getTasksLinkedToTag(String tagName) { - Optional possibleTag = findTagWithName(tagName); - if (possibleTag.isPresent()) { - Set tasks = uniqueTagsToTasksMap.get(possibleTag.get()); - return new ArrayList<>(tasks); - } else { - return new ArrayList<>(); - } - } - -``` -###### \src\main\java\seedu\todo\model\tag\UniqueTagCollectionModel.java -``` java -/** - * An interface that spells out the available methods for maintaining a unique tag list. - */ -public interface UniqueTagCollectionModel { - - /* Model Interfacing Methods*/ - /** - * Update the {@link UniqueTagCollectionModel} with the main to-do list {@code globalTaskList} - * stored in {@link seedu.todo.model.TodoModel}, for a new set of {@link Tag} - * - * @param globalTaskList To extract the unique list of {@link Tag}s from. - */ - void update(ObservableList globalTaskList); - - /** - * Notifies the {@link UniqueTagCollectionModel} that the given {@code task} is deleted, - * so that the {@link UniqueTagCollectionModel} can update the relations accordingly. - */ - void notifyTaskDeleted(ImmutableTask task); - - /* Tag Command Interfacing Methods */ - /** - * Registers the given {@code task} to a {@link Tag} in the {@link UniqueTagCollectionModel}. - * - * @param task The task to be attached under the {@link Tag}. - * @param tagName The name of the {@link Tag}. - * @return Returns the corresponding {@link Tag} object with {@code tagName} - * so this tag can be added to the {@code task}. - */ - Tag registerTagWithTask(ImmutableTask task, String tagName); - - /** - * Unregistere the given {@code task} from the {@link Tag} in the {@link UniqueTagCollectionModel}. - * - * @param task The task to be detached from the {@link Tag}. - * @param tagName The name of the {@link Tag}. - * @return Returns the corresponding {@link Tag} object with {@code tagName} - * so this tag can be removed from the {@code task}. - */ - Tag unregisterTagWithTask(ImmutableTask task, String tagName); - - /** - * Renames a {@link Tag} with the given {@code originalName} with the {@code newName} - */ - void renameTag(String originalName, String newName) throws ValidationException; - - /** - * Gets a copy of the list of tags. - */ - List getUniqueTagList(); - - /** - * Gets a copy of list of task associated with the {@link Tag} with the name {@code tagName} - */ - List getTasksLinkedToTag(String tagName); -} -``` -###### \src\main\java\seedu\todo\model\TodoModel.java -``` java - @Override - public void addTagsToTask(int index, String[] tagNames) throws ValidationException { - saveUndoState(); - update(index, mutableTask -> { - Set tagsFromTask = new HashSet<>(mutableTask.getTags()); - for (String tagName : tagNames) { - Tag newTag = uniqueTagCollection.registerTagWithTask(mutableTask, tagName); - tagsFromTask.add(newTag); - } - mutableTask.setTags(tagsFromTask); - }); - } - - @Override - public void deleteTagsFromTask(int index, String[] tagNames) throws ValidationException { - saveUndoState(); - update(index, mutableTask -> { - Set tagsFromTask = new HashSet<>(mutableTask.getTags()); - for (String tagName : tagNames) { - Tag deletedTag = uniqueTagCollection.unregisterTagWithTask(mutableTask, tagName); - tagsFromTask.remove(deletedTag); - } - mutableTask.setTags(tagsFromTask); - }); - } -} -``` -###### \src\main\java\seedu\todo\ui\controller\CommandController.java -``` java -/** - * Processes the input command from {@link CommandInputView}, pass it to {@link seedu.todo.logic.Logic} - * and hands the {@link seedu.todo.logic.commands.CommandResult} to {@link CommandFeedbackView} and - * {@link CommandErrorView} - */ -public class CommandController { - /* Variables */ - private Logic logic; - private CommandInputView inputView; - private CommandPreviewView previewView; - private CommandFeedbackView feedbackView; - private CommandErrorView errorView; - - /* Private Constructor */ - private CommandController() {} - - /** - * Constructs a link between the classes defined in the parameters. - */ - public static CommandController constructLink(Logic logic, CommandInputView inputView, - CommandPreviewView previewView, CommandFeedbackView feedbackView, CommandErrorView errorView) { - - CommandController controller = new CommandController(); - controller.logic = logic; - controller.inputView = inputView; - controller.previewView = previewView; - controller.feedbackView = feedbackView; - controller.errorView = errorView; - controller.start(); - return controller; - } - - /** - * Asks {@link #inputView} to start listening for a new key strokes. - * Once the callback returns a command, {@link #handleInput(KeyCode, String)} will process the input. - */ - private void start() { - inputView.listenToInput(this::handleInput); - } - -``` -###### \src\main\java\seedu\todo\ui\controller\CommandController.java -``` java - /** - * Displays error in the respective UI elements - * @param errorBag group of errors to display - */ - private void viewDisplayError(ErrorBag errorBag) { - inputView.flagError(); - feedbackView.flagError(); - errorView.displayErrors(errorBag); - } - - /** - * Displays success behaviour in the respective UI elements - */ - private void viewDisplaySuccess() { - inputView.resetViewState(); - feedbackView.unFlagError(); - errorView.hideCommandErrorView(); - } - - /** - * Displays a message with regards to the user's input - * @param message to be displayed to user - */ - private void displayMessage(String message) { - feedbackView.displayMessage(message); - } -} -``` -###### \src\main\java\seedu\todo\ui\UiManager.java -``` java - @Subscribe - private void handleExpandCollapseTaskEvent(ExpandCollapseTaskEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getTodoListView().toggleExpandCollapsed(event.task); - } - -``` -###### \src\main\java\seedu\todo\ui\UiManager.java -``` java - @Subscribe - private void handleShowPreviewEvent(ShowPreviewEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getHelpView().hideHelpPanel(); - mainWindow.getCommandPreviewView().displayCommandSummaries(event.getPreviewInfo()); - } - - @Subscribe - private void handleCommandInputEnterEvent(CommandInputEnterEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getHelpView().hideHelpPanel(); - mainWindow.getCommandFeedbackView().clearMessage(); - } - - @Subscribe - private void handleHighlightTaskEvent(HighlightTaskEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getTodoListView().scrollAndSelect(event.getTask()); - } - -``` -###### \src\main\java\seedu\todo\ui\util\FxViewUtil.java -``` java -/** - * Contains generic utility methods for JavaFX views - */ -public class FxViewUtil { - -``` -###### \src\main\java\seedu\todo\ui\util\FxViewUtil.java -``` java - /** - * Hides a specified UI element, and ensures that it does not occupy any space. - */ - public static void setCollapsed(Node node, boolean isCollapsed) { - node.setVisible(!isCollapsed); - node.setManaged(!isCollapsed); - } - - /** - * Set the text to UI element when available, collapse the UI element when not. - */ - public static void displayTextWhenAvailable(Label labelToDisplay, Node nodeToHide, - Optional optionalString) { - if (optionalString.isPresent()) { - labelToDisplay.setText(optionalString.get()); - } else { - labelToDisplay.setText(""); - setCollapsed(nodeToHide, true); - } - } - - /** - * Sets a recurring task on the UI specified in handler to repeat every specified seconds. - * Does not start until the user executes .play() - * @param seconds duration between each repeats - * @param handler method to run is specified here - * @return {@link Timeline} object to run. - */ - public static Timeline setRecurringUiTask(int seconds, EventHandler handler) { - Timeline recurringTask = new Timeline(new KeyFrame(Duration.seconds(seconds), handler)); - recurringTask.setCycleCount(Timeline.INDEFINITE); - return recurringTask; - } - - /** - * Converts an index from a list to the index that is displayed to the user via the Ui - */ - public static int convertToListIndex(int uiIndex) { - return uiIndex - 1; - } - - /** - * Converts an index displayed on the Ui to the user, to the index used on the list - */ - public static int convertToUiIndex(int listIndex) { - return listIndex + 1; - } -} -``` -###### \src\main\java\seedu\todo\ui\util\UiPartLoaderUtil.java -``` java - /** - * Attaches only one children view element to a specified placeholder. - * However, if either one of the params is null, it will result in no-op. - * Also, if there are any other children in the placeholder, they will be cleared first. - * - * @param placeholder to add the childrenView to, no-op if null - * @param childrenView to be attached to the placeholder, no-op if null - */ - private static void attachToPlaceholder(AnchorPane placeholder, Node childrenView) { - if (placeholder != null && childrenView != null) { - ObservableList placeholderChildren = placeholder.getChildren(); - placeholderChildren.clear(); - placeholderChildren.add(childrenView); - } - } -} -``` -###### \src\main\java\seedu\todo\ui\util\ViewGeneratorUtil.java -``` java -/** - * A utility class that generates commonly used UI elements, such as Labels. - */ -public class ViewGeneratorUtil { - - /** - * Generates a {@link Text} object with the class style applied onto the object. - * @param string to be wrapped in the {@link Text} object - * @param classStyle css style to be applied to the label - * @return a {@link Text} object - */ - public static Text constructText(String string, String classStyle) { - Text text = new Text(string); - ViewStyleUtil.addClassStyles(text, classStyle); - return text; - } - - /** - * Constructs a label view with a dark grey rounded background. - */ - public static Label constructRoundedText(String string) { - Label label = constructLabel(string, "roundLabel"); - label.setPadding(new Insets(0, 8, 0, 8)); - return label; - } - - /** - * Generates a {@link Label} object with the class style applied onto the object. - * @param string to be wrapped in the {@link Label} object - * @param classStyle css style to be applied to the label - * @return a {@link Label} object - */ - public static Label constructLabel(String string, String classStyle) { - Label label = new Label(string); - ViewStyleUtil.addClassStyles(label, classStyle); - return label; - } - - /** - * Place all the specified texts into a {@link TextFlow} object. - */ - public static TextFlow placeIntoTextFlow(Text... texts) { - return new TextFlow(texts); - } -} -``` -###### \src\main\java\seedu\todo\ui\util\ViewStyleUtil.java -``` java -/** - * Deals with the CSS styling of View elements - */ -public class ViewStyleUtil { - - /* Style Classes Constants */ - public static final String STYLE_COLLAPSED = "collapsed"; - public static final String STYLE_COMPLETED = "completed"; - public static final String STYLE_OVERDUE = "overdue"; - public static final String STYLE_SELECTED = "selected"; - public static final String STYLE_TEXT_4 = "text4"; - public static final String STYLE_ERROR = "error"; - public static final String STYLE_CODE = "code"; - public static final String STYLE_BOLDER = "bolder"; - public static final String STYLE_UNDERLINE = "underline"; - public static final String STYLE_ONGOING = "ongoing"; - - /*Static Helper Methods*/ - /** - * Adds only one instance of all the class styles to the node object - * @param node view object to add the class styles to - * @param classStyles all the class styles that is to be added to the node - */ - public static void addClassStyles(Node node, String... classStyles) { - for (String classStyle : classStyles) { - addClassStyle(node, classStyle); - } - } - - /** - * Remove all instance of all the class styles to the node object - * @param node view object to add the class styles to - * @param classStyles all the class styles that is to be removed from the node - */ - public static void removeClassStyles(Node node, String... classStyles) { - for (String classStyle : classStyles) { - removeClassStyle(node, classStyle); - } - } - - /** - * Adds or removes class style based on a boolean parameter - * @param isAdding true to add, false to remove - * @param node view object to add the class styles to - * @param classStyles all the class styles that is to be added to/removed from the node - */ - public static void addRemoveClassStyles(boolean isAdding, Node node, String... classStyles) { - for (String classStyle : classStyles) { - if (isAdding) { - addClassStyles(node, classStyle); - } else { - removeClassStyles(node, classStyle); - } - } - } - - /** - * Toggles one style class to the node: - * If supplied style class is available, remove it. Else, add one instance of it. - * - * @return true if toggled from OFF -> ON - */ - public static boolean toggleClassStyle(Node node, String classStyle) { - boolean wasPreviouslyOff = !node.getStyleClass().contains(classStyle); - if (wasPreviouslyOff) { - addClassStyles(node, classStyle); - } else { - removeClassStyles(node, classStyle); - } - return wasPreviouslyOff; - } - - /* Private Helper Methods */ - /** - * Adds only one instance of a single class style to the node object - */ - private static void addClassStyle(Node node, String classStyle) { - if (!node.getStyleClass().contains(classStyle)) { - node.getStyleClass().add(classStyle); - } - } - - /** - * Removes all instances of a single class style from the node object - */ - private static void removeClassStyle(Node node, String classStyle) { - while (node.getStyleClass().contains(classStyle)) { - node.getStyleClass().remove(classStyle); - } - } -} -``` -###### \src\main\java\seedu\todo\ui\view\CommandErrorView.java -``` java -/** - * A view class that displays specific command errors in greater detail. - */ -public class CommandErrorView extends UiPart { - /* Constants */ - private static final String FXML = "CommandErrorView.fxml"; - - /* Variables */ - private final Logger logger = LogsCenter.getLogger(CommandFeedbackView.class); - - /* Layouts */ - private AnchorPane placeholder; - private VBox errorViewBox; - @FXML private VBox nonFieldErrorBox; - @FXML private VBox fieldErrorBox; - @FXML private GridPane nonFieldErrorGrid; - @FXML private GridPane fieldErrorGrid; - -``` -###### \src\main\java\seedu\todo\ui\view\CommandErrorView.java -``` java - /** - * Configure the UI layout of {@link CommandErrorView} - */ - private void configureLayout() { - FxViewUtil.applyAnchorBoundaryParameters(errorViewBox, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(nonFieldErrorBox, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(fieldErrorBox, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(fieldErrorGrid, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(nonFieldErrorGrid, 0.0, 0.0, 0.0, 0.0); - } - - /** - * Displays both field and non-field errors to the user - * @param errorBag that contains both field and non-field errors - */ - public void displayErrors(ErrorBag errorBag) { - showCommandErrorView(); - clearOldErrorsFromViews(); - displayFieldErrors(errorBag.getFieldErrors()); - displayNonFieldErrors(errorBag.getNonFieldErrors()); - } - - /** - * Feeds non field errors to the {@link #nonFieldErrorGrid}. - * If there are no non-field errors, then {@link #nonFieldErrorBox} will be hidden. - * @param nonFieldErrors that stores a list of non-field errors - */ - private void displayNonFieldErrors(List nonFieldErrors) { - if (nonFieldErrors.isEmpty()) { - hideErrorBox(nonFieldErrorBox); - } else { - int rowCounter = 0; - for (String error : nonFieldErrors) { - addRowToGrid(nonFieldErrorGrid, rowCounter++, rowCounter + ".", error); - } - } - } - - /** - * Feeds field errors to the {@link #fieldErrorGrid}. - * If there are no field errors, then {@link #fieldErrorBox} will be hidden. - * @param fieldErrors that stores the field errors - */ - private void displayFieldErrors(Map fieldErrors) { - if (fieldErrors.isEmpty()) { - hideErrorBox(fieldErrorBox); - } else { - int rowCounter = 0; - for (Map.Entry fieldError : fieldErrors.entrySet()) { - addRowToGrid(fieldErrorGrid, rowCounter++, fieldError.getKey(), fieldError.getValue()); - } - } - } - - /* Override Methods */ - @Override - public void setPlaceholder(AnchorPane placeholder) { - this.placeholder = placeholder; - } - - @Override - public void setNode(Node node) { - this.errorViewBox = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - - /*Helper Methods*/ - /** - * Adds a row of text to the targetGrid - * @param targetGrid to add a row of text on - * @param rowIndex which row to add this row of text - * @param leftText text for the first column - * @param rightText text for the second column - */ - private void addRowToGrid(GridPane targetGrid, int rowIndex, String leftText, String rightText) { - Label leftLabel = ViewGeneratorUtil.constructLabel(leftText, ViewStyleUtil.STYLE_TEXT_4); - Label rightLabel = ViewGeneratorUtil.constructLabel(rightText, ViewStyleUtil.STYLE_TEXT_4); - targetGrid.addRow(rowIndex, leftLabel, rightLabel); - } - - /** - * Clears all elements in the given grid. - */ - private void clearGrid(GridPane gridPane) { - gridPane.getChildren().clear(); - } - - /** - * Hides a field or non-field error box. - * @param vBox can be either {@link #fieldErrorBox} or {@link #nonFieldErrorBox} - */ - private void hideErrorBox(VBox vBox) { - FxViewUtil.setCollapsed(vBox, true); - } - - /** - * Shows a field or non-field error box. - * @param vBox can be either {@link #fieldErrorBox} or {@link #nonFieldErrorBox} - */ - private void showErrorBox(VBox vBox) { - FxViewUtil.setCollapsed(vBox, false); - } - - /** - * Hides the entire {@link CommandErrorView} - */ - public void hideCommandErrorView() { - FxViewUtil.setCollapsed(placeholder, true); - } - - /** - * Displays the entire {@link CommandErrorView} - */ - private void showCommandErrorView() { - FxViewUtil.setCollapsed(placeholder, false); - } - - /** - * Clears previous errors from the grid, and then unhide all the error boxes. - */ - private void clearOldErrorsFromViews() { - clearGrid(nonFieldErrorGrid); - clearGrid(fieldErrorGrid); - showErrorBox(nonFieldErrorBox); - showErrorBox(fieldErrorBox); - } -} -``` -###### \src\main\java\seedu\todo\ui\view\CommandFeedbackView.java -``` java -/** - * Display textual feedback to command input via this view with {@link #displayMessage(String)}. - */ -public class CommandFeedbackView extends UiPart { - /* Constants */ - private static final String FXML = "CommandFeedbackView.fxml"; - - /* Variables */ - private final Logger logger = LogsCenter.getLogger(CommandFeedbackView.class); - - /* Layout Elements */ - @FXML private Label commandFeedbackLabel; - private AnchorPane textContainer; - -``` -###### \src\main\java\seedu\todo\ui\view\CommandFeedbackView.java -``` java - /* Interfacing Methods */ - /** - * Displays a message onto the {@link #commandFeedbackLabel}. - * @param message The feedback message to be shown to the user. - */ - public void displayMessage(String message) { - commandFeedbackLabel.setText(message); - } - - /** - * Clears any message in {@link #commandFeedbackLabel}. - */ - public void clearMessage() { - commandFeedbackLabel.setText(""); - } - - /** - * Indicate an error visually on the {@link #commandFeedbackLabel}. - */ - public void flagError() { - ViewStyleUtil.addClassStyles(commandFeedbackLabel, ViewStyleUtil.STYLE_ERROR); - } - - /** - * Remove the error flag visually on the {@link #commandFeedbackLabel}. - */ - public void unFlagError() { - ViewStyleUtil.removeClassStyles(commandFeedbackLabel, ViewStyleUtil.STYLE_ERROR); - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - this.textContainer = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java -/** - * A view class that handles the Input text box directly. - */ -public class CommandInputView extends UiPart { - /* Constants */ - private static final String FXML = "CommandInputView.fxml"; - - /* Variables */ - private final Logger logger = LogsCenter.getLogger(CommandInputView.class); - - /* Layout Elements */ - private AnchorPane commandInputPane; - @FXML private TextArea commandTextField; - -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java - /** - * Configure the UI properties of {@link CommandInputView} - */ - private void configureProperties() { - setCommandInputHeightAutoResizeable(); - unflagErrorWhileTyping(); - listenAndRaiseEnterEvent(); - } - -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java - /** - * Listens for Enter keystrokes, and raises an event when it happens. - */ - public void listenAndRaiseEnterEvent() { - this.commandTextField.addEventHandler(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.ENTER) { - EventsCenter.getInstance().post(new CommandInputEnterEvent()); - event.consume(); //To prevent commandTextField from printing a new line. - } - }); - } - - /* UI Methods */ - /** - * Resets the text box by {@link #unflagError()} after a key is pressed - */ - private void unflagErrorWhileTyping() { - this.commandTextField.addEventFilter(KeyEvent.KEY_PRESSED, event -> unflagError()); - } - - /** - * Allow {@link #commandTextField} to adjust automatically with the height of the content of the - * text area itself. - */ - private void setCommandInputHeightAutoResizeable() { - new TextAreaResizer(commandTextField); - } - - /** - * Resets the state of the text box, by clearing the text box and clear the errors. - */ - public void resetViewState() { - unflagError(); - clear(); - } - - /** - * Indicate an error visually on the {@link #commandTextField} - */ - public void flagError() { - ViewStyleUtil.addClassStyles(commandTextField, ViewStyleUtil.STYLE_ERROR); - } - - /** - * Remove the error flag visually on the {@link #commandTextField} - */ - private void unflagError() { - ViewStyleUtil.removeClassStyles(commandTextField, ViewStyleUtil.STYLE_ERROR); - } - - /** - * Clears the texts inside the text box - */ - private void clear() { - commandTextField.clear(); - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - commandInputPane = (AnchorPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - -``` -###### \src\main\java\seedu\todo\ui\view\FilterBarView.java -``` java -/** - * Shows a row of filter categories via {@link TaskViewFilter} - * to filter the tasks in {@link TodoListView} - */ -public class FilterBarView extends UiPart { - /* Constants */ - private final Logger logger = LogsCenter.getLogger(FilterBarView.class); - private static final String FXML = "FilterBarView.fxml"; - - /* Layout Views */ - private FlowPane filterViewPane; - - /* Variables */ - private Map taskFilterBoxesMap = new HashMap<>(); - - /* Layout Initialisation */ - /** - * Loads and initialise the {@link #filterViewPane} to the {@link seedu.todo.ui.MainWindow} - * @param primaryStage of the application - * @param placeholder where the view element {@link #filterViewPane} should be placed - * @return an instance of this class - */ - public static FilterBarView load(Stage primaryStage, AnchorPane placeholder, - ObservableValue filter) { - - FilterBarView filterView = UiPartLoaderUtil - .loadUiPart(primaryStage, placeholder, new FilterBarView()); - filterView.configureLayout(); - filterView.configureProperties(); - filterView.bindListener(filter); - return filterView; - } - - /** - * Configure the UI layout of {@link FilterBarView} - */ - private void configureLayout() { - FxViewUtil.applyAnchorBoundaryParameters(filterViewPane, 0.0, 0.0, 0.0, 0.0); - } - - /** - * Initialise and configure the UI properties of {@link FilterBarView} - */ - private void configureProperties() { - initialiseAllViewFilters(); - selectOneViewFilter(TaskViewFilter.DEFAULT); - } - - /** - * Display all the {@link TaskViewFilter} on the {@link #filterViewPane} - */ - private void initialiseAllViewFilters() { - for (TaskViewFilter filter : TaskViewFilter.all()) { - appendEachViewFilter(filter); - } - } - - /** - * Add one {@link TaskViewFilter} on the {@link #filterViewPane} - * and save an instance to the {@link #taskFilterBoxesMap} - * @param filter to add onto the pane - */ - private void appendEachViewFilter(TaskViewFilter filter) { - HBox textContainer = constructViewFilterBox(filter); - taskFilterBoxesMap.put(filter, textContainer); - filterViewPane.getChildren().add(textContainer); - } - - /** - * Given a filter, construct a view element to be displayed on the {@link #filterViewPane} - * @param filter to be displayed - * @return a view element - */ - private HBox constructViewFilterBox(TaskViewFilter filter) { - String filterName = WordUtils.capitalize(filter.name); - String[] partitionedText = StringUtil.partitionStringAtPosition(filterName, filter.shortcutCharPosition); - - Label leftLabel = new Label(partitionedText[0]); - Label centreLabel = new Label(partitionedText[1]); - Label rightLabel = new Label(partitionedText[2]); - ViewStyleUtil.addClassStyles(centreLabel, ViewStyleUtil.STYLE_UNDERLINE); - - return new HBox(leftLabel, centreLabel, rightLabel); - } - - /** - * Binds this component with the {@link TaskViewFilter} property it listens to - */ - private void bindListener(ObservableValue filter) { - filter.addListener((observable, oldValue, newValue) -> selectOneViewFilter(newValue)); - } - - /** - * Select exactly one filter from {@link #filterViewPane} - */ - public void selectOneViewFilter(TaskViewFilter filter) { - clearAllViewFiltersSelection(); - selectViewFilter(filter); - } - - /* Helper Methods */ - /** - * Clears all selection from the {@link #filterViewPane} - */ - private void clearAllViewFiltersSelection() { - for (HBox filterBox : taskFilterBoxesMap.values()) { - ViewStyleUtil.removeClassStyles(filterBox, ViewStyleUtil.STYLE_SELECTED); - } - } - - /** - * Mark the filter as selected on {@link #filterViewPane} - * However, if filter is null, nothing is done. - */ - private void selectViewFilter(TaskViewFilter filter) { - if (filter != null) { - HBox filterBox = taskFilterBoxesMap.get(filter); - ViewStyleUtil.addClassStyles(filterBox, ViewStyleUtil.STYLE_SELECTED); - } - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - this.filterViewPane = (FlowPane) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} -``` -###### \src\main\java\seedu\todo\ui\view\HelpView.java -``` java -/** - * A view that displays all the help commands in a single view. - */ -public class HelpView extends UiPart { - - /* Constants */ - private static final String FXML = "HelpView.fxml"; - - /* Variables */ - private final Logger logger = LogsCenter.getLogger(HelpView.class); - - /*Layouts*/ - private VBox helpPanelView; - - @FXML private GridPane helpGrid; - - /** - * Loads and initialise the feedback view element to the placeHolder - * @param primaryStage of the application - * @param placeholder where the view element {@link #helpPanelView} should be placed - * @return an instance of this class - */ - public static HelpView load(Stage primaryStage, AnchorPane placeholder) { - HelpView helpView = UiPartLoaderUtil.loadUiPart(primaryStage, placeholder, new HelpView()); - helpView.configureLayout(); - helpView.hideHelpPanel(); - return helpView; - } - - /** - * Configure the UI layout of {@link CommandErrorView} - */ - private void configureLayout() { - FxViewUtil.applyAnchorBoundaryParameters(helpPanelView, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(helpGrid, 0.0, 0.0, 0.0, 0.0); - } - - /** - * Displays a list of commands into the helpPanelView - */ - public void displayCommandSummaries(List commandSummaries) { - this.showHelpPanel(); - helpGrid.getChildren().clear(); - int rowIndex = 0; - for (CommandSummary commandSummary : commandSummaries) { - appendCommandSummary(rowIndex++, commandSummary); - } - } - - /** - * Add a command summary to each row of the helpGrid - * @param rowIndex the row number to which the command summary should append to - * @param commandSummary to be displayed - */ - private void appendCommandSummary(int rowIndex, CommandSummary commandSummary) { - Text commandScenario = ViewGeneratorUtil - .constructText(commandSummary.scenario, ViewStyleUtil.STYLE_TEXT_4); - Text commandName = ViewGeneratorUtil - .constructText(commandSummary.command, ViewStyleUtil.STYLE_TEXT_4); - Text commandArgument = ViewGeneratorUtil - .constructText(" " + commandSummary.arguments, ViewStyleUtil.STYLE_TEXT_4); - - ViewStyleUtil.addClassStyles(commandArgument, ViewStyleUtil.STYLE_CODE); - ViewStyleUtil.addClassStyles(commandName, ViewStyleUtil.STYLE_CODE, ViewStyleUtil.STYLE_BOLDER); - - TextFlow combinedCommand = ViewGeneratorUtil.placeIntoTextFlow(commandName, commandArgument); - helpGrid.addRow(rowIndex, commandScenario, combinedCommand); - } - - /* Ui Methods */ - public void hideHelpPanel() { - FxViewUtil.setCollapsed(helpPanelView, true); - } - - private void showHelpPanel() { - FxViewUtil.setCollapsed(helpPanelView, false); - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - this.helpPanelView = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} -``` -###### \src\main\java\seedu\todo\ui\view\TaskCardView.java -``` java -/** - * This class links up with TaskCardView.fxml layout to display details of a given - * ReadOnlyTask to users via the TaskListPanel.fxml. - */ -public class TaskCardView extends UiPart { - /*Constants*/ - private static final String FXML = "TaskCardView.fxml"; - - private static final String TASK_TYPE = "Task"; - private static final String EVENT_TYPE = "Event"; - - /*Static Field*/ - /* - Provides a global reference between an ImmutableTask to the wrapper TaskCardView class, - since we have no direct access of TaskCardView from the ListView object. - */ - private static final Map taskCardMap = new HashMap<>(); - -``` -###### \src\main\java\seedu\todo\ui\view\TaskCardView.java -``` java - /** - * Displays all other view elements, including title, type label, pin image, description and location texts. - */ - private void displayEverythingElse() { - titleLabel.setText(String.valueOf(displayedIndex) + ". " + task.getTitle()); - pinImage.setVisible(task.isPinned()); - typeLabel.setText(task.isEvent() ? EVENT_TYPE : TASK_TYPE); - FxViewUtil.displayTextWhenAvailable(descriptionLabel, descriptionBox, task.getDescription()); - FxViewUtil.displayTextWhenAvailable(locationLabel, locationBox, task.getLocation()); - } - - /** - * Displays the tags in lexicographical order, ignoring case. - */ - private void displayTags(){ - List tagList = new ArrayList<>(task.getTags()); - if (tagList.isEmpty()) { - FxViewUtil.setCollapsed(tagsBox, true); - } else { - tagList.sort((o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString())); - for (Tag tag : tagList) { - Label tagLabel = ViewGeneratorUtil.constructRoundedText(tag.getTagName()); - tagsBox.getChildren().add(tagLabel); - } - } - } - - /** - * Sets style according to the status (e.g. completed, overdue, etc) of the task. - */ - private void setStyle() { - boolean isCompleted = task.isCompleted(); - boolean isOverdue = task.getEndTime().isPresent() - && timeUtil.isOverdue(task.getEndTime().get()) && !task.isEvent(); - boolean isOngoing = task.isEvent() - && timeUtil.isOngoing(task.getStartTime().get(), task.getEndTime().get()); - - if (isCompleted) { - ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_COMPLETED); - } else if (isOverdue) { - ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_OVERDUE); - } else if (isOngoing){ - ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_ONGOING); - } - } - - /** - * Initialise the view to show collapsed state if it can be collapsed, - * else hide the {@link #moreInfoLabel} otherwise. - */ - private void initialiseCollapsibleView() { - ViewStyleUtil.addRemoveClassStyles(true, taskCard, ViewStyleUtil.STYLE_COLLAPSED); - FxViewUtil.setCollapsed(moreInfoLabel, !isTaskCollapsible()); - FxViewUtil.setCollapsed(tagsBox, isTaskCollapsible()); - } - - /** - * Displays formatted task or event timings in the time field. - */ - private void displayTimings() { - String displayTimingOutput; - Optional startTime = task.getStartTime(); - Optional endTime = task.getEndTime(); - boolean isEventWithTime = task.isEvent() && startTime.isPresent() && endTime.isPresent(); - boolean isTaskWithTime = !task.isEvent() && endTime.isPresent(); - - if (isEventWithTime) { - displayTimingOutput = timeUtil.getEventTimeText(startTime.get(), endTime.get()); - } else if (isTaskWithTime) { - displayTimingOutput = timeUtil.getTaskDeadlineText(endTime.get()); - } else { - FxViewUtil.setCollapsed(dateBox, true); - return; - } - dateLabel.setText(displayTimingOutput); - } - - /** - * Allows timing, and deadline highlight style to be updated automatically. - */ - private void setTimingAutoUpdate() { - Timeline timeline = FxViewUtil.setRecurringUiTask(30, event -> { - displayTimings(); - setStyle(); - }); - timeline.play(); - } - - /* Methods interfacing with UiManager*/ - /** - * Toggles the task card's collapsed or expanded state, only if this card is collapsible. - */ - public void toggleCardCollapsing() { - if (isTaskCollapsible()) { - //Sets both the collapsed style of the card, and mark the visibility of the "more" label. - boolean isCollapsing = ViewStyleUtil.toggleClassStyle(taskCard, ViewStyleUtil.STYLE_COLLAPSED); - FxViewUtil.setCollapsed(moreInfoLabel, !isCollapsing); - FxViewUtil.setCollapsed(tagsBox, isCollapsing); - } - } - - /** - * Displays in the Ui whether this card is selected - * @param isSelected true when the card is selected - */ - public void markAsSelected(boolean isSelected) { - ViewStyleUtil.addRemoveClassStyles(isSelected, taskCard, ViewStyleUtil.STYLE_SELECTED); - } - - /* Helper Methods */ - /** - * Returns true if this task card can be collapsed, based on the information given from the - * {@link ImmutableTask} - */ - private boolean isTaskCollapsible() { - boolean hasDescription = task.getDescription().isPresent(); - boolean hasTags = !task.getTags().isEmpty(); - return hasDescription || hasTags; - } - - /* Getters */ - /** - * Gets the mapped {@link TaskCardView} object from an {@link ImmutableTask} object - * @param task that is being wrapped by the {@link TaskCardView} object - * @return a {@link TaskCardView} object that contains this task (can be null if not available) - */ - public static TaskCardView getTaskCard(ImmutableTask task) { - return taskCardMap.get(task); - } - - public int getDisplayedIndex() { - return displayedIndex; - } - - public VBox getLayout() { - return taskCard; - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - taskCard = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } -} -``` -###### \src\main\java\seedu\todo\ui\view\TodoListView.java -``` java -/** - * A panel that holds all the tasks inflated from TaskCardView. - */ -public class TodoListView extends UiPart { - /*Constants*/ - private static final String FXML = "TodoListView.fxml"; - - /*Variables*/ - private final Logger logger = LogsCenter.getLogger(TodoListView.class); - private VBox panel; - - /*Layout Declarations*/ - @FXML - private ListView todoListView; - -``` -###### \src\main\java\seedu\todo\ui\view\TodoListView.java -``` java - /* Ui Methods */ - /** - * Toggles the expanded/collapsed view of a task card. - * - * @param task The specific to be expanded or collapsed from view. - */ - public void toggleExpandCollapsed(ImmutableTask task) { - TaskCardView taskCardView = TaskCardView.getTaskCard(task); - if (taskCardView != null) { - taskCardView.toggleCardCollapsing(); - } - } - - /** - * Scrolls the {@link #todoListView} to the particular task card at the listIndex. - */ - public void scrollAndSelect(int listIndex) { - Platform.runLater(() -> { - todoListView.scrollTo(listIndex); - todoListView.getSelectionModel().clearAndSelect(listIndex); - }); - } - - /** - * Scrolls the {@link #todoListView} to the particular task card. - * - * @param task for the list to scroll to. - */ - public void scrollAndSelect(ImmutableTask task) { - TaskCardView taskCardView = TaskCardView.getTaskCard(task); - int listIndex = FxViewUtil.convertToListIndex(taskCardView.getDisplayedIndex()); - scrollAndSelect(listIndex); - } - - /* Override Methods */ - @Override - public void setNode(Node node) { - panel = (VBox) node; - } - - @Override - public String getFxmlPath() { - return FXML; - } - -``` -###### \src\main\java\seedu\todo\ui\view\TodoListView.java -``` java - /** - * Sets the style properties of a cell on the to-do list, that cannot be done in any other places. - */ - private void setTaskCardStyleProperties(TaskCardView taskCardView) { - this.setPadding(Insets.EMPTY); - this.selectedProperty().addListener((observable, oldValue, newValue) - -> taskCardView.markAsSelected(newValue)); - } - } -} -``` -###### \src\main\resources\style\DefaultStyle.css -``` css -.main { - -fx-background-color: #2D2D2D; - -fx-border-color: #00A4FF; - -fx-border-width: 1px; - -fx-padding: 12; - -fx-spacing: 4; -} - -.text1 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Black"; - -fx-font-size: 17pt; -} - -.text2 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 15pt; -} - -.text3 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 13pt; -} - -.text4 { - -fx-text-fill: #FFFFFF; - -fx-fill: #ffffff; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; -} - -.code { - -fx-font-family: "Consolas"; -} - -.bolder { - -fx-font-weight: bolder; -} - -.underline { - -fx-underline: true; -} - -.gridPanel { - -fx-hgap: 16pt; - -fx-vgap: 2pt; - -fx-background-color: #3D3D3D; - -fx-padding: 8 8 8 8; -} - -.spacingBig { - -fx-spacing: 16px; - -fx-hgap: 16px; - -fx-vgap: 16px; -} - -.spacing { - -fx-spacing: 8px; - -fx-hgap: 8px; - -fx-vgap: 8px; -} - -.spacingSmall { - -fx-spacing: 2px; - -fx-hgap: 2px; - -fx-vgap: 2px; -} - -.subheadingPadding { - -fx-padding: 0 0 0 2; -} - - -.commandFeedback{ - -fx-text-fill: #FFFFFF; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; -} - -.commandFeedback.error { - -fx-text-fill: #FF6464; -} - -.commandInput { - -fx-font-family: "Consolas"; - -fx-font-size: 20px; -} - -.commandInput.error { - -fx-border-color: #FF6464; - -fx-border-width: 2px; - -fx-text-fill: #FF6464; -} - -.commandError { - -fx-text-fill: #FFFFFF; - -fx-font-family: "Segoe UI"; - -fx-font-size: 12pt; - -fx-wrap-text: true; -} - -.roundLabel { - -fx-background-radius: 10; - -fx-text-fill: #ffffff; - -fx-background-color: #2D2D2D; - -fx-font-size: 10pt; - -fx-font-family: "Segoe UI Semilight"; -} - -/***View Filter Styles Start***/ -.viewFilter .label{ - -fx-font-family: "Segoe UI"; - -fx-font-size: 14pt; - -fx-text-fill: #FFFFFF; -} - -.viewFilter .selected { - -fx-background-color: #FFFFFF; - -fx-background-radius: 100; - -fx-padding: 0 8 0 8; -} - -.viewFilter .selected .label { - -fx-text-fill: #2D2D2D; - -fx-font-family: "Segoe UI Semibold"; -} - -``` -###### \src\main\resources\style\DefaultStyle.css -``` css -/***TaskCardView Styles Start***/ -/*Default and Base*/ -.taskCard .label { - -fx-font-smoothing-type: lcd; -} - -.taskCard .titleLabel { - -fx-font-size: 16pt; - -fx-font-family: "Segoe UI Semibold"; -} - -.taskCard .descriptionLabel { - -fx-font-size: 12pt; - -fx-font-family: "Segoe UI"; -} - -.taskCard .footnoteLabel { - -fx-font-size: 10pt; - -fx-font-family: "Segoe UI Semilight"; - -fx-font-style: italic; -} - -.taskCard .highlightedBackground { - -fx-background-color: #00A4FF; -} - -.taskCard .lightBackground { - -fx-background-color: #d2d2d2; -} - -.taskCard .pinImage { - -fx-image: url("../images/star_gold.png"); - -fx-fit-to-width: 30px; - -fx-fit-to-height: 30px; -} - -.taskCard .dateImage { - -fx-image: url("../images/clock_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -.taskCard .locationImage { - -fx-image: url("../images/location_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -/*Completed*/ -.completed .label{ - -fx-text-fill: #7F7F7F; -} - -.completed .titleLabel .text { - -fx-strikethrough: true; -} - -.completed .roundLabel { - -fx-background-color: #7F7F7F; - -fx-text-fill: #ffffff; -} - -.completed .pinImage { - -fx-image: url("../images/star_grey.png"); -} - -.completed .dateImage { - -fx-image: url("../images/clock_grey.png"); -} - -.completed .locationImage { - -fx-image: url("../images/location_grey.png"); -} - -/*Overdue*/ -.overdue { - -fx-background-color: #FF6464; -} - -.overdue .label { - -fx-text-fill: #FFFFFF; -} - -.overdue .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #FF6464; -} - -.overdue .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.overdue .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.overdue .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \src\main\resources\style\DefaultStyle.css -``` css -/*Selected*/ -.selected { - -fx-background-color: #00A4FF; -} - -.selected .label { - -fx-text-fill: #FFFFFF; -} - -.selected .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #00A4FF; -} - -.selected .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.selected .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.selected .locationImage { - -fx-image: url("../images/location_white.png"); -} - -/*Collapse*/ -.collapsed .collapsible { - visibility: collapse; - -fx-pref-height: 0px; - -fx-min-height: 0px; -} - -/***TaskCardView Styles End***/ - -``` -###### \src\test\java\guitests\AddCommandTest.java -``` java -/** - * Test the add command via GUI. - * Note: - * Order-ness of the tasks is not tested. - * Invalid command input is not tested. - */ -public class AddCommandTest extends TodoListGuiTest { - - @Test - public void add_initialData() { - //Test if the data has correctly loaded the data into view. - assertTrue(todoListView.isDisplayedCorrectly()); - } - - @Test - public void add_addTasks() { - //Add a task - ImmutableTask task1 = TaskFactory.task(); - executeAddTestHelper(task1); - - //Add another task - ImmutableTask task2 = TaskFactory.task(); - executeAddTestHelper(task2); - - //Add duplicated task - executeAddTestHelper(task2); - } - - @Test - public void add_addEvents() { - //Add an event - ImmutableTask event1 = TaskFactory.event(); - executeAddTestHelper(event1); - - //Add another event - ImmutableTask event2 = TaskFactory.event(); - executeAddTestHelper(event2); - - //Add duplicated task - executeAddTestHelper(event1); - } - - @Test - public void add_addManyRandom() { - //Add a long list of random task, which in the end spans at least 2 pages. - List randomTaskList = TaskFactory.list(15, 25); - randomTaskList.forEach(this::executeAddTestHelper); - } - - /* Helper Methods */ - /** - * Gets the index of the newly added task. - */ - private int getNewlyAddedTaskIndex() { - return TestUtil.compareAndGetIndex(previousTasksFromView, todoListView.getImmutableTaskList()); - } - - /** - * A helper method to run the entire add command process and testing. - */ - private void executeAddTestHelper(ImmutableTask task) { - updatePreviousTaskListFromView(); - executeAddCommand(task); - assertCorrectnessHelper(task); - } - - /** - * Executes an add command given a {@code task} - */ - private void executeAddCommand(ImmutableTask task) { - String commandText = CommandGeneratorUtil.generateAddCommand(task); - runCommand(commandText); - } - - private void assertCorrectnessHelper(ImmutableTask newTask) { - int addedIndex = getNewlyAddedTaskIndex(); - TaskCardViewHandle taskCardHandle = todoListView.getTaskCardViewHandle(addedIndex); - - assertAddSuccess(taskCardHandle, newTask, addedIndex); - assertCorrectFeedbackDisplayed(newTask); - assertCollapsed(taskCardHandle); - } - - /** - * Check the two following areas: - * 1. If the {@code task} added to the view is reflected correctly in it's own task card view. - * 2. If the remaining task cards are present and still displayed correctly in their own - * respective card views. - */ - private void assertAddSuccess(TaskCardViewHandle newTaskHandle, ImmutableTask newTask, int addedListIndex) { - int expectedDisplayedIndex = UiTestUtil.convertToUiIndex(addedListIndex); - - //Test for the newly added task. - assertTrue(newTaskHandle.isDisplayedCorrectly(expectedDisplayedIndex, newTask)); - - //Test for remaining tasks. - assertTrue(todoListView.isDisplayedCorrectly()); - } - - /** - * Check if the correct feedback message for adding has been displayed to the user. - */ - private void assertCorrectFeedbackDisplayed(ImmutableTask task) { - assertFeedbackMessage("\'" + task.getTitle() + "\' successfully added!"); - } - - /** - * Tasks should be collapsed when newly added. - * Checks the case where if the task is collapsible, - * then the task is in the collapsed state when newly added. - */ - private void assertCollapsed(TaskCardViewHandle newTask) { - if (newTask.isTaskCollapsible()) { - assertTrue(newTask.isTaskCardCollapsed()); - } - } -} -``` -###### \src\test\java\guitests\DeleteCommandTest.java -``` java -/** - * Test the delete command via GUI. - * Note: - * Invalid indices are not tested. - */ -public class DeleteCommandTest extends TodoListGuiTest { - - @Override - protected TodoList getInitialData() { - return getInitialDataHelper(10, 20); - } - - @Test - public void delete_correctBoundary() { - //delete the last item in the list - executeDeleteHelper(initialTaskData.size()); - - //delete the first in the list (note that we initialised the size to be > 2. - executeDeleteHelper(1); - - } - - @Test - public void delete_allTasks() { - //delete all the elements, until you have none left. - Random random = new Random(); - int remainingTasks = initialTaskData.size(); - while (remainingTasks > 0) { - int randomChoice = random.nextInt(remainingTasks--) + 1; - executeDeleteHelper(randomChoice); - } - } - - /** - * A helper method to run the entire delete command process and testing. - */ - private void executeDeleteHelper(int displayedIndex) { - ImmutableTask deletedTask = executeDeleteCommand(displayedIndex); - assertDeleteSuccess(deletedTask); - assertCorrectFeedbackDisplayed(deletedTask); - } - - /** - * Deletes a task from the to-do list view, and returns the deleted task for verification. - */ - private ImmutableTask executeDeleteCommand(int displayedIndex) { - int listIndex = UiTestUtil.convertToListIndex(displayedIndex); - String commandText = CommandGeneratorUtil.generateDeleteCommand(displayedIndex); - ImmutableTask deletedTask = todoListView.getTask(listIndex); - - runCommand(commandText); - return deletedTask; - } - - /** - * Check if the {@code task} deleted from the view is reflected correctly (i.e. it's no longer there) - * and check if the remaining tasks are displayed correctly. - */ - private void assertDeleteSuccess(ImmutableTask deletedTask) { - //Check if the task is really deleted. - assertFalse(todoListView.getImmutableTaskList().contains(deletedTask)); - - //Test if the remaining list is correct. - assertTrue(todoListView.isDisplayedCorrectly()); - } - - /** - * Check if the correct feedback message for deleting has been displayed to the user. - */ - private void assertCorrectFeedbackDisplayed(ImmutableTask task) { - assertFeedbackMessage("\'" + task.getTitle() + "\' successfully deleted!"); - } - -} -``` -###### \src\test\java\guitests\guihandles\CommandFeedbackViewHandle.java -``` java -/** - * A handler for retrieving feedback to user via {@link CommandFeedbackView} - */ -public class CommandFeedbackViewHandle extends GuiHandle { - /* Constants */ - public static final String FEEDBACK_VIEW_LABEL_ID = "#commandFeedbackLabel"; - - /** - * Constructs a handle to the {@link CommandFeedbackViewHandle} - * - * @param guiRobot {@link GuiRobot} for the current GUI test. - * @param primaryStage The stage where the views for this handle is located. - */ - public CommandFeedbackViewHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - /** - * Get the feedback {@link Label} object. - */ - private Label getFeedbackLabel() { - return (Label) getNode(FEEDBACK_VIEW_LABEL_ID); - } - - /** - * Get the text that is displayed on this {@link #getFeedbackLabel()} object. - */ - public String getText() { - return getFeedbackLabel().getText(); - } - - /** - * Returns true if the {@code feedbackMessage} matches the displayed message in the view. - */ - public boolean doesFeedbackMessageMatch(String feedbackMessage) { - return this.getText().equals(feedbackMessage); - } - - /** - * Returns true if this feedback view has error style applied. - */ - public boolean isErrorStyleApplied() { - return UiTestUtil.containsStyleClass(getFeedbackLabel(), "error"); - } -} -``` -###### \src\test\java\guitests\guihandles\CommandInputViewHandle.java -``` java -/** - * A handle to the {@link CommandInputView}'s command text box in the GUI. - */ -public class CommandInputViewHandle extends GuiHandle { - /* Constants */ - private static final String COMMAND_INPUT_FIELD_ID = "#commandTextField"; - - /** - * Constructs a handle to the {@link CommandInputView} - * - * @param guiRobot {@link GuiRobot} for the current GUI test. - * @param primaryStage The stage where the views for this handle is located. - */ - public CommandInputViewHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - /* Interfacing Methods */ - /** - * Types in the supplied command into the text box in {@link CommandInputView}. - * Note: any existing text in the text box will be replaced. - * Note: this method will not execute the command. See {@link #runCommand(String)}. - * @param command Command text to be supplied into the text box. - */ - public void enterCommand(String command) { - setTextAreaText(COMMAND_INPUT_FIELD_ID, command); - } - - /** - * Gets the command text that is inside the text box in {@link CommandInputView}. - * @return A command text. - */ - public String getCommandInput() { - return getTextAreaText(COMMAND_INPUT_FIELD_ID); - } - -``` -###### \src\test\java\guitests\guihandles\TaskCardViewHandle.java -``` java -/** - * Provides a handle to a {@link TaskCardView} - * that exists in {@link TodoListView} - */ -public class TaskCardViewHandle extends GuiHandle { - - /* Constants */ - private static final String TITLE_LABEL_ID = "#titleLabel"; - private static final String DESCRIPTION_LABEL_ID = "#descriptionLabel"; - private static final String DATE_LABEL_ID = "#dateLabel"; - private static final String LOCATION_LABEL_ID = "#locationLabel"; - - private static final String DESCRIPTION_BOX_ID = "#descriptionBox"; - private static final String DATE_BOX_ID = "#dateBox"; - private static final String LOCATION_BOX_ID = "#locationBox"; - - private static final String PIN_IMAGE_ID = "#pinImage"; - - private static final String TYPE_LABEL_ID = "#typeLabel"; - private static final String MOREINFO_LABEL_ID = "#moreInfoLabel"; - - /* Variables */ - private Node rootNode; - - /** - * Constructs a handle for {@link TaskCardView}. - * - * @param guiRobot The GUI test robot. - * @param primaryStage The main stage that is executed from the application's UI. - * @param rootNode Node that houses the contents of this Task Card. - */ - public TaskCardViewHandle(GuiRobot guiRobot, Stage primaryStage, Node rootNode){ - super(guiRobot, primaryStage, null); - this.rootNode = rootNode; - } - - /* Task Property Getters */ - public String getDisplayedTitle() { - return getTextFromLabel(TITLE_LABEL_ID); - } - - public String getDisplayedDescription() { - return getTextFromLabel(DESCRIPTION_LABEL_ID); - } - - public String getDisplayedDateText() { - return getTextFromLabel(DATE_LABEL_ID); - } - - public String getDisplayedLocation() { - return getTextFromLabel(LOCATION_LABEL_ID); - } - - public String getDisplayedTypeLabel() { - return getTextFromLabel(TYPE_LABEL_ID); - } - - public boolean getMoreInfoLabelVisibility() { - Node moreInfoLabel = getNode(MOREINFO_LABEL_ID); - return UiTestUtil.isDisplayed(moreInfoLabel); - } - - public boolean getDescriptionBoxVisibility() { - Node descriptionBox = getNode(DESCRIPTION_BOX_ID); - return UiTestUtil.isDisplayed(descriptionBox); - } - - public boolean getDateBoxVisibility() { - Node dateBox = getNode(DATE_BOX_ID); - return UiTestUtil.isDisplayed(dateBox); - } - - public boolean getLocationBoxVisibility() { - Node locationBox = getNode(LOCATION_BOX_ID); - return UiTestUtil.isDisplayed(locationBox); - } - - public boolean getPinImageVisibility() { - Node pinImage = getNode(PIN_IMAGE_ID); - return UiTestUtil.isDisplayed(pinImage); - } - - public boolean isSelectedStyleApplied() { - return UiTestUtil.containsStyleClass(rootNode, "selected"); - } - - public boolean isCompletedStyleApplied() { - return UiTestUtil.containsStyleClass(rootNode, "completed"); - } - - public boolean isOverdueStyleApplied() { - return UiTestUtil.containsStyleClass(rootNode, "overdue"); - } - - public boolean isTaskCardCollapsed() { - return UiTestUtil.containsStyleClass(rootNode, "collapsed"); - } - - public boolean isTaskCollapsible() { - return !getDisplayedDescription().isEmpty(); - } - - /* General Methods */ - /** - * Checks if the supplied index matches to what this node displays. - * To be used for nodes filtering. - */ - public boolean matchesTask(int displayedIndex) { - return getDisplayedTitle().startsWith(displayedIndex + ". "); - } - - /** - * Given an {@link ImmutableTask} and a displayed index, check if this view is displayed correctly. - * @param displayedIndex Index displayed in the view. - * @param task Task displayed in the view. - * @return Returns true only if and only if the elements in this view is displayed correctly. - * TODO: Maybe we do not need this at all. - */ - public boolean isDisplayedCorrectly(int displayedIndex, ImmutableTask task) { - //JUnit Assertion Test: To know which test are failing in detail - assertTrue(isTitleCorrect(displayedIndex, task)); - assertTrue(isDescriptionCorrect(task)); - assertTrue(isTaskCardCollapsedStateCorrect()); - assertTrue(isCompletedDisplayCorrect(task)); - assertTrue(isDateTextCorrect(task)); - assertTrue(isLocationCorrect(task)); - assertTrue(isTypeDisplayCorrect(task)); - assertTrue(isPinDisplayCorrect(task)); - assertTrue(isOverdueDisplayCorrect(task)); - return true; - } - - private boolean isTitleCorrect(int displayedIndex, ImmutableTask task) { - String expected = convertToDisplayedTitle(displayedIndex, task.getTitle()); - String actual = getDisplayedTitle(); - return expected.equals(actual); - } - - private boolean isDescriptionCorrect(ImmutableTask task) { - java.util.Optional description = task.getDescription(); - - if (description.isPresent()) { - //If there is a task description, it should match with the displayed description. - String expected = description.get(); - String actual = getDisplayedDescription(); - return expected.equals(actual); - } else { - //Description should be hidden when there is no description. - boolean expected = false; - boolean actual = getDescriptionBoxVisibility(); - return expected == actual; - } - } - - private boolean isDateTextCorrect(ImmutableTask task) { - java.util.Optional startTime = task.getStartTime(); - java.util.Optional endTime = task.getEndTime(); - - TimeUtil timeUtil = new TimeUtil(); - - String displayedDateText = getDisplayedDateText(); - String expectedDateText; - - if (!startTime.isPresent() && !endTime.isPresent()) { - //Date box should be hidden when there is no start and end time. - return !getDateBoxVisibility(); - } else if (startTime.isPresent() && endTime.isPresent()) { - //When start and end date are available, expect event format - expectedDateText = timeUtil.getEventTimeText(startTime.get(), endTime.get()); - } else if (endTime.isPresent()) { - //When only end time is present, expect deadline format - expectedDateText = timeUtil.getTaskDeadlineText(endTime.get()); - } else { - //Otherwise, illegal date state. - throw new IllegalStateException("Start time is present, but end time is not."); - } - return expectedDateText.equals(displayedDateText); - } - - private boolean isLocationCorrect(ImmutableTask task) { - java.util.Optional location = task.getLocation(); - - if (location.isPresent()) { - //If there is a location, it should match with the displayed location. - String expected = location.get(); - String actual = getDisplayedLocation(); - return expected.equals(actual); - } else { - //Description should be hidden when there is no description. - boolean expected = false; - boolean actual = getLocationBoxVisibility(); - return expected == actual; - } - } - - private boolean isPinDisplayCorrect(ImmutableTask task) { - boolean expected = task.isPinned(); - boolean actual = getPinImageVisibility(); - return expected == actual; - } - - private boolean isCompletedDisplayCorrect(ImmutableTask task) { - boolean expected = task.isCompleted(); - boolean actual = isCompletedStyleApplied(); - return expected == actual; - } - - private boolean isOverdueDisplayCorrect(ImmutableTask task) { - java.util.Optional endTime = task.getEndTime(); - boolean actual = isOverdueStyleApplied(); - boolean expected; - - if (endTime.isPresent() && !task.isEvent()) { - expected = seedu.todo.testutil.TimeUtil.isOverdue(endTime.get()); - } else { - expected = false; - } - return expected == actual; - } - - private boolean isTypeDisplayCorrect(ImmutableTask task) { - String actual = getDisplayedTypeLabel(); - String expected; - - if (task.isEvent()) { - expected = "Event"; - } else { - expected = "Task"; - } - - return expected.equals(actual); - } - - public boolean isTaskCardCollapsedStateCorrect() { - boolean collapsedStyleApplied = UiTestUtil.containsStyleClass(rootNode, "collapsed"); - boolean moreInfoLabelDisplayed = UiTestUtil.isDisplayed(getNode(MOREINFO_LABEL_ID)); - - if (isTaskCollapsible()) { - return collapsedStyleApplied == moreInfoLabelDisplayed; - } else { - return !moreInfoLabelDisplayed; - } - } - - /* View Elements Helper Methods */ -``` -###### \src\test\java\guitests\guihandles\TaskCardViewHandle.java -``` java - /** - * Gets a text from the node with {@code fieldId}. - * - * @param fieldId To get the node's text from. - * @return Returns the text presented in the node. - */ - private String getTextFromLabel(String fieldId) { - return ((Label) getNode(fieldId)).getText(); - } - - /** - * Converts {@code actualTitle} to a displayed title with the relevant {@code displayedIndex}. - * @param displayedIndex The index that is shown on the title displayed to the user. - * @param actualTitle The actual title of the task. - * @return Returns a title text that is actually displayed to the user. - */ - private String convertToDisplayedTitle(int displayedIndex, String actualTitle) { - return displayedIndex + ". " + actualTitle; - } - -``` -###### \src\test\java\guitests\guihandles\TodoListViewHandle.java -``` java -/** - * Provides a handle for the {@link TodoListView} - * containing a list of tasks. - */ -public class TodoListViewHandle extends GuiHandle { - - /* Constants */ - public static final int NOT_FOUND = -1; - private static final String TASK_CARD_ID = "#taskCard"; - private static final String TODO_LIST_VIEW_ID = "#todoListView"; - -``` -###### \src\test\java\guitests\guihandles\TodoListViewHandle.java -``` java - /** - * Gets a list of {@link ImmutableTask} - */ - public List getImmutableTaskList() { - return getTodoListView().getItems(); - } - - /** - * Gets a set of task card nodes in this to-do list. - */ - public Set getAllTaskCardNodes() { - return guiRobot.lookup(TASK_CARD_ID).queryAll(); - } - - /** - * Gets a specific task from the list of tasks stored in the view by the list index. - */ - public ImmutableTask getTask(int listIndex) { - return getImmutableTaskList().get(listIndex); - } - - /** - * Gets the first occurring element from {@link #getImmutableTaskList()} - * where the detail matches the {@code task} param. - * - * @return Returns a value bounded from 0 to length - 1. - * Also returns {@code NOT_FOUND} (value of -1) if not found in the list. - */ - public int getFirstTaskIndex(ImmutableTask task) { - List tasks = getImmutableTaskList(); - for (int listIndex = 0; listIndex < tasks.size(); listIndex++) { - ImmutableTask taskInList = tasks.get(listIndex); - if (TestUtil.isShallowEqual(task, taskInList)) { - return listIndex; - } - } - return NOT_FOUND; - } - - /** - * Gets a {@link TaskCardViewHandle} object with the position {@code listIndex} located in the to-do list view. - * Guaranteed unique result for identical tasks. - * Inherits the behaviour from {@link #getTaskCardViewNode(int)}. - * - * @param listIndex Index of the {@link #getImmutableTaskList()}. - * @return An instance of the handle. - */ - public TaskCardViewHandle getTaskCardViewHandle(int listIndex) { - Node taskCardNode = getTaskCardViewNode(listIndex); - if (taskCardNode != null) { - return new TaskCardViewHandle(guiRobot, primaryStage, taskCardNode); - } else { - return null; - } - } - - /** - * Gets a {@link Node} object with the position {@code listIndex} located in the to-do list view. - * Guarantees: - * - Unique result for identical tasks, because we are referencing from the index displayed - * in the UI. - * - The object returned will never be null. - * This means if I can't find the object, an exception will be thrown. - * Behaviour: - * Because a task card node is only drawn when the task card is shown on the screen, - * this method will automatically scroll the to-do list view to the position where the - * node is drawn. - * - * @param listIndex Index of the {@link #getImmutableTaskList()} - * (must be a valid value from 0 to length of task list - 1, - * else {@link RuntimeException} is thrown. - * @return An instance of the node. Guarantees non-null. - * @throws NodeFinderException if we can't find the node after finite attempts. - */ - private Node getTaskCardViewNode(int listIndex) throws NodeFinderException { - int displayedIndex = UiTestUtil.convertToUiIndex(listIndex); - Optional possibleNode; - int attemptCounter = 0; - - do { - Platform.runLater(() -> getTodoListView().scrollTo(listIndex)); - guiRobot.sleep(50 * attemptCounter++); //Allow the new nodes to be loaded from scrolling. - - Set taskCardNodes = getAllTaskCardNodes(); - possibleNode = taskCardNodes.stream().filter(node -> { - TaskCardViewHandle taskCardView = new TaskCardViewHandle(guiRobot, primaryStage, node); - return taskCardView.matchesTask(displayedIndex); - }).findFirst(); - } while (!possibleNode.isPresent() && attemptCounter < 50); - - if (possibleNode.isPresent()) { - return possibleNode.get(); - } else { - String errorMessage - = "Either the node fails to draw on the screen, or you provided an invalid index " - + listIndex + " where the number of nodes is " + getAllTaskCardNodes().size(); - throw new NodeFinderException(errorMessage, NodeFinderException.ErrorType.NO_NODES_FOUND); - } - } - - /** - * Checks if all the tasks stored inside the to-do list are displayed correctly. - * Note: This does not check the sorted-ness of the list. - * @return True if all the items are correctly displayed. - */ - public boolean isDisplayedCorrectly() { - return doesTodoListMatch(); - } - - /** - * Given a list of tasks, check if all the tasks in the list are displayed correctly in the - * {@link TodoListView}. - * Note: this does not check the sorted-ness of the list. - * - * @return True if all the tasks in the {@code tasks} are displayed correctly. - */ - public boolean doesTodoListMatch() { - boolean outcome = true; - List tasks = getImmutableTaskList(); - for (int listIndex = 0; listIndex < tasks.size(); listIndex++) { - ImmutableTask task = tasks.get(listIndex); - TaskCardViewHandle handle = getTaskCardViewHandle(listIndex); - int displayedIndex = UiTestUtil.convertToUiIndex(listIndex); - outcome &= handle.isDisplayedCorrectly(displayedIndex, task); - } - return outcome; - } - - /** - * Clicks on the ListView. - */ - public void clickOnListView() { - Point2D point= TestUtil.getScreenMidPoint(getTodoListView()); - 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 containsInOrder(int startPosition, ImmutableTask... tasks) { - List taskList = getImmutableTaskList(); - - // Return false if the list in panel is too short to contain the given list - if (startPosition + tasks.length > taskList.size()){ - return false; - } - - // Return false if any of the task doesn't match - for (int i = 0; i < tasks.length; i++) { - ImmutableTask taskInView = taskList.get(i); - ImmutableTask taskInList = tasks[i]; - if (!taskInView.equals(taskInList)) { - return false; - } - } - - return true; - } - - /** - * Navigates the {@link TodoListView} to display and select the task. - * @param listIndex Index of the list that the list view should navigate to. - * @return Handle of the selected task. - */ - public TaskCardViewHandle navigateToTask(int listIndex) { - guiRobot.interact(() -> { - getTodoListView().scrollTo(listIndex); - guiRobot.sleep(150); - getTodoListView().getSelectionModel().select(listIndex); - }); - guiRobot.sleep(100); - return getTaskCardViewHandle(listIndex); - } -} -``` -###### \src\test\java\seedu\todo\commons\util\StringUtilTest.java -``` java - @Test - public void partitionStringAtPosition_emptyString() { - String[] expected = {"", "", ""}; - - //Tests null string - testPartitionStringAtPositionHelper(null, 0, expected); - - //Test empty String - testPartitionStringAtPositionHelper("", 0, expected); - } - - @Test - public void partitionStringAtPosition_positionOutOfBounds() { - String input = "I have a Pikachu"; - String[] expected = {"", "", ""}; - - //Tests position too low - testPartitionStringAtPositionHelper(input, -1, expected); - - //Tests position too high - testPartitionStringAtPositionHelper(input, 16, expected); - } - - @Test - public void partitionStringAtPosition_partitionCorrectly() { - String input = "I have a Pikachu"; - - //Test lower bound - testPartitionStringAtPositionHelper(input, 0, new String[] {"", "I", " have a Pikachu"}); - - //Test upper bound - testPartitionStringAtPositionHelper(input, 15, new String[] {"I have a Pikach", "u", ""}); - - //Test normal partition - testPartitionStringAtPositionHelper(input, 5, new String[] {"I hav", "e", " a Pikachu"}); - } - - /** - * Helper method to test partitionStringAtPosition(...). - * @param input String to be partitioned. - * @param position Position where partition should take place. - * @param expected Expected output as String array. - */ - private void testPartitionStringAtPositionHelper(String input, int position, String[] expected) { - String[] outcome = StringUtil.partitionStringAtPosition(input, position); - assertArrayEquals(expected, outcome); - } - - @Test - public void splitString_emptyInput() { - String[] expected = new String[0]; - - //Test null input. - testSplitStringHelper(null, expected); - - //Test empty input. - testSplitStringHelper("", expected); - - //Test only space and commas. - testSplitStringHelper(" , , ,,, ,,,, , , ,,, , ,", expected); - } - - @Test - public void splitString_validInput() { - //Input does not include space and comma - testSplitStringHelper("!@(*&$!R#@%", new String[]{"!@(*&$!R#@%"}); - - //Test one element - testSplitStringHelper("Pichu-Pikachu_RAICHU's", new String[] {"Pichu-Pikachu_RAICHU's"}); - - //Test multiple element split by space and comma - testSplitStringHelper("an apple a, day, keeps , , doctor ,,, away", - new String[] {"an", "apple", "a", "day", "keeps", "doctor", "away"}); - } - - /** - * Helper method to test splitString(...). - * @param input String to be split. - * @param expected Expected output as String array. - */ - private void testSplitStringHelper(String input, String[] expected) { - String[] outcome = StringUtil.splitString(input); - assertArrayEquals(expected, outcome); - } - - @Test - public void testConvertListToString_emptyList() { - String expected = ""; - - //Test null list - testConvertListToStringHelper(null, expected); - - //Test empty list - testConvertListToStringHelper(new String[0], expected); - } - - @Test - public void testConvertListToString_validInput() { - //Test one element - testConvertListToStringHelper(new String[]{"applepie123!"}, "applepie123!"); - - //Test several elements - testConvertListToStringHelper(new String[]{"this", "is", "apple", "pen"}, "this, is, apple, pen"); - } - - /** - * Helper method to test splitString(...). - * @param input String to be split. - * @param expected Expected output as String array. - */ - private void testConvertListToStringHelper(String[] input, String expected) { - String outcome = StringUtil.convertListToString(input); - assertEquals(expected, outcome); - } - -``` -###### \src\test\java\seedu\todo\commons\util\TimeUtilTest.java -``` java -/** - * Tests the TimeUtil class - */ -public class TimeUtilTest { - - /** - * A subclass of TimeUtil that provides the ability to override the current system time, - * so that time sensitive components can be conveniently tested. - */ - private class ModifiedTimeUtil extends TimeUtil { - - /** - * Construct a ModifiedTimeUtil object overriding the current time with Clock object. - * Is only used for dependency injection in testing time sensitive components. - */ - private ModifiedTimeUtil(Clock clock) { - this.clock = clock; - } - - /** - * Construct a ModifiedTimeUtil object overriding the current time with LocalDateTime object. - * Is only used for dependency injection in testing time sensitive components. - */ - public ModifiedTimeUtil(LocalDateTime pseudoCurrentTime) { - this(Clock.fixed(pseudoCurrentTime.toInstant( - ZoneId.systemDefault().getRules().getOffset(pseudoCurrentTime)), ZoneId.systemDefault())); - } - } - - /** - * Aids to test taskDeadlineText with a current time and due time, against an expected output. - */ - private void testTaskDeadlineTextHelper(String expectedOutput, LocalDateTime currentTime, - LocalDateTime dueTime) { - TimeUtil timeUtil = new ModifiedTimeUtil(currentTime); - String generatedOutput = timeUtil.getTaskDeadlineText(dueTime); - assertEquals(expectedOutput, generatedOutput); - } - - /** - * Aids to test eventTimeText with a current time, startTime and endTime, against an expected output. - */ - private void testEventTimeTextHelper(String expectedOutput, LocalDateTime currentTime, - LocalDateTime startTime, LocalDateTime endTime) { - TimeUtil timeUtil = new ModifiedTimeUtil(currentTime); - String generatedOutput = timeUtil.getEventTimeText(startTime, endTime); - assertEquals(expectedOutput, generatedOutput); - } - - @Test - public void getTaskDeadlineString_nullEndTime() { - testTaskDeadlineTextHelper("", LocalDateTime.now(), null); - } - - @Test - public void getTaskDeadlineText_dueNow() { - String expectedOutput = "due now"; - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Lower Bound: 0th second - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0), dueTime); - //Upper Bound: 59th second - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 59), dueTime); - //In the middle: 30th second - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 30), dueTime); - } - - @Test - public void getTaskDeadlineText_dueLessThanAMinute() { - String expectedOutput = "in less than a minute"; - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Lower bound: 59 seconds left - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 59, 1), dueTime); - //Upper bound: 1 second left - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 59, 59), dueTime); - //In the middle: 30 seconds left - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 59, 30), dueTime); - } - - @Test - public void getTaskDeadlineText_aMinuteBeforeDeadline() { - String expectedOutput = "in 1 minute"; - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Upper Bound - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 59, 0), dueTime); - //In the middle - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 58, 30), dueTime); - //Lower Bound - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 11, 58, 1), dueTime); - } - - @Test - public void getTaskDeadlineText_aMinuteAfterDeadline() { - String expectedOutput = "1 minute ago"; - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Lower Bound - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 1, 0), dueTime); - //Middle - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 1, 30), dueTime); - //Upper Bound - testTaskDeadlineTextHelper(expectedOutput, LocalDateTime.of(2016, Month.MARCH, 20, 12, 1, 59), dueTime); - } - - @Test - public void getTaskDeadlineText_minutesBeforeDeadline() { - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Test 2 min to 59 min - for (int minutesLeft = 2; minutesLeft <= 59; minutesLeft++) { - String expectedOutput = "in " + minutesLeft + " minutes"; - - testTaskDeadlineTextHelper(expectedOutput, dueTime.minusMinutes(minutesLeft), dueTime); - testTaskDeadlineTextHelper(expectedOutput, dueTime.minusMinutes(minutesLeft).minusSeconds(30), dueTime); - testTaskDeadlineTextHelper(expectedOutput, dueTime.minusMinutes(minutesLeft).minusSeconds(59), dueTime); - } - } - - @Test - public void getTaskDeadlineText_minutesAfterDeadline() { - LocalDateTime dueTime = LocalDateTime.of(2016, Month.MARCH, 20, 12, 0, 0); - - //Test 2 min to 59 min - for (int minutesLater = 2; minutesLater <= 59; minutesLater++) { - String expectedOutput = minutesLater + " minutes ago"; - - testTaskDeadlineTextHelper(expectedOutput, dueTime.plusMinutes(minutesLater), dueTime); - testTaskDeadlineTextHelper(expectedOutput, dueTime.plusMinutes(minutesLater).plusSeconds(30), dueTime); - testTaskDeadlineTextHelper(expectedOutput, dueTime.plusMinutes(minutesLater).plusSeconds(59), dueTime); - } - } - - @Test - public void getTaskDeadlineText_todayBeforeDeadline() { - testTaskDeadlineTextHelper("by today, 5:59 PM", - LocalDateTime.of(2016, Month.MARCH, 20, 10, 45), LocalDateTime.of(2016, Month.MARCH, 20, 17, 59)); - testTaskDeadlineTextHelper("by tonight, 6:00 PM", - LocalDateTime.of(2016, Month.MARCH, 20, 11, 58), LocalDateTime.of(2016, Month.MARCH, 20, 18, 0)); - testTaskDeadlineTextHelper("in 30 minutes", - LocalDateTime.of(2016, Month.MARCH, 20, 0, 0), LocalDateTime.of(2016, Month.MARCH, 20, 0, 30)); - } - - @Test - public void getTaskDeadlineText_todayAfterDeadline() { - testTaskDeadlineTextHelper("since today, 12:00 PM", - LocalDateTime.of(2016, Month.MARCH, 20, 18, 45), LocalDateTime.of(2016, Month.MARCH, 20, 12, 0)); - testTaskDeadlineTextHelper("since tonight, 6:50 PM", - LocalDateTime.of(2016, Month.MARCH, 20, 23, 58), LocalDateTime.of(2016, Month.MARCH, 20, 18, 50)); - testTaskDeadlineTextHelper("30 minutes ago", - LocalDateTime.of(2016, Month.MARCH, 20, 0, 30), LocalDateTime.of(2016, Month.MARCH, 20, 0, 0)); - } - - @Test - public void getTaskDeadlineText_tomorrowBeforeDeadline() { - testTaskDeadlineTextHelper("by tomorrow, 12:00 PM", - LocalDateTime.of(2016, Month.MARCH, 20, 12, 0), LocalDateTime.of(2016, Month.MARCH, 21, 12, 0)); - testTaskDeadlineTextHelper("by tomorrow, 12:51 AM", - LocalDateTime.of(2016, Month.MARCH, 20, 23, 50), LocalDateTime.of(2016, Month.MARCH, 21, 0, 51)); - testTaskDeadlineTextHelper("in 20 minutes", - LocalDateTime.of(2016, Month.MARCH, 20, 23, 50), LocalDateTime.of(2016, Month.MARCH, 21, 0, 10)); - } - - @Test - public void getTaskDeadlineText_yesterdayAfterDeadline() { - testTaskDeadlineTextHelper("since yesterday, 12:00 PM", - LocalDateTime.of(2016, Month.MARCH, 21, 12, 0), LocalDateTime.of(2016, Month.MARCH, 20, 12, 0)); - testTaskDeadlineTextHelper("since yesterday, 12:51 AM", - LocalDateTime.of(2016, Month.MARCH, 21, 23, 50), LocalDateTime.of(2016, Month.MARCH, 20, 0, 51)); - testTaskDeadlineTextHelper("20 minutes ago", - LocalDateTime.of(2016, Month.MARCH, 21, 0, 10), LocalDateTime.of(2016, Month.MARCH, 20, 23, 50)); - } - - @Test - public void getTaskDeadlineText_thisYearBeforeDeadline() { - testTaskDeadlineTextHelper("by 12 August, 12:55 PM", - LocalDateTime.of(2016, Month.JANUARY, 21, 12, 0), - LocalDateTime.of(2016, Month.AUGUST, 12, 12, 55)); - - testTaskDeadlineTextHelper("by 15 September, 12:00 AM", - LocalDateTime.of(2016, Month.SEPTEMBER, 13, 23, 59), - LocalDateTime.of(2016, Month.SEPTEMBER, 15, 0, 0)); - } - - @Test - public void getTaskDeadlineText_thisYearAfterDeadline() { - testTaskDeadlineTextHelper("since 21 January, 8:47 PM", - LocalDateTime.of(2016, Month.AUGUST, 12, 12, 55), - LocalDateTime.of(2016, Month.JANUARY, 21, 20, 47)); - - testTaskDeadlineTextHelper("since 13 September, 11:59 PM", - LocalDateTime.of(2016, Month.SEPTEMBER, 15, 0, 0), - LocalDateTime.of(2016, Month.SEPTEMBER, 13, 23, 59)); - } - - @Test - public void getTaskDeadlineText_differentYearBeforeDeadline() { - testTaskDeadlineTextHelper("in 1 minute", - LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59), - LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0)); - testTaskDeadlineTextHelper("by tomorrow, 1:15 AM", - LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 0), - LocalDateTime.of(2017, Month.JANUARY, 1, 1, 15)); - testTaskDeadlineTextHelper("by 31 January 2017, 1:05 AM", - LocalDateTime.of(2016, Month.JUNE, 30, 22, 0), - LocalDateTime.of(2017, Month.JANUARY, 31, 1, 5)); - testTaskDeadlineTextHelper("by 31 August 2020, 12:35 PM", - LocalDateTime.of(2016, Month.FEBRUARY, 13, 13, 0), - LocalDateTime.of(2020, Month.AUGUST, 31, 12, 35)); - } - - @Test - public void getTaskDeadlineText_differentYearAfterDeadline() { - testTaskDeadlineTextHelper("1 minute ago", - LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0), - LocalDateTime.of(2016, Month.DECEMBER, 31, 23, 59)); - testTaskDeadlineTextHelper("since yesterday, 12:00 AM", - LocalDateTime.of(2017, Month.JANUARY, 1, 0, 0), - LocalDateTime.of(2016, Month.DECEMBER, 31, 0, 0)); - testTaskDeadlineTextHelper("since 30 June 2016, 10:00 PM", - LocalDateTime.of(2017, Month.JANUARY, 31, 1, 5), - LocalDateTime.of(2016, Month.JUNE, 30, 22, 0)); - testTaskDeadlineTextHelper("since 13 February 2016, 1:00 PM", - LocalDateTime.of(2020, Month.AUGUST, 31, 12, 35), - LocalDateTime.of(2016, Month.FEBRUARY, 13, 13, 0)); - } - - @Test - public void getEventTimeText_nullStartTime() { - testEventTimeTextHelper("", LocalDateTime.now(), null, LocalDateTime.now().plusMinutes(1)); - } - - @Test - public void getEventTimeText_nullEndTime() { - testEventTimeTextHelper("", LocalDateTime.now(), LocalDateTime.now().plusMinutes(1), null); - } - - @Test - public void getEventTimeText_sameDay() { - LocalDateTime currentTime = LocalDateTime.of(2016, 10, 20, 12, 00); - testEventTimeTextHelper("yesterday, from 4:50 PM to 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 19, 16, 50), LocalDateTime.of(2016, 10, 19, 20, 30)); - testEventTimeTextHelper("today, from 4:50 PM to 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 20, 16, 50), LocalDateTime.of(2016, 10, 20, 20, 30)); - testEventTimeTextHelper("tonight, from 6:00 PM to 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 20, 18, 00), LocalDateTime.of(2016, 10, 20, 20, 30)); - testEventTimeTextHelper("tomorrow, from 4:50 PM to 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 21, 16, 50), LocalDateTime.of(2016, 10, 21, 20, 30)); - testEventTimeTextHelper("21 November, from 4:50 PM to 8:30 PM", currentTime, - LocalDateTime.of(2016, 11, 21, 16, 50), LocalDateTime.of(2016, 11, 21, 20, 30)); - testEventTimeTextHelper("21 November 2017, from 4:50 PM to 8:30 PM", currentTime, - LocalDateTime.of(2017, 11, 21, 16, 50), LocalDateTime.of(2017, 11, 21, 20, 30)); - } - - @Test - public void getEventTimeText_differentDay() { - LocalDateTime currentTime = LocalDateTime.of(2016, 10, 20, 12, 00); - testEventTimeTextHelper("from yesterday, 4:50 PM to today, 2:30 PM", currentTime, - LocalDateTime.of(2016, 10, 19, 16, 50), LocalDateTime.of(2016, 10, 20, 14, 30)); - testEventTimeTextHelper("from yesterday, 4:50 PM to tonight, 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 19, 16, 50), LocalDateTime.of(2016, 10, 20, 20, 30)); - testEventTimeTextHelper("from today, 4:50 PM to tomorrow, 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 20, 16, 50), LocalDateTime.of(2016, 10, 21, 20, 30)); - testEventTimeTextHelper("from tonight, 6:50 PM to tomorrow, 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 20, 18, 50), LocalDateTime.of(2016, 10, 21, 20, 30)); - testEventTimeTextHelper("from tomorrow, 6:50 PM to 22 October, 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 21, 18, 50), LocalDateTime.of(2016, 10, 22, 20, 30)); - testEventTimeTextHelper("from 18 October, 6:50 PM to 22 October, 8:30 PM", currentTime, - LocalDateTime.of(2016, 10, 18, 18, 50), LocalDateTime.of(2016, 10, 22, 20, 30)); - } - - @Test - public void getEventTimeText_differentYear() { - LocalDateTime currentTime = LocalDateTime.of(2016, 12, 31, 12, 00); - testEventTimeTextHelper("from 19 October, 4:50 PM to 3 January 2017, 2:30 PM", currentTime, - LocalDateTime.of(2016, 10, 19, 16, 50), LocalDateTime.of(2017, 1, 3, 14, 30)); - testEventTimeTextHelper("from 19 October, 4:50 PM to tomorrow, 2:30 PM", currentTime, - LocalDateTime.of(2016, 10, 19, 16, 50), LocalDateTime.of(2017, 1, 1, 14, 30)); - testEventTimeTextHelper("from today, 4:50 PM to 4 January 2017, 2:30 PM", currentTime, - LocalDateTime.of(2016, 12, 31, 16, 50), LocalDateTime.of(2017, 1, 4, 14, 30)); - } - - @Test - public void isOverdue_nullEndTime() { - TimeUtil timeUtil = new TimeUtil(); - assertFalse(timeUtil.isOverdue(null)); - } - - @Test - public void isOverdue_endTimeAfterNow() { - TimeUtil timeUtil = new ModifiedTimeUtil(LocalDateTime.of(2016, Month.DECEMBER, 12, 12, 34)); - LocalDateTime laterEndTime = LocalDateTime.of(2016, Month.DECEMBER, 12, 12, 35); - assertFalse(timeUtil.isOverdue(laterEndTime)); - } - - @Test - public void isOverdue_endTimeBeforeNow() { - TimeUtil timeUtil = new ModifiedTimeUtil(LocalDateTime.of(2016, Month.DECEMBER, 12, 12, 36)); - LocalDateTime laterEndTime = LocalDateTime.of(2016, Month.DECEMBER, 12, 12, 35); - assertTrue(timeUtil.isOverdue(laterEndTime)); - } - -``` -###### \src\test\java\seedu\todo\testutil\TaskFactory.java -``` java - /** - * Generates a list of random tasks with a random size between {@code lowerBound} - * and {@code upperBound} inclusive. - * - * @return Returns a randomly generated list. - */ - public static List list(int lowerBound, int upperBound) { - int size = lowerBound + random.nextInt(upperBound - lowerBound + 1); - ArrayList tasks = new ArrayList<>(); - for (int i = 0; i < size; i++) { - tasks.add(random()); - } - return tasks; - } -} -``` -###### \src\test\java\seedu\todo\testutil\TimeUtil.java -``` java - /** - * Checks against system time if the provided dueTime is before system time. - * - * @param dueTime The due time to check against with the current system time. - * @return Returns true if the provided dueTime is before system time. - */ - public static boolean isOverdue(LocalDateTime dueTime) { - return dueTime.isBefore(LocalDateTime.now()); - } - - /** - * Gets the complete date time text in the following format: - * 12 August 2015, 12:34 PM - */ - public static String getDateTimeText(LocalDateTime dateTime) { - return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_FULL_DATE)); - } -} -``` -###### \src\test\java\seedu\todo\testutil\UiTestUtil.java -``` java -/** - * This class stores all the commonly used helper functions that aids in GUI testing. - */ -public class UiTestUtil { - - /** - * Checks if the node is displayed on the user interface by checking its properties. - * - * @param node The node to be tested for whether it is displayed on the UI. - * @return Returns true if the node is displayed, false otherwise. - */ - public static boolean isDisplayed(Node node) { - //Checks if the node is hidden programatically - boolean isVisible = node.isVisible(); - - //Otherwise, check if the node is hidden with CSS styling - boolean isCollapsed = parentContainsStyleClass(node, "collapsed"); - boolean isCollapsible = containsStyleClass(node, "collapsible"); - - return isVisible && !(isCollapsed && isCollapsible); - } - - public static boolean containsStyleClass(Node node, String styleClass) { - return node.getStyleClass().contains(styleClass); - } - - public static boolean parentContainsStyleClass(Node node, String styleClass) { - return node.getStyleableParent().getStyleClass().contains(styleClass); - } - - /** - * Converts an index from a list to the index that is displayed to the user via the Ui - */ - public static int convertToListIndex(int uiIndex) { - return uiIndex - 1; - } - - /** - * Converts an index displayed on the Ui to the user, to the index used on the list - */ - public static int convertToUiIndex(int listIndex) { - return listIndex + 1; - } - -} -``` diff --git a/collated/A0135805Hreuse.md b/collated/A0135805Hreuse.md deleted file mode 100644 index 530fe13ac225..000000000000 --- a/collated/A0135805Hreuse.md +++ /dev/null @@ -1,23 +0,0 @@ -# A0135805Hreuse -###### \src\main\java\seedu\todo\model\tag\UniqueTagCollection.java -``` java - /* Other Override Methods */ - @Override - public Iterator iterator() { - return uniqueTagsToTasksMap.keySet().iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniqueTagCollection // instanceof handles nulls - && this.uniqueTagsToTasksMap.equals( - ((UniqueTagCollection) other).uniqueTagsToTasksMap)); - } - - @Override - public int hashCode() { - return uniqueTagsToTasksMap.hashCode(); - } -} -``` diff --git a/collated/A0135805Hreused.md b/collated/A0135805Hreused.md deleted file mode 100644 index c1bdf0e58117..000000000000 --- a/collated/A0135805Hreused.md +++ /dev/null @@ -1,351 +0,0 @@ -# A0135805Hreused -###### \src\main\java\seedu\todo\model\tag\Tag.java -``` java - /* Override Methods */ - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag && this.tagName.equals(((Tag) other).tagName)) // if is tag - || (other instanceof String && this.tagName.equals(other)); // if is string - //Enables string comparison for hashing. - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - - /* Getters */ - public String getTagName() { - return tagName; - } -} -``` -###### \src\main\java\seedu\todo\ui\view\CommandErrorView.java -``` java - /** - * Loads and initialise the feedback view element to the placeHolder - * @param primaryStage of the application - * @param placeHolder where the view element {@link #errorViewBox} should be placed - * @return an instance of this class - */ - public static CommandErrorView load(Stage primaryStage, AnchorPane placeHolder) { - CommandErrorView errorView = UiPartLoaderUtil - .loadUiPart(primaryStage, placeHolder, new CommandErrorView()); - errorView.configureLayout(); - errorView.hideCommandErrorView(); - return errorView; - } - -``` -###### \src\main\java\seedu\todo\ui\view\CommandFeedbackView.java -``` java - /** - * Loads and initialise the feedback view element to the placeholder. - * - * @param primaryStage The main stage of the application. - * @param placeholder The place where the view element {@link #textContainer} should be placed. - * @return An instance of this class. - */ - public static CommandFeedbackView load(Stage primaryStage, AnchorPane placeholder) { - CommandFeedbackView feedbackView = UiPartLoaderUtil - .loadUiPart(primaryStage, placeholder, new CommandFeedbackView()); - feedbackView.configureLayout(); - return feedbackView; - } - - /** - * Configure the UI layout of {@link CommandFeedbackView}. - */ - private void configureLayout() { - FxViewUtil.applyAnchorBoundaryParameters(textContainer, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(commandFeedbackLabel, 0.0, 0.0, 0.0, 0.0); - } - -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java - /** - * Loads and initialise the input view element to the placeHolder - * @param primaryStage of the application - * @param placeHolder where the view element {@link #commandInputPane} should be placed - * @return an instance of this class - */ - public static CommandInputView load(Stage primaryStage, AnchorPane placeHolder) { - CommandInputView commandInputView = UiPartLoaderUtil - .loadUiPart(primaryStage, placeHolder, new CommandInputView()); - commandInputView.configureLayout(); - commandInputView.configureProperties(); - return commandInputView; - } - - /** - * Configure the UI layout of {@link CommandInputView} - */ - private void configureLayout() { - FxViewUtil.applyAnchorBoundaryParameters(commandInputPane, 0.0, 0.0, 0.0, 0.0); - FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); - } - -``` -###### \src\main\java\seedu\todo\ui\view\TaskCardView.java -``` java - /*Layout Declarations*/ - @FXML - private VBox taskCard; - @FXML - private ImageView pinImage; - @FXML - private Label titleLabel; - @FXML - private Label typeLabel, moreInfoLabel; - @FXML - private Label descriptionLabel, dateLabel, locationLabel; - @FXML - private HBox descriptionBox, dateBox, locationBox; - @FXML - private FlowPane tagsBox; - - /* Variables */ - private ImmutableTask task; - private int displayedIndex; - private TimeUtil timeUtil = new TimeUtil(); - - /* Default Constructor */ - private TaskCardView(){ - } - - /* Initialisation Methods */ - /** - * Loads and initialise one cell of the task in the to-do list ListView. - * @param task to be displayed on the cell - * @param displayedIndex index to be displayed on the card itself to the user - * @return an instance of this class - */ - public static TaskCardView load(ImmutableTask task, int displayedIndex){ - TaskCardView taskListCard = new TaskCardView(); - taskListCard.task = task; - taskListCard.displayedIndex = displayedIndex; - taskCardMap.put(task, taskListCard); - return UiPartLoaderUtil.loadUiPart(taskListCard); - } - - /** - * Initialise all the view elements in a task card. - */ - @FXML - public void initialize() { - displayEverythingElse(); - displayTags(); - displayTimings(); - setStyle(); - setTimingAutoUpdate(); - initialiseCollapsibleView(); - } - -``` -###### \src\main\java\seedu\todo\ui\view\TodoListView.java -``` java - /** - * Default Constructor for {@link TodoListView} - */ - public TodoListView() { - super(); - } - - /** - * Loads and initialise the {@link TodoListView} to the placeHolder. - * - * @param primaryStage of the application - * @param placeHolder where the view element {@link #todoListView} should be placed - * @return an instance of this class - */ - public static TodoListView load(Stage primaryStage, AnchorPane placeHolder, - ObservableList todoList) { - - TodoListView todoListView = UiPartLoaderUtil - .loadUiPart(primaryStage, placeHolder, new TodoListView()); - todoListView.configure(todoList); - return todoListView; - } - - /** - * Configures the {@link TodoListView} - * - * @param todoList A list of {@link ImmutableTask} to be displayed on this {@link #todoListView}. - */ - private void configure(ObservableList todoList) { - setConnections(todoList); - } - - /** - * Links the list of {@link ImmutableTask} to the todoListView. - * - * @param todoList A list of {@link ImmutableTask} to be displayed on this {@link #todoListView}. - */ - private void setConnections(ObservableList todoList) { - todoListView.setItems(todoList); - todoListView.setCellFactory(param -> new TodoListViewCell()); - } - -``` -###### \src\main\java\seedu\todo\ui\view\TodoListView.java -``` java - /** - * Models a Task Card as a single ListCell of the ListView - */ - private class TodoListViewCell extends ListCell { - - /* Override Methods */ - @Override - protected void updateItem(ImmutableTask task, boolean empty) { - super.updateItem(task, empty); - - if (empty || task == null) { - setGraphic(null); - setText(null); - } else { - TaskCardView taskCardView = TaskCardView.load(task, FxViewUtil.convertToUiIndex(getIndex())); - setGraphic(taskCardView.getLayout()); - setTaskCardStyleProperties(taskCardView); - } - } - -``` -###### \src\test\java\guitests\guihandles\CommandInputViewHandle.java -``` java - /** - * Enters the given command in the Command Box and presses enter. - * @param command Command text to be executed. - */ - public void runCommand(String command) { - enterCommand(command); - pressEnter(); - guiRobot.sleep(GUI_SLEEP_DURATION); //Give time for the command to take effect - } - - /* Text View Helper Methods */ - /** - * Gets the text stored in a text area given the id to the text area - * - * @param textFieldId ID of the text area. - * @return Returns the text that is contained in the text area. - */ - private String getTextAreaText(String textFieldId) { - return ((TextArea) getNode(textFieldId)).getText(); - } - - /** - * Keys in the given {@code newText} to the specified text area given its ID. - * - * @param textFieldId ID for the text area. - * @param newText Text to be keyed in to the text area. - */ - private void setTextAreaText(String textFieldId, String newText) { - guiRobot.clickOn(textFieldId); - TextArea textArea = (TextArea)guiRobot.lookup(textFieldId).tryQuery().get(); - Platform.runLater(() -> textArea.setText(newText)); - guiRobot.sleep(GUI_SLEEP_DURATION); // so that the texts stays visible on the GUI for a short period - } -} -``` -###### \src\test\java\guitests\guihandles\TaskCardViewHandle.java -``` java - /** - * Search and returns exactly one matching node. - * - * @param fieldId Field ID to search inside the parent node. - * @return Returns one appropriate node that matches the {@code fieldId}. - * @throws NullPointerException when no node with {@code fieldId} can be found, intentionally breaking - * the tests. - */ - @Override - protected Node getNode(String fieldId) throws NullPointerException { - Optional node = guiRobot.from(rootNode).lookup(fieldId).tryQuery(); - if (node.isPresent()) { - return node.get(); - } else { - throw new NullPointerException("Node " + fieldId + " is not found."); - } - } - -``` -###### \src\test\java\guitests\guihandles\TaskCardViewHandle.java -``` java - /* Override Methods */ - @Override - public boolean equals(Object obj) { - if(obj instanceof TaskCardViewHandle) { - TaskCardViewHandle handle = (TaskCardViewHandle) obj; - - boolean hasEqualTitle = this.getDisplayedTitle() - .equals(handle.getDisplayedTitle()); - boolean hasEqualDescription = this.getDisplayedDescription() - .equals(handle.getDisplayedDescription()); - boolean hasEqualDateText = this.getDisplayedDateText() - .equals(handle.getDisplayedDateText()); - boolean hasEqualLocation = this.getDisplayedLocation() - .equals(handle.getDisplayedLocation()); - boolean hasEqualType = this.getDisplayedTypeLabel() - .equals(handle.getDisplayedTypeLabel()); - boolean hasEqualMoreInfoVisibility = this.getMoreInfoLabelVisibility() - == handle.getMoreInfoLabelVisibility(); - boolean hasEqualDescriptionBoxVisibility = this.getDescriptionBoxVisibility() - == handle.getDescriptionBoxVisibility(); - boolean hasEqualDateBoxVisibility = this.getDateBoxVisibility() - == handle.getDateBoxVisibility(); - boolean hasEqualLocationBoxVisibility = this.getLocationBoxVisibility() - == handle.getLocationBoxVisibility(); - boolean hasEqualPinImageVisibility = this.getPinImageVisibility() - == handle.getPinImageVisibility(); - boolean hasEqualSelectedStyleApplied = this.isSelectedStyleApplied() - == handle.isSelectedStyleApplied(); - boolean hasEqualCompletedStyleApplied = this.isCompletedStyleApplied() - == handle.isCompletedStyleApplied(); - boolean hasEqualOverdueStyleApplied = this.isOverdueStyleApplied() - == handle.isOverdueStyleApplied(); - - return hasEqualTitle && hasEqualDescription && hasEqualDateText - && hasEqualLocation && hasEqualType && hasEqualMoreInfoVisibility - && hasEqualDescriptionBoxVisibility && hasEqualDateBoxVisibility - && hasEqualLocationBoxVisibility && hasEqualPinImageVisibility - && hasEqualSelectedStyleApplied && hasEqualCompletedStyleApplied - && hasEqualOverdueStyleApplied; - } - return super.equals(obj); - } - - @Override - public String toString() { - return getDisplayedTitle() + " " + getDisplayedDescription(); - } -} -``` -###### \src\test\java\guitests\guihandles\TodoListViewHandle.java -``` java - /** - * Constructs a handle for {@link TodoListView}. - * - * @param guiRobot The GUI test robot. - * @param primaryStage The main stage that is executed from the application's UI. - */ - public TodoListViewHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - /* View Element Helper Methods */ - /** - * Gets an instance of {@link ListView} of {@link TodoListView} - */ - public ListView getTodoListView() { - return (ListView) getNode(TODO_LIST_VIEW_ID); - } - -``` diff --git a/collated/A0135805Hunused.md b/collated/A0135805Hunused.md deleted file mode 100644 index 13ff443ca975..000000000000 --- a/collated/A0135805Hunused.md +++ /dev/null @@ -1,15 +0,0 @@ -# A0135805Hunused -###### \src\main\java\seedu\todo\model\tag\Tag.java -``` java - //Feature to be implemented in V0.5 - /** - * Renames the tag with a {@code newName}. - * - * This class is intentional to be package private, so only {@link UniqueTagCollection} - * can rename tags. - */ - void rename(String newName) { - this.tagName = newName; - } - -``` diff --git a/collated/A0135817B.md b/collated/A0135817B.md deleted file mode 100644 index 639a06cf37e6..000000000000 --- a/collated/A0135817B.md +++ /dev/null @@ -1,3798 +0,0 @@ -# A0135817B -###### \build\resources\main\style\DefaultStyle.css -``` css -.searchStatus { - -fx-padding: 4px; -} - -.searchStatus Text { - -fx-font-size: 14px; - -fx-fill: #fff; -} - -.searchStatus .searchLabel { -} - -.searchStatus .searchTerm { - -fx-font-weight: bold; -} - -.searchStatus .searchCount { - -fx-text-alignment: right; -} - -``` -###### \classes\production\main\style\DefaultStyle.css -``` css -.searchStatus { - -fx-padding: 4px; -} - -.searchStatus Text { - -fx-font-size: 14px; - -fx-fill: #fff; -} - -.searchStatus .searchLabel { -} - -.searchStatus .searchTerm { - -fx-font-weight: bold; -} - -.searchStatus .searchCount { - -fx-text-alignment: right; -} - - - -``` -###### \src\main\java\seedu\todo\commons\util\StringUtil.java -``` java - /** - * Returns true if the string is null, of length zero, or contains only whitespace - */ - public static boolean isEmpty(String s) { - return s == null || s.length() == 0 || CharMatcher.whitespace().matchesAllOf(s); - } - -``` -###### \src\main\java\seedu\todo\commons\util\TimeUtil.java -``` java - /** - * Translates input string from International date format (DD/MM/YYYY) to American - * date format (MM/DD/YYYY), because Natty only recognizes the later - */ - public static String toAmericanDateFormat(String input) { - return DATE_REGEX.matcher(input).replaceAll("$3$2$1"); - } - -``` -###### \src\main\java\seedu\todo\logic\arguments\Argument.java -``` java -abstract public class Argument implements Parameter { - private static final String REQUIRED_ERROR_FORMAT = "The %s parameter is required"; - private static final String TYPE_ERROR_FORMAT = "The %s should be a %s. You gave '%s'."; - - protected static final String OPTIONAL_ARGUMENT_FORMAT = "[%s]"; - protected static final String FLAG_ARGUMENT_FORMAT = "%s%s %s"; - - private String name; - private String description; - private String flag; - private boolean optional = true; - private boolean boundValue = false; - - protected T value; - - private String requiredErrorMessage; - - private static final Logger logger = LogsCenter.getLogger(Argument.class); - - public Argument(String name) { - this.name = name; - } - - public Argument(String name, T defaultValue) { - this.name = name; - this.value = defaultValue; - } - - /** - * Binds a value to this parameter. Implementing classes MUST override AND - * call the parent class function so that the dirty bit is set for required - * parameter validation to work - */ - @Override - public void setValue(String input) throws IllegalValueException { - boundValue = true; - } - - public T getValue() { - return value; - } - - @Override - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Argument description(String description) { - this.description = description; - return this; - } - - public String getFlag() { - return flag; - } - - public Argument flag(String flag) { - this.flag = flag.trim().toLowerCase(); - - if (!this.flag.equals(flag)) { - logger.warning("Flag argument has uppercase or whitespace characters. These have been ignored."); - } - - return this; - } - - public boolean isOptional() { - return optional; - } - - public boolean hasBoundValue() { - return boundValue; - } - - /** - * Sets the field as required - */ - public Argument required() { - this.optional = false; - return this; - } - - /** - * Sets the field as required and specify an error message to show if it is not provided - * @param errorMessage shown to the user when the parameter is not provided - */ - public Argument required(String errorMessage) { - requiredErrorMessage = errorMessage; - this.optional = false; - return this; - } - - @Override - public boolean isPositional() { - return flag == null; - } - - @Override - public void checkRequired() throws IllegalValueException { - if (!isOptional() && !hasBoundValue()) { - String error = requiredErrorMessage == null ? - String.format(Argument.REQUIRED_ERROR_FORMAT, name) : requiredErrorMessage; - throw new IllegalValueException(error); - } - } - - /** - * Throws an IllegalValueException for a type mismatch between user input and what - * the argument expect - * @param field name of the argument - * @param expected the expected type for the argument - * @param actual what the user actually gave - */ - protected void typeError(String field, String expected, String actual) throws IllegalValueException { - throw new IllegalValueException(String.format(Argument.TYPE_ERROR_FORMAT, field, expected, actual)); - } - - @Override - public String toString() { - return toString(name); - } - - public String toString(String name) { - if (!isPositional()) { - name = String.format(FLAG_ARGUMENT_FORMAT, TodoParser.FLAG_TOKEN, flag, name); - } - - if (isOptional()) { - name = String.format(OPTIONAL_ARGUMENT_FORMAT, name); - } - - return name; - } -} -``` -###### \src\main\java\seedu\todo\logic\arguments\DateRange.java -``` java -/** - * Utility container class for the output from DateRangeArgument - */ -public class DateRange { - private final LocalDateTime endTime; - private LocalDateTime startTime; - - public DateRange(LocalDateTime endTime) { - this.endTime = endTime; - } - - public DateRange(LocalDateTime startTime, LocalDateTime endTime) { - this(endTime); - this.startTime = startTime; - } - - public LocalDateTime getEndTime() { - return endTime; - } - - public LocalDateTime getStartTime() { - return startTime; - } - - public boolean isRange() { - return startTime != null; - } -} -``` -###### \src\main\java\seedu\todo\logic\arguments\DateRangeArgument.java -``` java -public class DateRangeArgument extends Argument { - private static final PrettyTimeParser parser = new PrettyTimeParser(); - private static final TimeUtil timeUtil = new TimeUtil(); - - // TODO: Review all error messages to check for user friendliness - private static final String TOO_MANY_DATES_FORMAT = "You specified too many time - we found: %s"; - private static final String NO_DATE_FOUND_FORMAT = "%s does not seem to contain a date"; - - public DateRangeArgument(String name) { - // Makes sure that there is a default value, so that callers won't get null when they getValue() - super(name, new DateRange(null)); - } - - public DateRangeArgument(String name, DateRange defaultValue) { - super(name, defaultValue); - } - - @Override - public void setValue(String input) throws IllegalValueException { - super.setValue(input); - - if (StringUtil.isEmpty(input)) { - return; - } - - input = TimeUtil.toAmericanDateFormat(input); - List dateGroups = parser.parse(input); - - List dates = dateGroups.stream() - .map(TimeUtil::asLocalDateTime) - .sorted() - .collect(Collectors.toList()); - - if (dates.size() > 2) { - tooManyDatesError(dates); - } else if (dates.size() == 0) { - throw new IllegalValueException(String.format(DateRangeArgument.NO_DATE_FOUND_FORMAT, input)); - } - - if (dates.size() == 1) { - value = new DateRange(dates.get(0)); - } else { - value = new DateRange(dates.get(0), dates.get(1)); - } - } - - private void tooManyDatesError(List dates) throws IllegalValueException { - StringJoiner sj = new StringJoiner(", "); - for (LocalDateTime d : dates) { - sj.add(timeUtil.getTaskDeadlineText(d)); - } - String message = String.format(DateRangeArgument.TOO_MANY_DATES_FORMAT, sj.toString()); - throw new IllegalValueException(message); - } - -} -``` -###### \src\main\java\seedu\todo\logic\arguments\FlagArgument.java -``` java -public class FlagArgument extends Argument { - - public FlagArgument(String name) { - super(name); - flag(name.substring(0, 1).toLowerCase()); - this.value = false; - } - - public FlagArgument(String name, boolean defaultValue) { - super(name, defaultValue); - flag(name.substring(0, 1).toLowerCase()); - } - - @Override - public void setValue(String input) throws IllegalValueException { - this.value = true; - super.setValue(input); - } - - @Override - public String toString(String name) { - String flag = TodoParser.FLAG_TOKEN + getFlag(); - return isOptional() ? String.format(Argument.OPTIONAL_ARGUMENT_FORMAT, flag) : flag; - } -} -``` -###### \src\main\java\seedu\todo\logic\arguments\IntArgument.java -``` java -public class IntArgument extends Argument { - - public IntArgument(String name) { - super(name); - } - - public IntArgument(String name, int defaultValue) { - super(name, defaultValue); - } - - @Override - public void setValue(String input) throws IllegalValueException { - try { - value = Integer.parseInt(input); - super.setValue(input); - } catch (NumberFormatException e) { - typeError(this.getName(), "integer", input); - } - } - -} -``` -###### \src\main\java\seedu\todo\logic\arguments\Parameter.java -``` java -/** - * Represents a single command parameter that the parser will try to feed the user - * input into. The Parameter interface is needed because the Argument base class is - * typed, so this interface contains all of the non-typed methods that are common to - * all argument subclasses - */ -public interface Parameter { - void setValue(String input) throws IllegalValueException; - - boolean isPositional(); - - boolean hasBoundValue(); - - boolean isOptional(); - - String getFlag(); - - String getName(); - - String getDescription(); - - void checkRequired() throws IllegalValueException; -} -``` -###### \src\main\java\seedu\todo\logic\arguments\StringArgument.java -``` java -public class StringArgument extends Argument { - - public StringArgument(String name) { - super(name); - } - - public StringArgument(String name, String defaultValue) { - super(name, defaultValue); - } - - @Override - public void setValue(String input) throws IllegalValueException { - input = input.trim(); - - // Ignore empty strings - if (input.length() > 0) { - this.value = input; - } - - super.setValue(input); - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\AddCommand.java -``` java -public class AddCommand extends BaseCommand { - private static final String VERB = "added"; - - private Argument title = new StringArgument("title").required(); - - private Argument description = new StringArgument("description") - .flag("m"); - - private Argument pin = new FlagArgument("pin") - .flag("p"); - - private Argument location = new StringArgument("location") - .flag("l"); - - private Argument date = new DateRangeArgument("deadline") - .flag("d"); - - @Override - public Parameter[] getArguments() { - return new Parameter[] { - title, date, description, location, pin, - }; - } - - @Override - public String getCommandName() { - return "add"; - } - - @Override - public List getCommandSummary() { - String eventArguments = Joiner.on(" ").join(title, "/d start and end time", description, location, pin); - - return ImmutableList.of( - new CommandSummary("Add task", getCommandName(), getArgumentSummary()), - new CommandSummary("Add event", getCommandName(), eventArguments)); - } - - @Override - public CommandResult execute() throws ValidationException { - ImmutableTask addedTask = this.model.add(title.getValue(), task -> { - task.setDescription(description.getValue()); - task.setPinned(pin.getValue()); - task.setLocation(location.getValue()); - task.setStartTime(date.getValue().getStartTime()); - task.setEndTime(date.getValue().getEndTime()); - }); - if(!model.getObservableList().contains(addedTask)) { - model.view(TaskViewFilter.DEFAULT); - } - eventBus.post(new HighlightTaskEvent(addedTask)); - eventBus.post(new ExpandCollapseTaskEvent(addedTask)); - return taskSuccessfulResult(title.getValue(), AddCommand.VERB); - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\BaseCommand.java -``` java -/** - * The base class for commands. All commands need to implement an execute function - * and a getArguments function that collects the command arguments for the use of - * the help command. - * - * To perform additional validation on incoming arguments, override the validateArguments - * function. - */ -public abstract class BaseCommand { - /** - * The default message that accompanies argument errors - */ - private static final String DEFAULT_ARGUMENT_ERROR_MESSAGE = ""; - - private static final String TASK_MODIFIED_SUCCESS_MESSAGE = "'%s' successfully %s!"; - - protected static final EventsCenter eventBus = EventsCenter.getInstance(); - - protected Model model; - - protected ErrorBag errors = new ErrorBag(); - - abstract protected Parameter[] getArguments(); - - /** - * Return the name of the command, which is used to call it - */ - abstract public String getCommandName(); - - /** - * Returns a list of command summaries for the command. This function returns a - * list because commands may (rarely) be responsible for more than one thing, - * like the add command. - */ - abstract public List getCommandSummary(); - - abstract public CommandResult execute() throws ValidationException; - - /** - * Binds the data model to the command object - */ - public void setModel(Model model) { - this.model = model; - } - - /** - * Binds the both positional and named command arguments from the parse results - * to the command object itself - * - * @throws ValidationException if the arguments are invalid - */ - public void setArguments(ParseResult arguments) throws ValidationException { - if (arguments.getPositionalArgument().isPresent()) { - setPositionalArgument(arguments.getPositionalArgument().get()); - } - - for (Entry e : arguments.getNamedArguments().entrySet()) { - setNameArgument(e.getKey(), e.getValue()); - } - - checkRequiredArguments(); - validateArguments(); - - errors.validate(getArgumentErrorMessage()); - } - - /** - * Hook allowing subclasses to implement their own validation logic for arguments - * Subclasses should add additional errors to the errors ErrorBag - */ - protected void validateArguments() { - // Does no additional validation by default - } - - protected void setPositionalArgument(String argument) { - for (Parameter p : getArguments()) { - if (p.isPositional()) { - try { - p.setValue(argument); - } catch (IllegalValueException e) { - errors.put(e.getMessage()); - } - } - } - } - - protected void setNameArgument(String flag, String argument) { - for (Parameter p : getArguments()) { - if (flag.equals(p.getFlag())) { - try { - p.setValue(argument); - } catch (IllegalValueException e) { - errors.put(p.getName(), e.getMessage()); - } - - return; - } - } - - // TODO: Do something for unrecognized argument? - } - - private void checkRequiredArguments() { - for (Parameter p : getArguments()) { - try { - p.checkRequired(); - } catch (IllegalValueException e) { - errors.put(p.getName(), e.getMessage()); - } - } - } - - /** - * Override this function if the command should return some other error - * message on argument validation error - */ - protected String getArgumentErrorMessage() { - return BaseCommand.DEFAULT_ARGUMENT_ERROR_MESSAGE; - } - - /** - * Returns a generic CommandResult with a "{task} successfully {verbed}" success message. - * - * @param title the title of the task that was verbed on - * @param verb the action that was performed on the task, in past tense - */ - protected CommandResult taskSuccessfulResult(String title, String verb) { - return new CommandResult(String.format(BaseCommand.TASK_MODIFIED_SUCCESS_MESSAGE, title, verb)); - } - - /** - * Turns the arguments into a string summary using their toString function - */ - protected String getArgumentSummary() { - StringJoiner sj = new StringJoiner(" "); - for (Parameter p : getArguments()) { - sj.add(p.toString()); - } - return sj.toString(); - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\CommandMap.java -``` java -public class CommandMap { - // List of command classes. Remember to register new commands here so that the - // dispatcher can recognize them - public static List> commandClasses = ImmutableList.of( - AddCommand.class, - CompleteCommand.class, - DeleteCommand.class, - EditCommand.class, - ExitCommand.class, - HelpCommand.class, - PinCommand.class, - UndoCommand.class, - RedoCommand.class, - SaveCommand.class, - LoadCommand.class, - ShowCommand.class, - FindCommand.class, - ViewCommand.class, - TagCommand.class - ); - - private static Map> commandMap; - - private static void buildCommandMap() { - commandMap = new LinkedHashMap<>(); - - for (Class command : CommandMap.commandClasses) { - String commandName = getCommand(command).getCommandName().toLowerCase(); - commandMap.put(commandName, command); - } - } - - public static Map> getCommandMap() { - if (commandMap == null) { - buildCommandMap(); - } - - return commandMap; - } - - public static BaseCommand getCommand(String key) { - return getCommand(getCommandMap().get(key)); - } - - public static BaseCommand getCommand(Class command) { - try { - return command.newInstance(); - } catch (InstantiationException|IllegalAccessException e) { - e.printStackTrace(); - return null; // This shouldn't happen - } - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\CommandResult.java -``` java -/** - * Represents the result of a command execution. - */ -public class CommandResult { - private final String feedback; - private final ErrorBag errors; - - public CommandResult() { - this.feedback = ""; - this.errors = null; - } - - public CommandResult(String feedback) { - this.feedback = feedback; - this.errors = null; - } - - public CommandResult(String feedback, ErrorBag errors) { - this.feedback = feedback; - this.errors = errors; - } - - public String getFeedback() { - return feedback; - } - - public ErrorBag getErrors() { - return errors; - } - - public boolean isSuccessful() { - return errors == null; - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\CommandSummary.java -``` java -public class CommandSummary { - /** - * The scenario the summary is aiming to describe, eg. add event, delete task, etc. - * Keep it short but descriptive. - */ - public final String scenario; - - /** - * The command to accomplish the scenario, eg. add, delete - */ - public final String command; - - /** - * The parameters for the command - */ - public final String arguments; - - public CommandSummary(String scenario, String command) { - this(scenario, command, ""); - } - - public CommandSummary(String scenario, String command, String arguments) { - this.scenario = scenario.trim(); - this.command = command.toLowerCase().trim(); - this.arguments = arguments.trim(); - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\DeleteCommand.java -``` java -public class DeleteCommand extends BaseCommand { - private static final String VERB = "deleted"; - - private Argument index = new IntArgument("index").required(); - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ index }; - } - - @Override - public String getCommandName() { - return "delete"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Delete task", getCommandName(), - getArgumentSummary())); - } - - @Override - public CommandResult execute() throws ValidationException { - ImmutableTask deletedTask = this.model.delete(index.getValue()); - return taskSuccessfulResult(deletedTask.getTitle(), DeleteCommand.VERB); - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\ExitCommand.java -``` java -/** - * Terminates the program. - */ -public class ExitCommand extends BaseCommand { - private final static String EXIT_MESSAGE = "Goodbye!"; - - @Override - public CommandResult execute() throws ValidationException { - EventsCenter.getInstance().post(new ExitAppRequestEvent()); - return new CommandResult(ExitCommand.EXIT_MESSAGE); - } - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{}; - } - - @Override - public String getCommandName() { - return "exit"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Close this app :(", getCommandName())); - } - -} -``` -###### \src\main\java\seedu\todo\logic\commands\HelpCommand.java -``` java -/** - * Shows the help panel - */ -public class HelpCommand extends BaseCommand { - private final static String HELP_MESSAGE = "Showing help..."; - - private static List commandSummaries; - - @Override - public CommandResult execute() throws ValidationException { - if (commandSummaries == null) { - commandSummaries = collectCommandSummaries(); - } - - EventsCenter.getInstance().post(new ShowHelpEvent(commandSummaries)); - return new CommandResult(HelpCommand.HELP_MESSAGE); - } - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{}; - } - - @Override - public String getCommandName() { - return "help"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Show help", getCommandName())); - } - - private List collectCommandSummaries() { - List summaries = new ArrayList<>(); - for (String key : CommandMap.getCommandMap().keySet()) { - summaries.addAll(CommandMap.getCommand(key).getCommandSummary()); - } - return summaries; - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\LoadCommand.java -``` java -public class LoadCommand extends BaseCommand { - private Argument location = new StringArgument("location").required(); - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ location }; - } - - @Override - public String getCommandName() { - return "load"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Load todo list from file", getCommandName(), - getArgumentSummary())); - } - - @Override - public CommandResult execute() throws ValidationException { - String path = location.getValue(); - model.load(path); - return new CommandResult(String.format("File loaded from %s", path)); - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\RedoCommand.java -``` java -public class RedoCommand extends BaseCommand { - @Override - protected Parameter[] getArguments() { - return new Parameter[0]; - } - - @Override - public String getCommandName() { - return "redo"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Redo", getCommandName())); - } - - @Override - public CommandResult execute() throws ValidationException { - model.redo(); - return new CommandResult("Redid last action"); - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\SaveCommand.java -``` java -/** - * Saves the save file to a different location - */ -public class SaveCommand extends BaseCommand { - private Argument location = new StringArgument("location"); - - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ location }; - } - - @Override - public String getCommandName() { - return "save"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of( - new CommandSummary("Save to a different file", getCommandName(), "location"), - new CommandSummary("Find my todo list file", getCommandName())); - } - - @Override - public CommandResult execute() throws ValidationException { - if (location.hasBoundValue()) { - model.save(location.getValue()); - - return new CommandResult(String.format("Todo list saved successfully to %s", location)); - } else { - return new CommandResult(String.format("Save location: %s", model.getStorageLocation())); - } - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\UndoCommand.java -``` java -public class UndoCommand extends BaseCommand { - @Override - protected Parameter[] getArguments() { - return new Parameter[0]; - } - - @Override - public String getCommandName() { - return "undo"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(new CommandSummary("Undo last edit", getCommandName())); - } - - @Override - public CommandResult execute() throws ValidationException { - model.undo(); - return new CommandResult("Undid last action"); - } -} -``` -###### \src\main\java\seedu\todo\logic\Dispatcher.java -``` java -public interface Dispatcher { - BaseCommand dispatch(String command) throws IllegalValueException; -} -``` -###### \src\main\java\seedu\todo\logic\Logic.java -``` java -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param input The command as entered by the user. - */ - CommandResult execute(String input); - -``` -###### \src\main\java\seedu\todo\logic\parser\Parser.java -``` java -public interface Parser { - ParseResult parse(String input); -} -``` -###### \src\main\java\seedu\todo\logic\parser\ParseResult.java -``` java -public interface ParseResult { - - String getCommand(); - - Optional getPositionalArgument(); - - Map getNamedArguments(); - -} -``` -###### \src\main\java\seedu\todo\logic\parser\TodoParser.java -``` java -/** - * Parses a input string into the command, positional argument and named arguments. - * The format looks like: - * - *
command positional [-f or --flag named]...
- * - * There will only be one command, optionally one positional argument, and - * optionally many named arguments. - */ -public class TodoParser implements Parser { - public static String FLAG_TOKEN = "/"; - - private List tokenize(String input) { - input = input.trim(); - - return Lists.newArrayList(Splitter - .on(" ") - .trimResults() - .omitEmptyStrings() - .split(input)); - } - - private String parseFlag(String token) { - return token.substring(1, token.length()); - } - - private boolean isFlag(String token) { - return token.startsWith(TodoParser.FLAG_TOKEN) && token.length() > 1; - } - - @Override - public ParseResult parse(String input) { - List tokens = tokenize(input); - Map named = new HashMap<>(); - - // Pull out the command, exit if there's no more things to parse - String command = tokens.remove(0).toLowerCase(); - - // Parse out positional argument - StringJoiner sj = new StringJoiner(" "); - while (!tokens.isEmpty() && !isFlag(tokens.get(0))) { - sj.add(tokens.remove(0)); - } - String positional = sj.toString(); - - // If there are no more tokens return immediately - if (tokens.isEmpty()) { - return new TodoResult(command, positional, named); - } - - // Parse out named arguments - String flag = tokens.remove(0); - sj = new StringJoiner(" "); - while (!tokens.isEmpty()) { - if (isFlag(tokens.get(0))) { - named.put(parseFlag(flag), sj.toString()); - flag = tokens.remove(0); - sj = new StringJoiner(" "); - } else { - sj.add(tokens.remove(0)); - } - } - named.put(parseFlag(flag), sj.toString()); - - return new TodoResult(command, positional, named); - } - - private class TodoResult implements ParseResult { - private final String command; - private final String positional; - private final Map named; - - public TodoResult(String command, String positional, Map named) { - this.command = command; - this.positional = positional.length() > 0 ? positional : null; - this.named = named; - } - - @Override - public String getCommand() { - return command; - } - - @Override - public Optional getPositionalArgument() { - return Optional.ofNullable(positional); - } - - @Override - public Map getNamedArguments() { - return named; - } - - @Override - public String toString() { - StringJoiner sj = new StringJoiner(" "); - sj.add(command).add(getPositionalArgument().orElse("")); - for (Entry e : getNamedArguments().entrySet()) { - sj.add(TodoParser.FLAG_TOKEN + e.getKey()).add(e.getValue()); - } - return sj.toString(); - } - } -} -``` -###### \src\main\java\seedu\todo\logic\TodoDispatcher.java -``` java -/** - * Selects the correct command based on the parser results - */ -public class TodoDispatcher implements Dispatcher { - private final static String COMMAND_NOT_FOUND_FORMAT = "'%s' doesn't look like any command we know."; - private final static String AMBIGUOUS_COMMAND_FORMAT = "Do you mean %s?"; - - public BaseCommand dispatch(String input) throws IllegalValueException { - // Implements character by character matching of input to the list of command names - // Since this eliminates non-matches at every character, it is fast even though - // it is theoretically O(n^2) in the worst case. - Set commands = new HashSet<>(CommandMap.getCommandMap().keySet()); - - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - Iterator s = commands.iterator(); - while (s.hasNext()) { - String commandName = s.next(); - if (input.length() > commandName.length() || commandName.charAt(i) != c) { - s.remove(); - } - } - - // Return immediately when there's one match left. This allow the user to - // type as little as possible, and is also good for autocomplete if that's - // on the radar - if (commands.size() == 1) { - String key = commands.iterator().next(); - return CommandMap.getCommand(key); - } else if (commands.isEmpty()) { - throw new IllegalValueException(String.format(TodoDispatcher.COMMAND_NOT_FOUND_FORMAT, input)); - } - } - - String ambiguousCommands = Joiner.on(" or ").join(commands); - throw new IllegalValueException(String.format(TodoDispatcher.AMBIGUOUS_COMMAND_FORMAT, ambiguousCommands)); - } -} -``` -###### \src\main\java\seedu\todo\logic\TodoLogic.java -``` java -/** - * Central controller for the application, abstracting application logic from the UI - */ -public class TodoLogic implements Logic { - private final Parser parser; - private final Model model; - private final Dispatcher dispatcher; - - private static final Logger logger = LogsCenter.getLogger(TodoLogic.class); - - public TodoLogic(Parser parser, Model model, Dispatcher dispatcher) { - assert parser != null; - assert model != null; - assert dispatcher != null; - - this.parser = parser; - this.model = model; - this.dispatcher = dispatcher; - } - - public CommandResult execute(String input) { - // Sanity check - input should not be empty - if (StringUtil.isEmpty(input)) { - return new CommandResult(); - } - - ParseResult parseResult = parser.parse(input); - BaseCommand command; - logger.fine("Parsed command: " + parseResult.toString()); - - try { - command = dispatcher.dispatch(parseResult.getCommand()); - } catch (IllegalValueException e) { - return new CommandResult(e.getMessage(), new ErrorBag()); - } - - try { - command.setArguments(parseResult); - command.setModel(model); - return command.execute(); - } catch (ValidationException e) { - logger.info(e.getMessage()); - return new CommandResult(e.getMessage(), e.getErrors()); - } - } - -``` -###### \src\main\java\seedu\todo\model\ErrorBag.java -``` java -public class ErrorBag { - private List nonFieldErrors = new ArrayList<>(); - private Map fieldErrors = new HashMap<>(); - - /** - * Add an error that is not related to any specific field - * - * @param nonFieldError the error message - */ - public void put(String nonFieldError) { - nonFieldErrors.add(nonFieldError); - } - - /** - * Add an error that is related to a specific field - * - * @param field the error is for - * @param error the error message - */ - public void put(String field, String error) { - fieldErrors.put(field, error); - } - - public List getNonFieldErrors() { - return nonFieldErrors; - } - - public Map getFieldErrors() { - return fieldErrors; - } - - public int size() { - return nonFieldErrors.size() + fieldErrors.size(); - } - - /** - * Throws a validation exception if the bag contains errors - * - * @param message a short message about why the validation failed - * @throws ValidationException if the ErrorBag is not empty - */ - public void validate(String message) throws ValidationException { - if (size() > 0) { - throw new ValidationException(message, this); - } - } -} -``` -###### \src\main\java\seedu\todo\model\ImmutableTodoList.java -``` java -public interface ImmutableTodoList { - /** - * Get an immutable list of tasks - */ - List getTasks(); -} -``` -###### \src\main\java\seedu\todo\model\Model.java -``` java -public interface Model { - /** - * Adds a new task or event with title only to the todo list. - * - * @param title the title of the task - * @return the task that was just created - * @throws IllegalValueException if the values set in the update predicate is invalid - */ - ImmutableTask add(String title) throws IllegalValueException; - - /** - * Adds a new task or event with title and other fields to the todo list. - * - * @param title the title of the task - * @param update a {@link MutableTask} is passed into this lambda. All other fields - * should be set from inside this lambda. - * @return the task that was just created - * @throws ValidationException if the fields in the task to be updated are not valid - */ - ImmutableTask add(String title, Consumer update) throws ValidationException; - - /** - * Deletes the given task from the todo list. This change is also propagated to the - * underlying persistence layer. - * - * @param index the 1-indexed position of the task that needs to be deleted - * @return the task that was just deleted - * @throws ValidationException if the task does not exist - */ - ImmutableTask delete(int index) throws ValidationException; - - /** - * Replaces certain fields in the task. Mutation of the {@link Task} object should - * only be done in the update lambda. The lambda takes in one parameter, - * a {@link MutableTask}, and does not expect any return value. For example: - * - *
todo.update(task, t -> {
-     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline back by 2h
-     *     t.setPin(true); // Pin this task
-     * });
- * - * @return the task that was just updated - * - * @throws ValidationException if the task does not exist or if the fields in the - * task to be updated are not valid - */ - ImmutableTask update(int index, Consumer update) throws ValidationException; - - /** - * Carries out the specified update in the fields of all visible tasks. Mutation of all {@link Task} - * objects should only be done in the update lambda. The lambda takes in a single parameter, - * a {@link MutableTask}, and does not expect any return value, as per the {@link update} command. Since - * this represents the observable layer, the changes required to be done to the underlying layer TodoList - * is set via getting a list of their indices in the underlying layer using a UUID map. - * - *
todo.updateAll (t -> {
-     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline of all Observable tasks back by 2h
-     *     t.setPin(true); // Pin all tasks in Observable view
-     * });
- * - * @throws ValidationException if any updates on any of the task objects are considered invalid - */ - void updateAll(Consumer update) throws ValidationException; - - /** - * Sets the model to the provided TaskViewFilter object. TaskViewFilters represents the - * filter and sorting needed by each intelligent view - */ - void view(TaskViewFilter view); - - /** - * Filters the list of tasks by this predicate. This is filtering is ran - * after the view predicate. No information about the search is shown to the user. - * Setting predicate to null will reset the search. - */ - void find(Predicate predicate); - - /** - * Filters the list of tasks by this predicate. This is filtering is ran - * after the view predicate. A list of search terms is also shown to the user. - */ - void find(Predicate predicate, List terms); - - /** - * Undoes the last operation that modifies the todolist - * @throws ValidationException if there are no more changes to undo - */ - void undo() throws ValidationException; - - /** - * Redoes the last operation that was undone - * @throws ValidationException if there are no more changes to redo - */ - void redo() throws ValidationException; - - /** - * Changes the save path of the TodoList storage - * @throws ValidationException if the path is not valid - */ - void save(String location) throws ValidationException; - - /** - * Loads a TodoList from the path. - * @throws ValidationException if the path or file is invalid - */ - void load(String location) throws ValidationException; - - /** - * Obtains the current storage methods - */ - String getStorageLocation(); - - /** - * Get an observable list of tasks. Used mainly by the JavaFX UI. - */ - UnmodifiableObservableList getObservableList(); - - /** - * Get the current view filter used on the model. Used mainly by the JavaFx UI. - */ - ObjectProperty getViewFilter(); - - /** - * Get the current status of the search used on the model. - */ - ObjectProperty getSearchStatus(); - -``` -###### \src\main\java\seedu\todo\model\task\BaseTask.java -``` java -public abstract class BaseTask implements ImmutableTask { - protected UUID uuid = UUID.randomUUID(); - - @Override - public UUID getUUID() { - return uuid; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof ImmutableTask)) { - return false; - } - - return getUUID().equals(((ImmutableTask) o).getUUID()); - } - - @Override - public int hashCode() { - return getUUID().hashCode(); - } - - @Override - public String toString() { - return getTitle(); - } -} -``` -###### \src\main\java\seedu\todo\model\task\Task.java -``` java -/** - * Represents a single task - */ -public class Task extends BaseTask implements MutableTask { - private StringProperty title = new SimpleStringProperty(); - private StringProperty description = new SimpleStringProperty(); - private StringProperty location = new SimpleStringProperty(); - - private BooleanProperty pinned = new SimpleBooleanProperty(); - private BooleanProperty completed = new SimpleBooleanProperty(); - - private ObjectProperty startTime = new SimpleObjectProperty<>(); - private ObjectProperty endTime = new SimpleObjectProperty<>(); - - private ObjectProperty> tags = new SimpleObjectProperty<>(new HashSet()); - private LocalDateTime createdAt = LocalDateTime.now(); - - /** - * Creates a new task - */ - public Task(String title) { - this.setTitle(title); - } - - /** - * Constructs a Task from a ReadOnlyTask - */ - public Task(ImmutableTask task) { - this.setTitle(task.getTitle()); - this.setDescription(task.getDescription().orElse(null)); - this.setLocation(task.getLocation().orElse(null)); - this.setStartTime(task.getStartTime().orElse(null)); - this.setEndTime(task.getEndTime().orElse(null)); - this.setCompleted(task.isCompleted()); - this.setPinned(task.isPinned()); - this.setCreatedAt(task.getCreatedAt()); - this.uuid = task.getUUID(); - this.setTags(task.getTags()); - } - - @Override - public String getTitle() { - return title.get(); - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description.get()); - } - - @Override - public Optional getLocation() { - return Optional.ofNullable(location.get()); - } - - @Override - public Optional getStartTime() { - return Optional.ofNullable(startTime.get()); - } - - @Override - public Optional getEndTime() { - return Optional.ofNullable(endTime.get()); - } - - @Override - public boolean isPinned() { - return pinned.get(); - } - - @Override - public boolean isCompleted() { - return completed.get(); - } - - @Override - public Set getTags() { - return Collections.unmodifiableSet(tags.get()); - } - - @Override - public LocalDateTime getCreatedAt() { return createdAt; } - - @Override - public void setTitle(String title) { - this.title.set(title); - } - - @Override - public void setPinned(boolean pinned) { - this.pinned.set(pinned); - } - - @Override - public void setCompleted(boolean completed) { - this.completed.set(completed); - } - - @Override - public void setDescription(String description) { - this.description.set(description); - } - - @Override - public void setLocation(String location) { - this.location.set(location); - } - - @Override - public void setStartTime(LocalDateTime startTime) { - this.startTime.set(startTime); - } - - @Override - public void setEndTime(LocalDateTime endTime) { - this.endTime.set(endTime); - } - - @Override - public void setTags(Set tags) { - this.tags.set(tags); - } - - public void setCreatedAt(LocalDateTime createdAt) { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - - this.createdAt = createdAt; - } - - public Observable[] getObservableProperties() { - return new Observable[] { - title, description, location, startTime, endTime, tags, completed, pinned, - }; - } -} -``` -###### \src\main\java\seedu\todo\model\TodoList.java -``` java -/** - * Represents the todolist inside memory. While Model works as the external - * interface for handling data and application state, this class is internal - * to Model and represents only CRUD operations to the todolist. - */ -public class TodoList implements TodoListModel { - private static final String INCORRECT_FILE_FORMAT_FORMAT = "%s doesn't seem to be in the correct format."; - private static final String FILE_NOT_FOUND_FORMAT = "%s does not seem to exist."; - private static final String FILE_SAVE_ERROR_FORMAT = "Couldn't save file: %s"; - - private ObservableList tasks = FXCollections.observableArrayList(Task::getObservableProperties); - - private MovableStorage storage; - - private static final Logger logger = LogsCenter.getLogger(TodoList.class); - private static final EventsCenter events = EventsCenter.getInstance(); - - public TodoList(MovableStorage storage) { - this.storage = storage; - - try { - setTasks(storage.read().getTasks(), false); - } catch (FileNotFoundException | DataConversionException e) { - logger.info("Data file not found. Will be starting with an empty TodoList"); - } - - // Update event status - new Timer().scheduleAtFixedRate(new UpdateEventTask(), 0, 60 * 1000); - } - - private void updateEventStatus() { - LocalDateTime now = LocalDateTime.now(); - boolean todoListModified = false; - - for (Task task : tasks) { - boolean isIncompleteEvent = !task.isCompleted() && task.isEvent(); - if (isIncompleteEvent && now.isAfter(task.getEndTime().get())) { - task.setCompleted(true); - todoListModified = true; - } - } - if (todoListModified) { - saveTodoList(); - } - } - - private void raiseStorageEvent(String message, Exception e) { - // TODO: Have this raise an event - } - - private void saveTodoList() { - try { - storage.save(this); - } catch (IOException e) { - events.post(new DataSavingExceptionEvent(e)); - } - } - - @Override - public ImmutableTask add(String title) { - Task task = new Task(title); - tasks.add(task); - - saveTodoList(); - return task; - } - - @Override - public ImmutableTask add(String title, Consumer update) throws ValidationException { - ValidationTask validationTask = new ValidationTask(title); - update.accept(validationTask); - Task task = validationTask.convertToTask(); - tasks.add(task); - - saveTodoList(); - return task; - } - - @Override - public ImmutableTask delete(int index) throws ValidationException { - Task task = tasks.remove(index); - saveTodoList(); - return task; - } - - @Override - public ImmutableTask update(int index, Consumer update) throws ValidationException { - Task task = tasks.get(index); - ValidationTask validationTask = new ValidationTask(task); - update.accept(validationTask); - validationTask.validate(); - - // changes are validated and accepted - update.accept(task); - saveTodoList(); - return task; - } - -``` -###### \src\main\java\seedu\todo\model\TodoListModel.java -``` java -public interface TodoListModel extends ImmutableTodoList { - /** - * Adds a new task or event with title only to the todo list. - * - * @param title the title of the task - * @return the task that was just created - * @throws IllegalValueException if the values set in the update predicate is invalid - */ - ImmutableTask add(String title) throws IllegalValueException; - - /** - * Adds a new task or event with title and other fields to the todo list. - * - * @param title the title of the task - * @param update a {@link MutableTask} is passed into this lambda. All other fields - * should be set from inside this lambda. - * @return the task that was just created - * @throws ValidationException if the fields in the task to be updated are not valid - */ - ImmutableTask add(String title, Consumer update) throws ValidationException; - - /** - * Deletes the given task from the todo list. This change is also propagated to the - * underlying persistence layer. - * - * @param index the 1-indexed position of the task that needs to be deleted - * @return the task that was just deleted - * @throws ValidationException if the task does not exist - */ - ImmutableTask delete(int index) throws ValidationException; - - /** - * Replaces certain fields in the task. Mutation of the {@link Task} object should - * only be done in the update lambda. The lambda takes in one parameter, - * a {@link MutableTask}, and does not expect any return value. For example: - * - *
todo.update(task, t -> {
-     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline back by 2h
-     *     t.setPin(true); // Pin this task
-     * });
- * - * @return the task that was just updated - * - * @throws ValidationException if the task does not exist or if the fields in the - * task to be updated are not valid - */ - ImmutableTask update(int index, Consumer update) throws ValidationException; - -``` -###### \src\main\java\seedu\todo\model\TodoListModel.java -``` java - /** - * Changes the save path of the TodoList storage - * @throws ValidationException if the path is not valid - */ - void save(String location) throws ValidationException; - - /** - * Loads a TodoList from the path. - * @throws ValidationException if the path or file is invalid - */ - void load(String location) throws ValidationException; - - /** - * Replaces the tasks in list with the one in the - */ - void setTasks(List todoList); - - /** - * Get an observable list of tasks. Used mainly by the JavaFX UI. - */ - ObservableList getObservableList(); - -} - -``` -###### \src\main\java\seedu\todo\model\TodoModel.java -``` java -/** - * Represents the data layer of the application. The TodoModel handles any - * interaction with the application state that are not persisted, such as the - * view (sort and filtering), undo and redo. Since this layer handles - * sorting and filtering, task ID must be passed through {@link #getTaskIndex} - * to transform them into the index {@link TodoList} methods can use. - */ -public class TodoModel implements Model { - // Constants - private static final int UNDO_LIMIT = 10; - private static final String INDEX_OUT_OF_BOUND_FORMAT = "There is no task no. %d"; - private static final String NO_MORE_UNDO_REDO_FORMAT = "There are no more steps to %s"; - - // Dependencies - private TodoListModel todoList; - private UniqueTagCollectionModel uniqueTagCollection = new UniqueTagCollection(); - private MovableStorage storage; - - // Stack of transformation that the tasks go through before being displayed to the user - private ObservableList tasks; - private FilteredList viewFilteredTasks; - private FilteredList findFilteredTasks; - private SortedList sortedTasks; - - // State stacks for managing un/redo - private Deque> undoStack = new ArrayDeque<>(); - private Deque> redoStack = new ArrayDeque<>(); - - /** - * Contains the current view tab the user has selected. - * {@link #getViewFilter()} is the getter and {@link #view(TaskViewFilter)} is the setter - */ - private ObjectProperty view = new SimpleObjectProperty<>(); - - private ObjectProperty search = new SimpleObjectProperty<>(); - - public TodoModel(Config config) { - this(new TodoListStorage(config.getTodoListFilePath())); - } - - public TodoModel(MovableStorage storage) { - this(new TodoList(storage), storage); - } - - public TodoModel(TodoListModel todoList, MovableStorage storage) { - this.storage = storage; - this.todoList = todoList; - - tasks = todoList.getObservableList(); - viewFilteredTasks = new FilteredList<>(tasks); - findFilteredTasks = new FilteredList<>(viewFilteredTasks); - sortedTasks = new SortedList<>(findFilteredTasks); - - // Sets the default view - view(TaskViewFilter.DEFAULT); - } - - /** - * Because the model does filtering and sorting on the tasks, the incoming index needs to be - * translated into it's index in the underlying todoList. The code below is not particularly - * clean, but it works well enough. - * - * @throws ValidationException if the index is invalid - */ - private int getTaskIndex(int index) throws ValidationException { - int taskIndex; - - try { - ImmutableTask task = getObservableList().get(index - 1); - taskIndex = tasks.indexOf(task); - } catch (IndexOutOfBoundsException e) { - taskIndex = -1; - } - - if (taskIndex == -1) { - String message = String.format(TodoModel.INDEX_OUT_OF_BOUND_FORMAT, index); - throw new ValidationException(message); - } - - return taskIndex; - } - - private void saveState(Deque> stack) { - List tasks = todoList.getTasks().stream() - .map(Task::new).collect(Collectors.toList()); - - stack.addFirst(tasks); - while (stack.size() > TodoModel.UNDO_LIMIT) { - stack.removeLast(); - } - } - - private void saveUndoState() { - saveState(undoStack); - redoStack.clear(); - } - - @Override - public ImmutableTask add(String title) throws IllegalValueException { - saveUndoState(); - return todoList.add(title); - } - - @Override - public ImmutableTask add(String title, Consumer update) throws ValidationException { - saveUndoState(); - return todoList.add(title, update); - } - - @Override - public ImmutableTask delete(int index) throws ValidationException { - saveUndoState(); - ImmutableTask taskToDelete = getObservableList().get(getTaskIndex(index)); - uniqueTagCollection.notifyTaskDeleted(taskToDelete); - return todoList.delete(getTaskIndex(index)); - } - - @Override - public ImmutableTask update(int index, Consumer update) throws ValidationException { - saveUndoState(); - return todoList.update(getTaskIndex(index), update); - } - -``` -###### \src\main\java\seedu\todo\model\TodoModel.java -``` java - @Override - public void view(TaskViewFilter view) { - viewFilteredTasks.setPredicate(view.filter); - - sortedTasks.setComparator((a, b) -> { - int pin = Boolean.compare(b.isPinned(), a.isPinned()); - return pin != 0 || view.sort == null ? pin : view.sort.compare(a, b); - }); - - this.view.setValue(view); - } - - @Override - public void find(Predicate predicate) { - findFilteredTasks.setPredicate(predicate); - search.setValue(null); - } - - @Override - public void find(Predicate predicate, List terms) { - findFilteredTasks.setPredicate(predicate); - search.setValue(new SearchStatus(terms, findFilteredTasks.size(), tasks.size())); - } - - @Override - public void undo() throws ValidationException { - if (undoStack.isEmpty()) { - String message = String.format(TodoModel.NO_MORE_UNDO_REDO_FORMAT, "undo"); - throw new ValidationException(message); - } - - List tasks = undoStack.removeFirst(); - uniqueTagCollection.update(todoList.getObservableList()); - saveState(redoStack); - todoList.setTasks(tasks); - } - - @Override - public void redo() throws ValidationException { - if (redoStack.isEmpty()) { - String message = String.format(TodoModel.NO_MORE_UNDO_REDO_FORMAT, "redo"); - throw new ValidationException(message); - } - - List tasks = redoStack.removeFirst(); - uniqueTagCollection.update(todoList.getObservableList()); - saveState(undoStack); - todoList.setTasks(tasks); - } - - @Override - public void save(String location) throws ValidationException { - todoList.save(location); - } - - @Override - public void load(String location) throws ValidationException { - todoList.load(location); - uniqueTagCollection.update(todoList.getObservableList()); - } - - @Override - public String getStorageLocation() { - return storage.getLocation(); - } - - @Override - public UnmodifiableObservableList getObservableList() { - return new UnmodifiableObservableList<>(sortedTasks); - } - - @Override - public ObjectProperty getViewFilter() { - return view; - } - - @Override - public ObjectProperty getSearchStatus() { - return search; - } - -``` -###### \src\main\java\seedu\todo\storage\FixedStorage.java -``` java -/** - * Represents a storage mechanism to save an object to a fixed location - * @param - */ -public interface FixedStorage { - /** - * Reads the object from storage - * @return the object read from storage - * @throws DataConversionException if the data read was not of the expected type - * @throws FileNotFoundException if there was no file - */ - T read() throws DataConversionException, FileNotFoundException; - - /** - * Persists an object - * @param object the object that needs to be persisted - * @throws IOException if there was any problem saving the object to storage - */ - void save(T object) throws IOException; -} -``` -###### \src\main\java\seedu\todo\storage\MovableStorage.java -``` java -/** - * Represents an storage mechanism that allows an object to be saved to a - * specific location - */ -public interface MovableStorage extends FixedStorage { - - String getLocation(); - - T read(String location) throws DataConversionException, FileNotFoundException; - - void save(T object, String newLocation) throws IOException; -} -``` -###### \src\main\java\seedu\todo\ui\controller\TextAreaResizer.java -``` java -/** - * Automatically resize a text area object such that the height fits the content of the text area - */ -public class TextAreaResizer { - // Constants - private static final int CHAR_AHEAD = 3; - private static final int PADDING_OFFSET = 5; - - // FXML elements - private final TextArea textArea; - private final Text pseudoText = new Text(); - - // Internal variables - private double lineHeight; - private double charWidth; - private double textAreaWidth; - private int textAreaChars; - - public TextAreaResizer(TextArea textArea) { - this.textArea = textArea; - textAreaChars = textArea.getText().length(); - textAreaWidth = textArea.getWidth(); - - pseudoText.fontProperty().addListener((observable, oldValue, newValue) -> updateCharacterWidth()); - pseudoText.fontProperty().bind(textArea.fontProperty()); - - textArea.widthProperty().addListener((observable, oldValue, newValue) -> { - textAreaWidth = (double) newValue - PADDING_OFFSET; - updateTextArea(); - }); - - textArea.textProperty().addListener((observable, oldValue, newValue) -> { - textAreaChars = newValue.length(); - updateTextArea(); - }); - } - - private void updateCharacterWidth() { - final int TEST_CHAR_LEN = 60; - - pseudoText.setText(""); - double initialWidth = pseudoText.getLayoutBounds().getWidth(); - pseudoText.setText(Strings.repeat("w", TEST_CHAR_LEN)); - double newWidth = pseudoText.getLayoutBounds().getWidth(); - charWidth = (newWidth - initialWidth) / TEST_CHAR_LEN; - } - - private void updateTextArea() { - // Silly hack to make sure lineHeight is available before continuing - if (lineHeight <= 0) { - if ((lineHeight = textArea.getLayoutBounds().getHeight() - PADDING_OFFSET) <= 0) { - return; - } - } - - int lines = (int) (((textAreaChars + CHAR_AHEAD) * charWidth) / textAreaWidth) + 1; - double height = lines * lineHeight + PADDING_OFFSET; - - Platform.runLater(() -> { - textArea.setMaxHeight(height); - textArea.setPrefHeight(height); - }); - } -} -``` -###### \src\main\resources\style\DefaultStyle.css -``` css -.searchStatus { - -fx-padding: 4px; -} - -.searchStatus Text { - -fx-font-size: 14px; - -fx-fill: #fff; -} - -.searchStatus .searchLabel { -} - -.searchStatus .searchTerm { - -fx-font-weight: bold; -} - -.searchStatus .searchCount { - -fx-text-alignment: right; -} - -``` -###### \src\test\java\seedu\todo\logic\arguments\ArgumentTest.java -``` java -public class ArgumentTest { - private Argument arg = new TestArgument(); - - @Test - public void testRequiredErrorMessage() { - arg.required("Hello world"); - - try { - arg.checkRequired(); - } catch (IllegalValueException e) { - assertEquals("Hello world", e.getMessage()); - } - } - - @Test(expected=IllegalValueException.class) - public void testRequired() throws IllegalValueException { - arg.required(); - arg.checkRequired(); - } - - @Test - public void testIsOptional() { - assertTrue(arg.isOptional()); - arg.required(); - assertFalse(arg.isOptional()); - } - - @Test - public void testIsPositional() { - assertTrue(arg.isPositional()); - arg.flag("t"); - assertFalse(arg.isPositional()); - } - - @Test - public void testFlag() { - assertNull(arg.getFlag()); - arg.flag("h"); - assertEquals("h", arg.getFlag()); - arg.flag(" H "); - assertEquals("h", arg.getFlag()); - } - - @Test - public void testDescription() { - assertNull(arg.getDescription()); - arg.description("Hello World"); - assertEquals("Hello World", arg.getDescription()); - } - - @Test - public void testToString() { - assertEquals("[Test]", arg.toString()); - assertEquals("[Something]", arg.toString("Something")); - - arg.flag("t"); - assertEquals("[/t Test]", arg.toString()); - assertEquals("[/t Something]", arg.toString("Something")); - - arg.required(); - assertEquals("/t Test", arg.toString()); - assertEquals("/t Something", arg.toString("Something")); - } - - private class TestArgument extends Argument { - public TestArgument() { - super("Test"); - } - } -} -``` -###### \src\test\java\seedu\todo\logic\arguments\DateRangeArgumentTest.java -``` java -public class DateRangeArgumentTest { - private final Argument arg = new DateRangeArgument("Test"); - private final LocalDateTime tomorrow = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(0, 0)); - - @Test - public void testDefaultValue() throws Exception { - assertNull(arg.getValue().getStartTime()); - assertNull(arg.getValue().getEndTime()); - assertFalse(arg.hasBoundValue()); - } - - @Test - public void testEmptyInput() throws Exception { - arg.setValue(""); - assertNull(arg.getValue().getStartTime()); - assertNull(arg.getValue().getEndTime()); - assertTrue(arg.hasBoundValue()); - } - - @Test - public void testInternationalDate() throws Exception { - arg.setValue("6/12/16"); - assertEquals(LocalDate.of(2016, 12, 6), arg.getValue().getEndTime().toLocalDate()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testIsoDate() throws Exception { - arg.setValue("06-12-2016"); - assertEquals(LocalDate.of(2016, 12, 6), arg.getValue().getEndTime().toLocalDate()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testInternationalDateTime() throws Exception { - arg.setValue("6/12/16 12:45pm"); - assertEquals(LocalDateTime.of(2016, 12, 6, 12, 45), arg.getValue().getEndTime()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testNaturalLanguageDateTime() throws Exception { - arg.setValue("12 Oct 2014 6pm"); - assertEquals(LocalDateTime.of(2014, 10, 12, 18, 0), arg.getValue().getEndTime()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testRelativeDate() throws Exception { - arg.setValue("tomorrow"); - assertEquals(tomorrow.toLocalDate(), arg.getValue().getEndTime().toLocalDate()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testRelativeDateTime() throws Exception { - arg.setValue("tomorrow 6pm"); - assertEquals(tomorrow.withHour(18), arg.getValue().getEndTime()); - assertFalse(arg.getValue().isRange()); - } - - @Test - public void testRelativeDateRange() throws Exception { - arg.setValue("tomorrow 6 to 8pm"); - assertEquals(tomorrow.withHour(20), arg.getValue().getEndTime()); - assertEquals(tomorrow.withHour(18), arg.getValue().getStartTime()); - } - - @Test - public void testFormalDateTimeRange() throws Exception { - arg.setValue("18-12-16 1800hrs to 2000hrs"); - LocalDateTime date = LocalDateTime.of(2016, 12, 18, 0, 0); - assertEquals(date.withHour(20), arg.getValue().getEndTime()); - assertEquals(date.withHour(18), arg.getValue().getStartTime()); - - arg.setValue("18-12-16 1800hrs to 19-12-16 2000hrs"); - assertEquals(LocalDateTime.of(2016, 12, 18, 18, 0), arg.getValue().getStartTime()); - assertEquals(LocalDateTime.of(2016, 12, 19, 20, 0), arg.getValue().getEndTime()); - } - - @Test(expected=IllegalValueException.class) - public void testNoDate() throws Exception { - arg.setValue("no date here"); - } - - @Test(expected=IllegalValueException.class) - public void testTooManyDates() throws Exception { - arg.setValue("yesterday, today, tomorrow"); - } - -} -``` -###### \src\test\java\seedu\todo\logic\arguments\DateRangeTest.java -``` java -public class DateRangeTest { - - @Test - public void testDateRangeLocalDateTime() { - LocalDateTime start = LocalDateTime.now(); - DateRange range = new DateRange(start); - assertNull(range.getStartTime()); - assertFalse(range.isRange()); - } - - @Test - public void testDateRangeLocalDateTimeLocalDateTime() { - LocalDateTime start = LocalDateTime.now(); - LocalDateTime end = start.plusHours(2); - DateRange range = new DateRange(start, end); - - assertTrue(range.isRange()); - } - -} -``` -###### \src\test\java\seedu\todo\logic\arguments\FlagArgumentTest.java -``` java -public class FlagArgumentTest { - - private Argument argument; - - @Before - public void setUp() throws Exception { - argument = new FlagArgument("test"); - } - - @Test - public void testDefaultValue() { - assertEquals("t", argument.getFlag()); - assertFalse(argument.getValue()); - - argument = new FlagArgument("Pin", true); - assertTrue(argument.getValue()); - assertEquals("p", argument.getFlag()); - } - - @Test - public void testSetEmptyValue() throws IllegalValueException { - argument.setValue(""); - assertTrue(argument.getValue()); - } - - @Test - public void testSetStringValue() throws IllegalValueException { - argument.setValue("Hello World"); - assertTrue(argument.getValue()); - } - - @Test - public void testToString() { - argument.flag("t"); - assertEquals("[/t]", argument.toString()); - - argument.required(); - assertEquals("/t", argument.toString()); - } - -} -``` -###### \src\test\java\seedu\todo\logic\arguments\IntArgumentTest.java -``` java -public class IntArgumentTest { - private Argument argument; - - private int setInput(String input) throws IllegalValueException { - argument.setValue(input); - return argument.getValue(); - } - - @Before - public void setUp() { - argument = new IntArgument("test"); - } - - @Test - public void testParse() throws IllegalValueException { - assertEquals(123, setInput("123")); - assertEquals(-345, setInput("-345")); - } - - @Test(expected=IllegalValueException.class) - public void testFloatArgument() throws IllegalValueException { - setInput("12.34"); - } - - @Test(expected=IllegalValueException.class) - public void testStringArgument() throws IllegalValueException { - setInput("random stuff"); - } -} -``` -###### \src\test\java\seedu\todo\logic\arguments\StringArgumentTest.java -``` java -public class StringArgumentTest { - - private Argument arg = new StringArgument("test"); - - @Test - public void testSetValue() throws IllegalValueException { - arg.setValue("Hello world"); - assertTrue(arg.hasBoundValue()); - assertEquals("Hello world", arg.getValue()); - } - - @Test - public void testTrimValue() throws IllegalValueException { - arg.setValue(" Hello world "); - assertTrue(arg.hasBoundValue()); - assertEquals("Hello world", arg.getValue()); - } - - @Test - public void testEmptyValue() throws IllegalValueException { - arg.setValue(" "); - assertTrue(arg.hasBoundValue()); - assertEquals(arg.getValue(), null); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\BaseCommandTest.java -``` java -public class BaseCommandTest extends CommandTest { - private Argument requiredArgument = mock(StringArgument.class); - private Argument flagArgument = mock(FlagArgument.class); - private Argument intArgument = mock(IntArgument.class); - private Argument stringArgument = mock(StringArgument.class); - - private StubCommand stubCommand; - - @Override - protected BaseCommand commandUnderTest() { - stubCommand = new StubCommand(); - return stubCommand; - } - - @Before - public void setUp() throws Exception { - when(requiredArgument.isPositional()).thenReturn(true); - when(requiredArgument.toString()).thenReturn("required"); - - when(flagArgument.isOptional()).thenReturn(true); - when(flagArgument.getFlag()).thenReturn("f"); - when(flagArgument.toString()).thenReturn("flag"); - - when(intArgument.isOptional()).thenReturn(true); - when(intArgument.getFlag()).thenReturn("i"); - when(intArgument.toString()).thenReturn("int"); - - when(stringArgument.isOptional()).thenReturn(true); - when(stringArgument.getFlag()).thenReturn("s"); - when(stringArgument.toString()).thenReturn("string"); - } - - @Test - public void testSetParameter() throws Exception { - this.setParameter("required") - .setParameter("f", "") - .setParameter("i", "20") - .setParameter("s", "Hello World"); - - execute(true); - - verify(requiredArgument).setValue("required"); - verify(flagArgument).setValue(""); - verify(intArgument).setValue("20"); - verify(stringArgument).setValue("Hello World"); - } - - @Test - public void testCustomArgumentError() throws Exception { - command = new CommandWithOverrideMethods(); - - try { - execute(false); - fail(); - } catch (ValidationException e) { - assertEquals("Test error message", e.getMessage()); - assertTrue(e.getErrors().getNonFieldErrors().contains("Test error")); - } - } - - @Test - public void getArgumentSummary() { - assertEquals("required flag int string", stubCommand.getArgumentSummaryResult()); - } - - @Test(expected=ValidationException.class) - public void testMissingRequiredArgument() throws Exception { - IllegalValueException e = mock(IllegalValueException.class); - doThrow(e).when(requiredArgument).checkRequired(); - - execute(false); - } - - private class StubCommand extends BaseCommand { - @Override - protected Parameter[] getArguments() { - return new Parameter[]{ requiredArgument, flagArgument, intArgument, stringArgument }; - } - - @Override - public String getCommandName() { - return "stub"; - } - - @Override - public List getCommandSummary() { - return ImmutableList.of(mock(CommandSummary.class)); - } - - @Override - public CommandResult execute() throws ValidationException { - // Does nothing - return new CommandResult("Great Success!"); - } - - public String getArgumentSummaryResult() { - return getArgumentSummary(); - } - } - - private class CommandWithOverrideMethods extends StubCommand { - @Override - protected String getArgumentErrorMessage() { - return "Test error message"; - } - - @Override - protected void validateArguments() { - errors.put("Test error"); - } - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\CommandSummaryTest.java -``` java -public class CommandSummaryTest { - @Test - public void testConstructor() { - CommandSummary summary = new CommandSummary(" Hello ", "World"); - // Check trim - assertEquals("Hello", summary.scenario); - // Check command is lowercase - assertEquals("world", summary.command); - // Check constructor without third argument - assertEquals("", summary.arguments); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\CommandTest.java -``` java -/** - * Base test case for testing commands. All command tests should extend this class. - * Provides a simple interface for setting up command testing as well as a number - * of assertions to inspect the model. - */ -public abstract class CommandTest { - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - protected Model model; - protected TodoList todolist; - @Mock protected MovableStorage storage; - @Mock protected ImmutableTodoList storageData; - protected BaseCommand command; - protected StubParseResult params; - protected CommandResult result; - - abstract protected BaseCommand commandUnderTest(); - - @Before - public void setUpCommand() throws Exception { - when(storage.read()).thenReturn(storageData); - when(storageData.getTasks()).thenReturn(Collections.emptyList()); - - todolist = new TodoList(storage); - model = new TodoModel(todolist, storage); - params = new StubParseResult(); - command = commandUnderTest(); - } - - /** - * Returns the task visible in the model at 1-indexed position, mimicking user input - */ - protected ImmutableTask getTaskAt(int index) { - return model.getObservableList().get(index - 1); - } - - /** - * Asserts that the model has this number of tasks stored in internal storage (visible and not visible) - */ - - protected void assertTotalTaskCount(int size) { - assertEquals(size, todolist.getTasks().size()); - } - - /** - * Asserts that the model has this number of tasks visible - */ - protected void assertVisibleTaskCount(int size) { - assertEquals(size, model.getObservableList().size()); - } - - /** - * Asserts that the task exists in memory - */ - protected void assertTaskExist(ImmutableTask task) { - if (!todolist.getTasks().contains(task)) { - throw new AssertionError("Task not found in model"); - } - } - - - /** - * Asserts that the task does not exist in memory - */ - protected void assertTaskNotExist(ImmutableTask task) { - if (todolist.getTasks().contains(task)) { - throw new AssertionError("Task found in model"); - } - } - - /** - * Asserts that the task is visible to the user through the model - */ - protected void assertTaskVisible(ImmutableTask task) { - if (!model.getObservableList().contains(task)) { - throw new AssertionError("Task is not visible"); - } - } - - /** - * Asserts that the task is visible to the user through the model. - * This can also mean the task is simply not in memory. Use {@link #assertTaskHidden} - * to assert that the task exists, but is not visible - */ - protected void assertTaskNotVisible(ImmutableTask task) { - if (model.getObservableList().contains(task)) { - throw new AssertionError("Task is visible"); - } - } - - /** - * Asserts that the task exists, but is not visible to the user through - * the model - */ - protected void assertTaskHidden(ImmutableTask task) { - assertTaskExist(task); - assertTaskNotVisible(task); - } - - /** - * Sets the positional parameter for command execution. Can be chained. - */ - protected CommandTest setParameter(String positional) { - params.positional = positional; - return this; - } - - /** - * Sets the named argument for command execution. Can be chained. - */ - protected CommandTest setParameter(String flag, String value) { - params.named.put(flag, value); - return this; - } - - @Test - public void testCommonProperties() { - assertNotNull(command.getArguments()); - assertThat(command.getCommandName(), not(containsString(" "))); - assertThat(command.getCommandSummary().size(), greaterThan(0)); - } - - /** - * Executes the command - */ - protected void execute(boolean expectSuccess) throws ValidationException { - command.setArguments(params); - command.setModel(model); - result = command.execute(); - - assertEquals(expectSuccess, result.isSuccessful()); - - // Resets the command object for re-execution - command = commandUnderTest(); - params = new StubParseResult(); - } - - private class StubParseResult implements ParseResult { - public String command; - public String positional; - public Map named = new HashMap<>(); - - @Override - public String getCommand() { - return command; - } - - @Override - public Optional getPositionalArgument() { - return Optional.ofNullable(positional); - } - - @Override - public Map getNamedArguments() { - return named; - } - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\ExitCommandTest.java -``` java -public class ExitCommandTest extends CommandTest { - @Override - protected BaseCommand commandUnderTest() { - return new ExitCommand(); - } - - @Test - public void testExecute() throws IllegalValueException, ValidationException { - EventsCollector eventCollector = new EventsCollector(); - execute(true); - assertThat(eventCollector.get(0), instanceOf(ExitAppRequestEvent.class)); - } - -} -``` -###### \src\test\java\seedu\todo\logic\commands\FindCommandTest.java -``` java - @Test - public void testFindWithFilter() throws ValidationException { - TaskViewFilter filter = new TaskViewFilter("test", t -> t.getTitle().contains("CS2101"), null); - model.view(filter); - - setParameter("Task"); - execute(true); - assertVisibleTaskCount(1); - } - - @Test - public void testDismissFind() throws ValidationException { - setParameter("project"); - execute(true); - assertVisibleTaskCount(2); - assertNotNull(model.getSearchStatus().getValue()); - - setParameter(""); - execute(true); - assertVisibleTaskCount(4); - assertNull(model.getSearchStatus().getValue()); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\HelpCommandTest.java -``` java -public class HelpCommandTest extends CommandTest { - @Override - protected BaseCommand commandUnderTest() { - return new HelpCommand(); - } - - @Test - public void testExecute() throws Exception { - EventsCollector eventsCollector = new EventsCollector(); - execute(true); - assertThat(eventsCollector.get(0), instanceOf(ShowHelpEvent.class)); - } - -} -``` -###### \src\test\java\seedu\todo\logic\commands\LoadCommandTest.java -``` java -/** - * This is an integration test for the {@code load} command. For tests on the - * load functionality itself, see {@link seedu.todo.storage.TodoListStorageTest} - */ -public class LoadCommandTest extends CommandTest { - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Mock private ImmutableTodoList tasks; - - @Override - protected BaseCommand commandUnderTest() { - return new LoadCommand(); - } - - @Test - public void testSaveLocation() throws Exception { - setParameter("new file"); - when(storage.read("new file")).thenReturn(tasks); - when(tasks.getTasks()).thenReturn(ImmutableList.of(new Task("Hello world"))); - - execute(true); - assertEquals("Hello world", getTaskAt(1).getTitle()); - } - - @Test(expected = ValidationException.class) - public void testHandleFileError() throws Exception { - setParameter("new file"); - doThrow(new FileNotFoundException()).when(storage).read("new file"); - execute(false); - } - -} -``` -###### \src\test\java\seedu\todo\logic\commands\RedoCommandTest.java -``` java -public class RedoCommandTest extends CommandTest { - @Override - protected BaseCommand commandUnderTest() { - return new RedoCommand(); - } - - /** - * This is an integration test for the redo command. For a more detailed test on the model itself - * {@link seedu.todo.model.TodoModelTest#testRedo} and other related tests - */ - @Test - public void testExecute() throws Exception { - model.add("Test task"); - model.undo(); - execute(true); - assertEquals("Test task", getTaskAt(1).getTitle()); - } - - @Test(expected = ValidationException.class) - public void testIncorrectExecute() throws Exception { - execute(false); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\SaveCommandTest.java -``` java -public class SaveCommandTest extends CommandTest { - @Test - public void testGetStorageLocation() throws Exception { - when(storage.getLocation()).thenReturn("test location"); - execute(true); - - assertThat(result.getFeedback(), containsString("test location")); - verify(storage, never()).save(eq(todolist), anyString()); - } - - @Test - public void testSaveLocation() throws Exception { - setParameter("new file"); - execute(true); - verify(storage).save(todolist, "new file"); - } - - @Test(expected = ValidationException.class) - public void testHandleFileError() throws Exception { - setParameter("new file"); - doThrow(new IOException()).when(storage).save(todolist, "new file"); - execute(false); - } - - @Override - protected BaseCommand commandUnderTest() { - return new SaveCommand(); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\UndoCommandTest.java -``` java -public class UndoCommandTest extends CommandTest { - - @Override - protected BaseCommand commandUnderTest() { - return new UndoCommand(); - } - - /** - * This is an integration test for the redo command. For a more detailed test on the model itself - * {@link TodoModelTest#testUndo} and other related tests - */ - @Test - public void testUndo() throws Exception { - model.add("Test task"); - execute(true); - } - - @Test(expected = ValidationException.class) - public void testEmptyUndo() throws Exception { - execute(false); - } -} -``` -###### \src\test\java\seedu\todo\logic\parser\TodoParserTest.java -``` java -public class TodoParserTest { - private Parser parser = new TodoParser(); - - @Test - public void testParse() { - ParseResult p; - - p = parser.parse("hello"); - assertEquals("hello", p.getCommand()); - - p = parser.parse("HeLLo"); - assertEquals("hello", p.getCommand()); - - p = parser.parse("HELLO"); - assertEquals("hello", p.getCommand()); - - p = parser.parse("hello world"); - assertEquals("hello", p.getCommand()); - - p = parser.parse(" hello "); - assertEquals("hello", p.getCommand()); - } - - @Test - public void testPositionalArgument() { - ParseResult p; - - p = parser.parse("hello world"); - assertEquals("world", p.getPositionalArgument().get()); - - p = parser.parse("hello one two three"); - assertEquals("one two three", p.getPositionalArgument().get()); - - p = parser.parse("hello one two three "); - assertEquals("one two three", p.getPositionalArgument().get()); - } - - @Test - public void testNamedArguments() { - ParseResult p; - - p = parser.parse("hello /f"); - assertEquals(1, p.getNamedArguments().size()); - assertTrue(p.getNamedArguments().containsKey("f")); - - p = parser.parse("hello /f Hello"); - assertEquals(1, p.getNamedArguments().size()); - assertEquals("Hello", p.getNamedArguments().get("f")); - - p = parser.parse("hello /f Hello "); - assertEquals(1, p.getNamedArguments().size()); - assertEquals("Hello", p.getNamedArguments().get("f")); - - p = parser.parse("hello /all Hello"); - assertEquals(1, p.getNamedArguments().size()); - assertEquals("Hello", p.getNamedArguments().get("all")); - - p = parser.parse("hello /f Hello /p /all"); - assertEquals(3, p.getNamedArguments().size()); - assertEquals("Hello", p.getNamedArguments().get("f")); - assertTrue(p.getNamedArguments().containsKey("p")); - assertTrue(p.getNamedArguments().containsKey("all")); - } - - @Test - public void testInvalidFlags() { - ParseResult p; - - p = parser.parse("hello /"); - assertTrue(p.getPositionalArgument().isPresent()); - assertEquals(0, p.getNamedArguments().size()); - } - -} -``` -###### \src\test\java\seedu\todo\logic\TodoDispatcherTest.java -``` java -public class TodoDispatcherTest { - private Dispatcher d = new TodoDispatcher(); - - @Test - public void testFullCommand() throws Exception { - assertThat(d.dispatch("add"), instanceOf(AddCommand.class)); - assertThat(d.dispatch("exit"), instanceOf(ExitCommand.class)); - } - - @Test - public void testPartialCommand() throws Exception { - assertThat(d.dispatch("ed"), instanceOf(EditCommand.class)); - assertThat(d.dispatch("a"), instanceOf(AddCommand.class)); - } - - @Test(expected = IllegalValueException.class) - public void testAmbiguousCommand() throws Exception { - d.dispatch("e"); - } - - @Test(expected = IllegalValueException.class) - public void testNonExistentCommand() throws Exception { - d.dispatch("applejack"); - } -} -``` -###### \src\test\java\seedu\todo\logic\TodoLogicTest.java -``` java -public class TodoLogicTest { - private static final String INPUT = "input"; - private static final String COMMAND = "command"; - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Mock private Parser parser; - @Mock private Dispatcher dispatcher; - @Mock private ParseResult parseResult; - @Mock private Model model; - @Mock private BaseCommand command; - - private Logic logic; - - @Before - public void setUp() throws Exception { - // Wire up some default behavior - when(parser.parse(TodoLogicTest.INPUT)) - .thenReturn(parseResult); - when(parseResult.getCommand()) - .thenReturn(TodoLogicTest.COMMAND); - when(dispatcher.dispatch(TodoLogicTest.COMMAND)) - .thenReturn(command); - - logic = new TodoLogic(parser, model, dispatcher); - } - - private CommandResult execute() throws Exception { - CommandResult r = logic.execute(TodoLogicTest.INPUT); - - verify(parser).parse(TodoLogicTest.INPUT); - verify(dispatcher).dispatch(TodoLogicTest.COMMAND); - - return r; - } - - @Test - public void testExecute() throws Exception { - execute(); - - verify(command).setModel(model); - verify(command).setArguments(parseResult); - verify(command).execute(); - - // Logic should not touch model directly - verifyZeroInteractions(model); - } - - @Test - public void testArgumentError() throws Exception { - // Create a stub exception for setArguments to throw - ValidationException e = mock(ValidationException.class); - ErrorBag errors = mock(ErrorBag.class); - - when(e.getErrors()).thenReturn(errors); - doThrow(e).when(command).setArguments(parseResult); - - CommandResult r = execute(); - - assertFalse(r.isSuccessful()); - // Make sure the command is never executed - verify(command, never()).execute(); - } - - @Test - public void testExecuteError() throws Exception { - // Create a stub exception for execute to throw - ValidationException e = mock(ValidationException.class); - ErrorBag errors = mock(ErrorBag.class); - - when(e.getErrors()).thenReturn(errors); - doThrow(e).when(command).execute(); - - CommandResult r = execute(); - - assertFalse(r.isSuccessful()); - assertEquals(errors, r.getErrors()); - } - - @Test - public void testDispatchError() throws Exception { - // Create a stub exception for execute to throw - IllegalValueException e = mock(IllegalValueException.class); - when(e.getMessage()).thenReturn("Test message"); - doThrow(e).when(dispatcher).dispatch(TodoLogicTest.COMMAND); - - CommandResult r = execute(); - - assertFalse(r.isSuccessful()); - assertEquals("Test message", r.getFeedback()); - } - - @Test - public void testEmptyInput() throws Exception { - CommandResult r = logic.execute(""); - - assertNotNull(r); - assertNotNull(r.getFeedback()); - verifyZeroInteractions(parser); - } -} -``` -###### \src\test\java\seedu\todo\model\ErrorBagTest.java -``` java -public class ErrorBagTest { - - private ErrorBag errorBag = new ErrorBag(); - - @Test - public void testPutString() { - errorBag.put("Hello World"); - errorBag.put("Another error"); - - assertEquals(0, errorBag.getNonFieldErrors().indexOf("Hello World")); - assertEquals(1, errorBag.getNonFieldErrors().indexOf("Another error")); - - assertEquals(2, errorBag.size()); - assertEquals(0, errorBag.getFieldErrors().size()); - } - - @Test - public void testPutStringString() { - errorBag.put("a", "Hello World"); - errorBag.put("b", "Another error"); - - assertEquals("Hello World", errorBag.getFieldErrors().get("a")); - assertEquals("Another error", errorBag.getFieldErrors().get("b")); - - assertEquals(2, errorBag.size()); - assertEquals(0, errorBag.getNonFieldErrors().size()); - } - - @Test - public void testSize() { - errorBag.put("Hello World"); - errorBag.put("a", "Hello World"); - - assertEquals(2, errorBag.size()); - } - - @Test - public void testValidateNoErrors() throws ValidationException { - assertEquals(0, errorBag.size()); - errorBag.validate("Validation message"); - } - - - @Test(expected=ValidationException.class) - public void testValidateWithErrors() throws ValidationException { - errorBag.put("Hello World"); - errorBag.put("a", "Hello World"); - - errorBag.validate("Validation message"); - } - - @Test - public void testValidationException() { - errorBag.put("Hello World"); - errorBag.put("a", "Hello World"); - - try { - errorBag.validate("Validation message"); - } catch (ValidationException e) { - assertEquals(errorBag, e.getErrors()); - assertEquals("Validation message", e.getMessage()); - } - - } - -} -``` -###### \src\test\java\seedu\todo\model\property\TaskViewFilterTest.java -``` java -public class TaskViewFilterTest { - @Test - public void testNoOverlappingShortcut() { - Set shortcuts = new HashSet<>(); - - for (TaskViewFilter filter : TaskViewFilter.all()) { - char shortcut = filter.name.charAt(filter.shortcutCharPosition); - assertFalse(shortcuts.contains(shortcut)); - shortcuts.add(shortcut); - } - } -} -``` -###### \src\test\java\seedu\todo\model\task\ImmutableTaskTest.java -``` java -/** - * This set of tests check that every implementation of ImmutableTask are equal and - * generates the same hashCode as each other when their copy constructor is used. - */ -public class ImmutableTaskTest { - private ImmutableTask reference = TaskFactory.fullEvent(); - - private List tasks = ImmutableList.of( - new ValidationTask(reference), - new Task(reference) - ); - - private void permuteTasks(Function mapper) { - Object referenceProperty = mapper.apply(reference); - - for (int i = 0; i < tasks.size(); i++) { - ImmutableTask subject = tasks.get(i); - Object subjectProperty = mapper.apply(subject); - - assertEquals(subjectProperty, referenceProperty); - assertEquals(referenceProperty, subjectProperty); - - for (int j = i; j < tasks.size(); j++) { - assertEquals(subjectProperty, mapper.apply(tasks.get(i))); - assertEquals(mapper.apply(tasks.get(i)), subjectProperty); - } - } - } - - @Test - public void testEquality() { - permuteTasks(task -> task); - } - - @Test - public void testHashing() { - permuteTasks(Object::hashCode); - } - - @Test - public void testProperties() { - permuteTasks(ImmutableTask::getTitle); - permuteTasks(ImmutableTask::getTags); - permuteTasks(ImmutableTask::getDescription); - permuteTasks(ImmutableTask::getStartTime); - permuteTasks(ImmutableTask::getEndTime); - permuteTasks(ImmutableTask::getLocation); - permuteTasks(ImmutableTask::isCompleted); - permuteTasks(ImmutableTask::isEvent); - permuteTasks(ImmutableTask::isPinned); - permuteTasks(ImmutableTask::getCreatedAt); - } -} -``` -###### \src\test\java\seedu\todo\model\TodoModelTest.java -``` java -public class TodoModelTest { - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Rule - public final ExpectedException exception = ExpectedException.none(); - - @Mock private MovableStorage storage; - @Mock private ImmutableTodoList storageData; - - private TodoList todolist; - private TodoModel model; - private UnmodifiableObservableList observableList; - - @Before - public void setUp() throws Exception { - when(storage.read()).thenReturn(storageData); - when(storageData.getTasks()).thenReturn(Collections.emptyList()); - - todolist = new TodoList(storage); - model = new TodoModel(todolist, storage); - observableList = model.getObservableList(); - } - - @Test - public void testUpdateDateFields() throws Exception { - model.add("Test Task"); - - assertNotNull(model.update(1, t -> { - t.setStartTime(TimeUtil.now); - t.setEndTime(TimeUtil.now.plusHours(4)); - })); - - ImmutableTask task = observableList.get(0); - assertEquals(TimeUtil.now, task.getStartTime().get()); - assertEquals(TimeUtil.now.plusHours(4), task.getEndTime().get()); - - model.update(1, t -> t.setEndTime(TimeUtil.now)); - task = observableList.get(0); - assertEquals(TimeUtil.now, task.getStartTime().get()); - assertEquals(TimeUtil.now, task.getEndTime().get()); - } - - @Test - public void testPinning() throws Exception { - model.add("Task 1"); - model.add("Task 2"); - model.add("Task 3"); - - // Get the last item and pin it - ImmutableTask lastTask = observableList.get(2); - model.update(3, t -> t.setPinned(true)); - - assertTrue(observableList.get(0).isPinned()); - assertEquals(lastTask.getTitle(), observableList.get(0).getTitle()); - } - - - @Test(expected = ValidationException.class) - public void testIllegalUpdate() throws Exception { - model.add("Foo Bar Test"); - model.update(2, t -> t.setTitle("Test 2")); - } - - @Test(expected = ValidationException.class) - public void testIllegalDelete() throws Exception { - model.add("Foo Bar Test"); - model.delete(2); - } - - @Test - public void testSorting() throws Exception { - model.add("Task 3", p -> p.setEndTime(TimeUtil.now)); - model.add("Task 2", p -> p.setEndTime(TimeUtil.now.plusHours(2))); - model.add("Task 1", p -> p.setEndTime(TimeUtil.now.plusHours(1))); - - TaskViewFilter lexi = new TaskViewFilter("lexi", - null, (a, b) -> a.getTitle().compareTo(b.getTitle())); - TaskViewFilter chrono = new TaskViewFilter("chrono", - null, (a, b) -> a.getEndTime().get().compareTo(b.getEndTime().get())); - - // Check that the items are sorted in lexicographical order by title - model.view(lexi); - assertEquals("Task 1", observableList.get(0).getTitle()); - assertEquals("Task 2", observableList.get(1).getTitle()); - assertEquals(3, observableList.size()); - - // Insert an item that comes before all others lexicographically - model.add("Task 0", p -> p.setEndTime(TimeUtil.now.plusHours(3))); - assertEquals("Task 0", observableList.get(0).getTitle()); - - // Change its title and check that it has moved down - model.update(1, t -> t.setTitle("Task 4")); - assertEquals("Task 1", observableList.get(0).getTitle()); - assertEquals("Task 4", observableList.get(3).getTitle()); - - // Check that sorting by time works - // Chronological ordering would give us Task 3, 1, 2, 4 - model.view(chrono); - assertEquals("Task 3", observableList.get(0).getTitle()); - assertEquals("Task 1", observableList.get(1).getTitle()); - assertEquals("Task 2", observableList.get(2).getTitle()); - assertEquals("Task 4", observableList.get(3).getTitle()); - } - - @Test - public void testFiltering() throws Exception { - model.add("Foo"); - model.add("FooBar"); - model.add("Bar"); - model.add("Bar Bar"); - - // Give us only tasks with "Foo" in the title - model.find(t -> t.getTitle().contains("Foo")); - assertEquals(2, observableList.size()); - } - - @Test - public void testSave() throws Exception { - model.save("new location"); - verify(storage).save(todolist, "new location"); - } - - @Test - public void testLoad() throws Exception { - model.add("Some task"); - - when(storageData.getTasks()) - .thenReturn(ImmutableList.of(new Task("Hello world"))); - when(storage.read("new location")).thenReturn(storageData); - - model.load("new location"); - // Check that the task list has been replaced - assertEquals(1, todolist.getTasks().size()); - assertEquals("Hello world", getTask(0).getTitle()); - } - - @Test - public void testUndo() throws Exception { - model.add("Test task 1"); - model.add("Test task 2"); - - // Undo add - model.undo(); - assertEquals(1, todolist.getTasks().size()); - assertEquals("Test task 1", getTask(0).getTitle()); - - // Undo edit - model.update(1, t -> t.setTitle("New title")); - model.undo(); - assertEquals("Test task 1", getTask(0).getTitle()); - - // Undo delete - model.delete(1); - model.undo(); - assertEquals(1, todolist.getTasks().size()); - } - - @Test - public void testPersistAfterUndo() throws Exception { - model.add("Test task 1"); - model.undo(); - verify(storage, times(2)).save(todolist); - } - - @Test - public void testUndoStackSize() throws Exception { - final int STACK_SIZE = 10; - - // Add 11 items (which will add 11 to the undo stack) - for (int i = 0; i < STACK_SIZE + 1; i++) { - model.add(TaskFactory.taskTitle()); - } - - // Undo until the undo stack is empty - for (int i = 0; i < STACK_SIZE; i++) { - model.undo(); - } - - // Make sure the last undo throws an exception, even though 11 add - // events were recorded - exception.expect(ValidationException.class); - model.undo(); - } - - @Test(expected = ValidationException.class) - public void testOnlyUndoDataChanges() throws Exception { - // Actions that does not cause underlying data to change should not cause - // undo stack to increase - model.find(ImmutableTask::isCompleted); - model.undo(); - } - - @Test(expected = ValidationException.class) - public void testEmptyUndoStack() throws Exception { - model.undo(); - } - - @Test - public void testRedo() throws Exception { - model.add("Test task 1"); - model.add("Test task 2"); - model.add("Test task 3"); - - // Undo then redo add - model.undo(); - model.undo(); - assertEquals(1, todolist.getTasks().size()); - - model.redo(); - assertEquals(2, todolist.getTasks().size()); - assertEquals("Test task 2", getTask(1).getTitle()); - - model.redo(); - assertEquals(3, todolist.getTasks().size()); - assertEquals("Test task 3", getTask(2).getTitle()); - } - - @Test - public void testPersistAfterRedo() throws Exception { - model.add("Test task 1"); - model.undo(); - model.redo(); - verify(storage, times(3)).save(todolist); - } - - @Test - public void testActionAfterRedoClearsStack() throws Exception { - model.add("Test task 1"); - model.add("Test task 2"); - model.add("Test task 3"); - - // Undo then redo add - model.undo(); - model.undo(); - assertEquals(1, todolist.getTasks().size()); - - model.redo(); - assertEquals(2, todolist.getTasks().size()); - assertEquals("Test task 2", getTask(1).getTitle()); - - // There should be at least one more undo in this, but by performing - // an action we should invalidate the undo stack - model.add("Test task 4"); - exception.expect(ValidationException.class); - model.redo(); - } - - @Test(expected = ValidationException.class) - public void testEmptyRedoStack() throws Exception { - model.redo(); - } - - @Test - public void testGetSaveLocation() throws Exception { - when(storage.getLocation()).thenReturn("test location"); - assertEquals("test location", model.getStorageLocation()); - } - - /** - * Get the task at the zero-index position in memory (reverse insertion order) - */ - private ImmutableTask getTask(int index) { - return todolist.getTasks().get(index); - } - -} -``` -###### \src\test\java\seedu\todo\model\TodoTest.java -``` java -public class TodoTest { - @Rule - public final ExpectedException exception = ExpectedException.none(); - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Mock private MovableStorage storage; - @Mock private ImmutableTodoList storageData; - private TodoList todo; - - @Before - public void setUp() throws Exception { - when(storage.read()).thenReturn(storageData); - todo = new TodoList(storage); - } - - @Test - public void testEmptyStorage() throws Exception { - when(storage.read()).thenThrow(new FileNotFoundException()); - todo = new TodoList(storage); - - assertThat(todo.getTasks(), empty()); - } - - @Test - public void testRestoreFromStorage() { - // Create a mock todo list, add it to mock storage and try to get - // TodoList to retrieve it - Task task1 = new Task("Task 1"); - Task task2 = new Task("Task 2"); - - when(storageData.getTasks()).thenReturn(ImmutableList.of(task1, task2)); - todo = new TodoList(storage); - - assertEquals(2, todo.getTasks().size()); - assertTrue(todo.getTasks().contains(task1)); - assertTrue(todo.getTasks().contains(task2)); - } - - @Test - public void testCompletedEvent() throws Exception { - LocalDateTime start = LocalDateTime.now().minusHours(4); - - when(storageData.getTasks()).thenReturn(ImmutableList.of( - TaskBuilder.name("Test task") - .event(start, start.plusHours(1)) - .build() - )); - todo = new TodoList(storage); - - Thread.sleep(5); - assertTrue(todo.getTasks().get(0).isCompleted()); - } - - @Test - public void testAdd() throws Exception { - assertNotNull(todo.add("Test Task 1")); - assertEquals(1, todo.getTasks().size()); - assertFalse(getTask(0).isPinned()); - assertFalse(getTask(0).isCompleted()); - verify(storage).save(todo); - - assertNotNull(todo.add("Test Task 2")); - assertEquals(2, todo.getTasks().size()); - assertEquals("Test Task 1", getTask(0).getTitle()); - assertEquals("Test Task 2", getTask(1).getTitle()); - verify(storage, times(2)).save(todo); - } - - @Test - public void testCreatedAt() throws Exception { - LocalDateTime initialTime = LocalDateTime.now().minusSeconds(1); - - LocalDateTime firstCreatedAt = todo.add("Test Task 1").getCreatedAt(); - assertTrue(firstCreatedAt.isAfter(initialTime)); - - // Delay for a bit - Thread.sleep(1); - LocalDateTime secondCreatedAt = todo.add("Test Task 2").getCreatedAt(); - assertTrue(secondCreatedAt.isAfter(firstCreatedAt)); - } - - @Test - public void testUpdate() throws Exception { - final String DESCRIPTION = "Really long description blah blah blah"; - final String TITLE = "New title"; - - todo.add("Old Title"); - - // Check that updating string fields work - assertNotNull(todo.update(0, t -> { - t.setTitle(TITLE); - t.setDescription(DESCRIPTION); - })); - - assertEquals(TITLE, getTask(0).getTitle()); - assertEquals(DESCRIPTION, getTask(0).getDescription().get()); - verify(storage, times(2)).save(todo); - } - - @Test - public void testUpdateUUID() throws Exception { - ImmutableTask task = todo.add(TaskFactory.taskTitle()); - UUID taskUUID = task.getUUID(); - int hashCode = task.hashCode(); - - todo.update(0, mutableTask -> { - assertEquals(taskUUID, mutableTask.getUUID()); - assertEquals(hashCode, mutableTask.hashCode()); - assertEquals(mutableTask, task); - }); - } - - @Test - public void testUpdateBooleanFields() throws Exception { - todo.add("Test Task"); - - // Check that updating boolean fields work - assertNotNull(todo.update(0, t -> t.setPinned(true))); - assertTrue(getTask(0).isPinned()); - verify(storage, times(2)).save(todo); - - assertNotNull(todo.update(0, t -> t.setCompleted(true))); - assertTrue(getTask(0).isCompleted()); - verify(storage, times(3)).save(todo); - } - - @Test - public void testDeleting() throws Exception { - todo.add("Foo"); - todo.add("FooBar"); - todo.add("Bar"); - todo.add("Bar Bar"); - - // Delete the first task, then check that it has been deleted - ImmutableTask topTask = todo.getObservableList().get(0); - todo.delete(0); - assertFalse(todo.getTasks().contains(topTask)); - assertEquals(3, todo.getTasks().size()); - verify(storage, times(5)).save(todo); - - // Continue deleting the top task until the list is empty - todo.delete(0); - verify(storage, times(6)).save(todo); - - todo.delete(0); - verify(storage, times(7)).save(todo); - - todo.delete(0); - verify(storage, times(8)).save(todo); - - assertTrue(todo.getTasks().isEmpty()); - } - - @Test - public void testEditSetEmptyTitle() throws Exception { - todo.add("Task 1"); - - try { - todo.update(0, t -> t.setTitle("")); - fail(); - } catch (ValidationException e) { - // Check that the bad update didn't go through - assertEquals("Task 1", getTask(0).getTitle()); - } - } - - @Test - public void testEdit() throws Exception { - todo.add("Task 1"); - - try { - todo.update(0, t -> { - t.setStartTime(LocalDateTime.now()); - t.setTitle("Title changed"); - }); - fail(); - } catch (ValidationException e) { - assertEquals("Task 1", getTask(0).getTitle()); - } - } - - @Test - public void testAddOnlyStartDate() throws Exception { - try { - todo.add("Task 1", t -> t.setStartTime(LocalDateTime.now())); - fail(); - } catch (ValidationException e) { - // Check that the new task is not added to the list - assertEquals(0, todo.getTasks().size()); - } - } - - @Test(expected = ValidationException.class) - public void testAddInvalidDateRange() throws Exception { - todo.add("Task 1", t -> { - t.setStartTime(LocalDateTime.now()); - t.setEndTime(LocalDateTime.now().minusHours(2)); - }); - } - - private ImmutableTask getTask(int index) { - return todo.getTasks().get(index); - } -} -``` -###### \src\test\java\seedu\todo\testutil\TaskFactory.java -``` java -public class TaskFactory { - private static final Faker faker = new Faker(new Locale("en", "SG")); - private static final Random random = new Random(); - - private static final List friends = ImmutableList.of( - "Govind", "Li Kai", "Louie", "Xien Dong", "Alex", "Jim" - ); - - private static final List locations = ImmutableList.of( - "LT15", "School of Computing", "Engineering Canteen", "Cafe Nowhere", - "Cinnamon College", "West Coast Plaza" - ); - - // For realism the test subject shall only have 5 modules - // And no Govind, he's not taking a double degree - private static final List modules = ImmutableList.of( - "MA1521", "CS2104", "GET1025", "CS1231", "CS2100"); - - private static final List tags = ImmutableList.of( - "urgent", "homework", "assignment", "tea_time", "pokemon_go", "to-do-12", - "sports", "dinner", "CS2103T", "physics", "chemistry", "econs", "math" - ); - - private static final List events = ImmutableList.of( - "Watch {sport} match with {friend}", - "Watch the next {superhero} movie with {friend}", - "Attend concert with {friend}" - ); - - private static final List tasks = ImmutableList.of( - "Finish {module} homework {number}", - "Do {module} assignment #{number}", - "Do {module} lab #{number}", - "Prepare for {module} test", - "Discuss {module} project with {friend}", - "Fix bug #{number}", - "Read new novel from {name}", - "Research on new ways to get {power}" - ); - - private static T choice(List choices) { - return choices.get(random.nextInt(choices.size())); - } - - private static String substitute(String format) { - return format - .replace("{friend}", choice(friends)) - .replace("{module}", choice(modules)) - .replace("{name}", faker.name().fullName()) - .replace("{sport}", faker.team().sport()) - .replace("{superhero}", faker.superhero().name()) - .replace("{power}", faker.superhero().power()) - .replace("{number}", Integer.toString(random.nextInt(6) + 1)); - } - - private static LocalDateTime randomDate() { - return TimeUtil.today() - .plusDays(random.nextInt(20)) - .plusHours(random.nextInt(12) + 9) - .plusMinutes(random.nextInt(3) * 15); - } - - public static String taskTitle() { - return substitute(choice(tasks)); - } - - public static String eventTitle() { - return substitute(choice(events)); - } - - public static ImmutableTask task() { - return TaskBuilder.name(taskTitle()).build(); - } - - public static ImmutableTask fullTask() { - return TaskBuilder.name(taskTitle()) - .description(faker.lorem().paragraph(2)) - .due(randomDate()) - .build(); - } - - public static ImmutableTask event() { - LocalDateTime start = randomDate(); - return TaskBuilder.name(eventTitle()) - .event(start, start.plusMinutes((random.nextInt(6) + 2) * 15)) - .build(); - } - - public static ImmutableTask fullEvent() { - LocalDateTime start = randomDate(); - return TaskBuilder.name(eventTitle()) - .event(start, start.plusMinutes((random.nextInt(6) + 2) * 15)) - .location(choice(locations)) - .build(); - } - - public static ImmutableTask random() { - switch (random.nextInt(4)) { - case 0: - return task(); - case 1: - return fullTask(); - case 2: - return event(); - default: - return fullEvent(); - } - } - - public static List list() { - List tasks = Lists.newArrayList(random()); - - while (random.nextInt(5) != 0) { - tasks.add(random()); - } - - return tasks; - } - -``` -###### \src\test\java\seedu\todo\testutil\TimeUtil.java -``` java -public class TimeUtil { - public static final LocalDateTime now = LocalDateTime.now(); - private static final String FORMAT_FULL_DATE = "d MMMM yyyy, h:mm a"; - - public static LocalDateTime today() { - return LocalDateTime.of(LocalDate.now(), LocalTime.of(0, 0)); - } - - public static LocalDateTime tomorrow() { - return LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(0, 0)); - } - -``` diff --git a/collated/A0139021U.md b/collated/A0139021U.md deleted file mode 100644 index 62e70978d920..000000000000 --- a/collated/A0139021U.md +++ /dev/null @@ -1,790 +0,0 @@ -# A0139021U -###### \src\main\java\seedu\todo\commons\events\ui\ShowPreviewEvent.java -``` java -/** - * An event requesting to view the help page. - */ -public class ShowPreviewEvent extends BaseEvent { - private List commandSummaries; - - public ShowPreviewEvent(List commandSummaries) { - this.commandSummaries = commandSummaries; - } - - public List getPreviewInfo() { - return commandSummaries; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - -} -``` -###### \src\main\java\seedu\todo\commons\util\StringUtil.java -``` java - /** - * Calculates the levenstein distance between the two strings and returns - * their closeness in a percentage score. - * @param s1 The first string - * @param s2 The second string - * @return The percentage score of their closeness - */ - public static double calculateClosenessScore(String s1, String s2) { - // empty string, not close at all - if (isEmpty(s1) || isEmpty(s2)) { - return 0d; - } - - s1 = s1.toLowerCase(); - s2 = s2.toLowerCase(); - int distance = StringUtils.getLevenshteinDistance(s1, s2); - double ratio = ((double) distance) / (Math.max(s1.length(), s2.length())); - return 100 - ratio * 100; - } -} -``` -###### \src\main\java\seedu\todo\logic\commands\CommandPreview.java -``` java - -/** - * Represents all relevant commands that will be used to show to the user. - */ -public class CommandPreview { - private static final int COMMAND_INDEX = 0; - private static final double CLOSENESS_THRESHOLD = 30d; - private List commandSummaries; - - public CommandPreview(String userInput) { - commandSummaries = filterCommandSummaries(userInput); - } - - public List getPreview() { - return commandSummaries; - } - - private List filterCommandSummaries(String input) { - List summaries = new ArrayList<>(); - - if (StringUtil.isEmpty(input)) { - return summaries; - } - - List inputList = Lists.newArrayList(Splitter.on(" ") - .trimResults() - .omitEmptyStrings() - .split(input.toLowerCase())); - - String command = inputList.get(COMMAND_INDEX); - - CommandMap.getCommandMap().keySet().parallelStream().filter(key -> - StringUtil.calculateClosenessScore(key, command) > CLOSENESS_THRESHOLD || key.startsWith(command)) - .forEach(key -> summaries.addAll(CommandMap.getCommand(key).getCommandSummary())); - - return summaries; - } -} -``` -###### \src\main\java\seedu\todo\logic\Logic.java -``` java - /** - * Receives the intermediate product of the command and sends a ShowPreviewEvent. - * @param input The intermediate input as entered by the user. - */ - void preview(String input); -} -``` -###### \src\main\java\seedu\todo\logic\TodoLogic.java -``` java - @Override - public void preview(String input) { - List listOfCommands = new CommandPreview(input).getPreview(); - EventsCenter.getInstance().post(new ShowPreviewEvent(listOfCommands)); - } -} -``` -###### \src\main\java\seedu\todo\model\task\ValidationTask.java -``` java -public class ValidationTask extends BaseTask implements MutableTask { - private static final String END_TIME = "endTime"; - private static final String TITLE = "title"; - private static final String ONLY_START_TIME_ERROR_MESSAGE = "You must define an ending time."; - private static final String TITLE_EMPTY_ERROR_MESSAGE = "Your title should not be empty."; - private static final String VALIDATION_ERROR_MESSAGE = "Your task is not in the correct format."; - private static final String START_AFTER_END_ERROR_MESSAGE = "No time travelling allowed! You've finished before you even start."; - - private ErrorBag errors = new ErrorBag(); - - private String title; - private String description; - private String location; - - private boolean pinned; - private boolean completed; - - private LocalDateTime startTime; - private LocalDateTime endTime; - - private Set tags = new HashSet<>(); - private LocalDateTime createdAt; - - public ValidationTask(String title) { - this.setTitle(title); - this.setCreatedAt(); - } - - /** - * Constructs a ValidationTask from an ImmutableTask - */ - public ValidationTask(ImmutableTask task) { - this.setTitle(task.getTitle()); - this.setDescription(task.getDescription().orElse(null)); - this.setLocation(task.getLocation().orElse(null)); - this.setStartTime(task.getStartTime().orElse(null)); - this.setEndTime(task.getEndTime().orElse(null)); - this.setCompleted(task.isCompleted()); - this.setPinned(task.isPinned()); - - this.createdAt = task.getCreatedAt(); - this.uuid = task.getUUID(); - } - - /** - * Validates the task by checking the individual fields are valid. - */ - public void validate() throws ValidationException { - isValidTime(); - isValidTitle(); - errors.validate(VALIDATION_ERROR_MESSAGE); - } - - private void isValidTitle() { - if (StringUtil.isEmpty(title)) { - errors.put(TITLE, TITLE_EMPTY_ERROR_MESSAGE); - } - } - - /** - * Validates time. Only valid when - * 1) both time fields are not declared - * 2) end time is present - * 3) start time is before end time - */ - private void isValidTime() { - if (startTime == null && endTime == null) { - return; - } else if (endTime == null) { - errors.put(END_TIME, ONLY_START_TIME_ERROR_MESSAGE); - } else if (startTime != null && startTime.isAfter(endTime)) { - errors.put(END_TIME, START_AFTER_END_ERROR_MESSAGE); - } - } - - /** - * Converts the validation task into an actual task for consumption. - * - * @return A task with observable properties - */ - public Task convertToTask() throws ValidationException { - validate(); - return new Task(this); - } - - @Override - public String getTitle() { - return title; - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } - - @Override - public Optional getLocation() { - return Optional.ofNullable(location); - } - - @Override - public Optional getStartTime() { - return Optional.ofNullable(startTime); - } - - @Override - public Optional getEndTime() { - return Optional.ofNullable(endTime); - } - - @Override - public boolean isPinned() { - return pinned; - } - - @Override - public boolean isCompleted() { - return completed; - } - - @Override - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - public LocalDateTime getCreatedAt() { return createdAt; } - - @Override - public UUID getUUID() { - return uuid; - } - - @Override - public void setTitle(String title) { - this.title = title; - } - - @Override - public void setDescription(String description) { - this.description = description; - } - - @Override - public void setLocation(String location) { - this.location = location; - } - - @Override - public void setPinned(boolean pinned) { - this.pinned = pinned; - } - - @Override - public void setCompleted(boolean completed) { - this.completed = completed; - } - - @Override - public void setStartTime(LocalDateTime startTime) { - this.startTime = startTime; - } - - @Override - public void setEndTime(LocalDateTime endTime) { - this.endTime = endTime; - } - - @Override - public void setTags(Set tags) { - this.tags = tags; - } - - public void setCreatedAt() { this.createdAt = LocalDateTime.now(); } -} -``` -###### \src\main\java\seedu\todo\storage\LocalDateTimeAdapter.java -``` java -public class LocalDateTimeAdapter extends XmlAdapter { - - @Override - public LocalDateTime unmarshal(String v) throws Exception { - return LocalDateTime.parse(v); - } - - @Override - public String marshal(LocalDateTime v) throws Exception { - return v.toString(); - } -} -``` -###### \src\main\java\seedu\todo\ui\controller\CommandController.java -``` java - /** - * Handles a key stroke from input and sends it to logic. Once logic sends back a preview, it will be - * processed by {@link #handleCommandResult(CommandResult)} - * @param keyCode key pressed by user - * @param userInput text as shown in input view - */ - private void handleInput(KeyCode keyCode, String userInput) { - switch (keyCode) { - case ENTER : // Submitting command - //Note: Do not execute an empty command. TODO: This check should be done in the parser class. - if (!StringUtil.isEmpty(userInput)) { - CommandResult result = logic.execute(userInput); - handleCommandResult(result); - } - break; - default : // Typing command, show preview - logic.preview(userInput); - errorView.hideCommandErrorView(); // Don't show error when previewing - break; - } - } - - /** - * Handles a CommandResult object, and updates the user interface to reflect the result. - * @param result produced by {@link Logic} - */ - private void handleCommandResult(CommandResult result) { - previewView.hidePreviewPanel(); - displayMessage(result.getFeedback()); - if (result.isSuccessful()) { - viewDisplaySuccess(); - } else { - viewDisplayError(result.getErrors()); - } - } - -``` -###### \src\main\java\seedu\todo\ui\UiManager.java -``` java - @Subscribe - private void handleShowHelpEvent(ShowHelpEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - mainWindow.getHelpView().displayCommandSummaries(event.getCommandSummaries()); - } - -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java - /** - * Sets {@link #commandTextField} to listen out for keystrokes. - * Once a keystroke is received, calls {@link KeyStrokeCallback} interface to process this command. - */ - public void listenToInput(KeyStrokeCallback listener) { - this.commandTextField.addEventHandler(KeyEvent.KEY_RELEASED, event -> { - KeyCode keyCode = event.getCode(); - String textInput = commandTextField.getText(); - - boolean isNonEssential = keyCode.isNavigationKey() || - keyCode.isFunctionKey() || - keyCode.isMediaKey() || - keyCode.isModifierKey(); - - if (!isNonEssential) { - listener.onKeyStroke(keyCode, textInput); - } - }); - } - -``` -###### \src\main\java\seedu\todo\ui\view\CommandInputView.java -``` java - /*Interface Declarations*/ - /** - * Defines an interface for controller class to receive a key stroke from this view class, and process it. - */ - public interface KeyStrokeCallback { - void onKeyStroke(KeyCode keyCode, String text); - } -} -``` -###### \src\test\java\guitests\CommandPreviewViewTest.java -``` java - -/** - * Test the preview function through the GUI. - * Note: - * Order-ness of the tasks is not tested. - * Invalid preview output is not tested. - */ -public class CommandPreviewViewTest extends TodoListGuiTest { - - @Test - public void testPreviewEmptyString() { - int expected = 0; - int actual = commandPreviewView.getRowsDisplayed(); - - assertEquals(expected, actual); - } - - @Test - public void testPreviewAddCommand() throws InterruptedException { - //Add a task - ImmutableTask task = TaskFactory.task(); - enterCommand(CommandGeneratorUtil.generateAddCommand(task)); - - int expected = 2; - int actual = commandPreviewView.getRowsDisplayed(); - - assertEquals(expected, actual); - } -} -``` -###### \src\test\java\guitests\guihandles\CommandPreviewViewHandle.java -``` java -public class CommandPreviewViewHandle extends GuiHandle { - /* Constants */ - public static final String PREVIEW_VIEW_GRID_ID = "#previewGrid"; - - /** - * Constructs a handle to the {@link CommandPreviewViewHandle} - * - * @param guiRobot {@link GuiRobot} for the current GUI test. - * @param primaryStage The stage where the views for this handle is located. - */ - public CommandPreviewViewHandle(GuiRobot guiRobot, Stage primaryStage) { - super(guiRobot, primaryStage, TestApp.APP_TITLE); - } - - /** - * Get the preview {@link GridPane} object. - */ - private GridPane getPreviewGrid() { - return (GridPane) getNode(PREVIEW_VIEW_GRID_ID); - } - - /** - * Get the number of rows that is displayed on this {@link #getPreviewGrid()} object. - */ - public int getRowsDisplayed() { - return getPreviewGrid().getChildren().size() / 2; - } -} -``` -###### \src\test\java\seedu\todo\commons\util\StringUtilTest.java -``` java - @Test - public void calculateClosenessScoreNull() { - double expected = 0d; - double outcome = StringUtil.calculateClosenessScore(null, null); - assertEquals(expected, outcome, 0d); - } - - @Test - public void calculateClosenessScoreEmptyString() { - double expected = 0d; - double outcome = StringUtil.calculateClosenessScore("", ""); - assertEquals(expected, outcome, 0d); - } - - @Test - public void calculateClosenessScoreSameString() { - double expected = 100d; - double outcome = StringUtil.calculateClosenessScore("test", "test"); - assertEquals(expected, outcome, 0d); - } - - @Test - public void calculateClosenessScoreDifferentString() { - double expected = 0d; - double outcome = StringUtil.calculateClosenessScore("test", "ioio"); - assertEquals(expected, outcome, 0d); - } - - @Test - public void calculateClosenessScoreSomewhatCloseAdd() { - double expected = 50d; - double outcome = StringUtil.calculateClosenessScore("add", "a"); - assertEquals(expected, outcome, 20d); - } - - @Test - public void calculateClosenessScoreSomewhatCloseComplete() { - double expected = 50d; - double outcome = StringUtil.calculateClosenessScore("complete", "Com"); - assertEquals(expected, outcome, 20d); - } -} -``` -###### \src\test\java\seedu\todo\logic\commands\CommandPreviewTest.java -``` java -public class CommandPreviewTest { - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Before - public void setUpPreview() throws Exception { - Set mockCommands = Sets.newHashSet("add", "delete"); - } - - @Test - public void testFilterAdd() throws Exception { - // TODO: find way to mock static methods - List expected = CommandMap.getCommand("add").getCommandSummary(); - List actual = new CommandPreview("add").getPreview(); - assertTrue(isShallowCompareCommandSummaries(expected, actual)); - } - - @Test - public void testFilterEmptyString() throws Exception { - List expected = new ArrayList<>(); - List actual = new CommandPreview("").getPreview(); - assertEquals(expected, actual); - } - - private boolean isShallowCompareCommandSummaries(List list, List otherList) { - if (list.size() != otherList.size()) { - return false; - } - for (int i = 0; i < list.size(); i++) { - CommandSummary summary = list.get(i); - CommandSummary otherSummary = list.get(i); - - boolean isEqual = summary.arguments.equals(otherSummary.arguments) && - summary.command.equals(otherSummary.command) && - summary.scenario.equals(otherSummary.scenario); - - if (!isEqual) { - return false; - } - } - return true; - } -} -``` -###### \src\test\java\seedu\todo\model\task\ValidationTaskTest.java -``` java -public class ValidationTaskTest { - private ValidationTask task; - - @Before - public void setUp() throws Exception { - task = new ValidationTask("Test Task"); - } - - @Test - public void testTaskString() { - assertEquals("Test Task", task.getTitle()); - } - - @Test - public void testValidateTaskNoTime() throws ValidationException { - task.validate(); - } - - @Test(expected = ValidationException.class) - public void testValidateEmptyStringTitle() throws ValidationException { - task.setTitle(""); - task.validate(); - } - - @Test - public void testValidateTitle() throws ValidationException { - String testTitle = "test"; - task.setTitle(testTitle); - task.validate(); - assertEquals(task.getTitle(), testTitle); - } - - @Test - public void testValidateTaskTime() throws ValidationException { - LocalDateTime startTime = LocalDateTime.of(1, 1, 1, 1, 1); - LocalDateTime endTime = LocalDateTime.of(1, 1, 1, 1, 2); - - task.setStartTime(startTime); - task.setEndTime(endTime); - - task.validate(); - } - - @Test(expected = ValidationException.class) - public void testValidateTaskOnlyStartTime() throws ValidationException { - LocalDateTime startTime = LocalDateTime.of(1, 1, 1, 1, 1); - task.setStartTime(startTime); - task.validate(); - } - - @Test - public void testValidateTaskOnlyEndTime() throws ValidationException { - LocalDateTime endTime = LocalDateTime.of(1, 1, 1, 1, 1); - task.setEndTime(endTime); - task.validate(); - } - - @Test(expected = ValidationException.class) - public void testValidateTaskStartTimeBeforeEnd() throws ValidationException { - LocalDateTime startTime = LocalDateTime.of(1, 1, 1, 1, 2); - LocalDateTime endTime = LocalDateTime.of(1, 1, 1, 1, 1); - - task.setStartTime(startTime); - task.setEndTime(endTime); - - task.validate(); - } - - @Test - public void testConvertToTask() throws ValidationException { - LocalDateTime startTime = LocalDateTime.of(1, 1, 1, 1, 1); - LocalDateTime endTime = LocalDateTime.of(1, 1, 1, 1, 2); - - task.setStartTime(startTime); - task.setEndTime(endTime); - - assertAllPropertiesEqual(task, task.convertToTask()); - } - - @Test(expected = AssertionError.class) - public void testConvertDifferentTask() throws ValidationException { - Task convertedTask = task.convertToTask(); - task.setPinned(true); - // task.setDescription("test"); - assertAllPropertiesEqual(task, convertedTask); - } - - @Test - public void testTaskImmutableTask() { - ValidationTask original = new ValidationTask("Mock Task"); - assertAllPropertiesEqual(original, new ValidationTask(original)); - - original = new ValidationTask("Mock Task"); - original.setStartTime(LocalDateTime.now()); - original.setEndTime(LocalDateTime.now().plusHours(2)); - assertAllPropertiesEqual(original, new Task(original)); - - original = new ValidationTask("Mock Task"); - original.setDescription("A Test Description"); - original.setLocation("Test Location"); - assertAllPropertiesEqual(original, new ValidationTask(original)); - } - - @Test - public void testTitle() { - task.setTitle("New Title"); - assertEquals("New Title", task.getTitle()); - } - - @Test - public void testDescription() { - assertFalse(task.getDescription().isPresent()); - - task.setDescription("A short description"); - assertEquals("A short description", task.getDescription().get()); - } - - @Test - public void testLocation() { - assertFalse(task.getLocation().isPresent()); - - task.setLocation("Some Test Location"); - assertEquals("Some Test Location", task.getLocation().get()); - } - - @Test - public void testPinned() { - assertFalse(task.isPinned()); - - task.setPinned(true); - assertTrue(task.isPinned()); - } - - @Test - public void testCompleted() { - assertFalse(task.isCompleted()); - - task.setCompleted(true); - assertTrue(task.isCompleted()); - } - - @Test - public void testLastUpdated() { - assertNotNull(task.getCreatedAt()); - task.setCreatedAt(); - assertEquals(LocalDateTime.now(), task.getCreatedAt()); - } - - @Test - public void testTags() throws ValidationException { - assertEquals(0, task.getTags().size()); - - Set tags = new HashSet<>(); - tags.add(new Tag("Hello")); - tags.add(new Tag("World")); - task.setTags(tags); - - assertEquals(2, task.getTags().size()); - // TODO: This should do more when we finalize how tags can be edited - } - - @Test - public void testGetUUID() { - assertNotNull(task.getUUID()); - } -} -``` -###### \src\test\java\seedu\todo\testutil\TaskBuilder.java -``` java -/** - * Builds a task for testing purposes. - */ -public class TaskBuilder { - - private Task task; - private boolean defaultTime = true; - - private static LocalDateTime now = LocalDateTime.now(); - - private TaskBuilder(String name) { - task = new Task(name); - } - - public static TaskBuilder name(String name) { - return new TaskBuilder(name); - } - - public TaskBuilder description(String description) { - task.setDescription(description); - return this; - } - - public TaskBuilder location(String location) { - task.setLocation(location); - return this; - } - - public TaskBuilder createdAt(LocalDateTime lastUpdated) { - defaultTime = false; - task.setCreatedAt(lastUpdated); - return this; - } - - public TaskBuilder completed() { - task.setCompleted(true); - return this; - } - - public TaskBuilder pinned() { - task.setPinned(true); - return this; - } - - public TaskBuilder due() { - return due(TimeUtil.tomorrow().plusHours(12)); - } - - public TaskBuilder due(LocalDateTime due) { - task.setEndTime(due); - return this; - } - - public TaskBuilder event() { - return event(TimeUtil.tomorrow().plusHours(12), TimeUtil.tomorrow().plusHours(14)); - } - - public TaskBuilder event(LocalDateTime start, LocalDateTime end) { - task.setStartTime(start); - task.setEndTime(end); - return this; - } - - public TaskBuilder tagged(String ... tags) throws ValidationException { - Set setOfTags = new HashSet<>(); - for (String tag: tags) { - setOfTags.add(new Tag(tag)); - } - task.setTags(setOfTags); - return this; - } - - public Task build() { - // Push the time up by 1s to avoid colliding with previously created tasks - if (defaultTime) { - now = now.plusSeconds(1); - task.setCreatedAt(now); - } - - return task; - } - -} -``` diff --git a/collated/A0315805H.md b/collated/A0315805H.md deleted file mode 100644 index d8c7ff87b3e6..000000000000 --- a/collated/A0315805H.md +++ /dev/null @@ -1,239 +0,0 @@ -# A0315805H -###### \classes\production\main\style\DefaultStyle.css -``` css -/***TaskCardView Styles Start***/ -/*Default and Base*/ -.taskCard .label { - -fx-font-smoothing-type: lcd; -} - -.taskCard .titleLabel { - -fx-font-size: 16pt; - -fx-font-family: "Segoe UI Semibold"; -} - -.taskCard .descriptionLabel { - -fx-font-size: 12pt; - -fx-font-family: "Segoe UI"; -} - -.taskCard .footnoteLabel { - -fx-font-size: 10pt; - -fx-font-family: "Segoe UI Semilight"; - -fx-font-style: italic; -} - -.taskCard .highlightedBackground { - -fx-background-color: #00A4FF; -} - -.taskCard .lightBackground { - -fx-background-color: #d2d2d2; -} - -.taskCard .pinImage { - -fx-image: url("../images/star_gold.png"); - -fx-fit-to-width: 30px; - -fx-fit-to-height: 30px; -} - -.taskCard .dateImage { - -fx-image: url("../images/clock_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -.taskCard .locationImage { - -fx-image: url("../images/location_black.png"); - -fx-fit-to-width: 20px; - -fx-fit-to-height: 20px; -} - -/*Completed*/ -.completed .label{ - -fx-text-fill: #7F7F7F; -} - -.completed .titleLabel .text { - -fx-strikethrough: true; -} - -.completed .roundLabel { - -fx-background-color: #7F7F7F; - -fx-text-fill: #ffffff; -} - -.completed .pinImage { - -fx-image: url("../images/star_grey.png"); -} - -.completed .dateImage { - -fx-image: url("../images/clock_grey.png"); -} - -.completed .locationImage { - -fx-image: url("../images/location_grey.png"); -} - -/*Overdue*/ -.overdue { - -fx-background-color: #FF6464; -} - -.overdue .label { - -fx-text-fill: #FFFFFF; -} - -.overdue .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #FF6464; -} - -.overdue .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.overdue .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.overdue .locationImage { - -fx-image: url("../images/location_white.png"); -} - - -``` -###### \classes\production\main\style\DefaultStyle.css -``` css -/*Selected*/ -.selected { - -fx-background-color: #00A4FF; -} - -.selected .label { - -fx-text-fill: #FFFFFF; -} - -.selected .roundLabel { - -fx-background-color: #FFFFFF; - -fx-text-fill: #00A4FF; -} - -.selected .pinImage { - -fx-image: url("../images/star_white.png"); -} - -.selected .dateImage { - -fx-image: url("../images/clock_white.png"); -} - -.selected .locationImage { - -fx-image: url("../images/location_white.png"); -} - -/*Collapse*/ -.collapsed .collapsible { - visibility: collapse; - -fx-pref-height: 0px; - -fx-min-height: 0px; -} - -/***TaskCardView Styles End***/ - -/**Modern UI***/ -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ -.button { - -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; - -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; - -fx-background-insets: 0 0 0 0, 0, 1, 2; -} - -.button:hover { - -fx-background-color: #3a3a3a; -} - -.button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; -} - -.button:focused { - -fx-border-color: white, white; - -fx-border-width: 1, 1; - -fx-border-style: solid, segments(1, 1); - -fx-border-radius: 0, 0; - -fx-border-insets: 1 1 1 1, 0; -} - -.button:disabled, .button:default:disabled { - -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; -} - -.button:default { - -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; -} - -.button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); -} - -.dialog-pane { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.label.content { - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-text-fill: white; -} - -.dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); -} - -.dialog-pane:header *.header-panel *.label { - -fx-font-size: 18px; - -fx-font-style: italic; - -fx-fill: white; - -fx-text-fill: white; -} - -.scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; -} - -.scroll-bar .increment-button, .scroll-bar .decrement-button { - -fx-background-color: transparent; - -fx-padding: 0 0 0 0; -} - -.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { - -fx-shape: " "; -} - -.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { - -fx-padding: 1 8 1 8; -} - -.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { - -fx-padding: 8 1 8 1; -} -``` diff --git a/collated/docs/A0092382A.md b/collated/docs/A0092382A.md new file mode 100644 index 000000000000..278cbe23410a --- /dev/null +++ b/collated/docs/A0092382A.md @@ -0,0 +1,353 @@ +# A0092382A +###### \DeveloperGuide.html +``` html +

1 Introduction#

Welcome to the Uncle Jim's Discount To-do App!

+ +

This guide will teach you how to set up your development environment, explain the basic architecture of the application, teach you how to perform some common development tasks, as well as provide contact information for the times when you require additional help.

+

It also provides you with the tools for you to contribute further to this project in te spirit of open source software.

+

We hope you enjoy working with our product!

+``` +###### \DeveloperGuide.html +``` html +

3.5 Common modules#

Modules used by multiple components are in the seedu.todo.commons package.

+ +

3.5.1 Core#

The core module contains many important classes used throughout the application.

+ +
    +
  • UnmodifiableObservableList: Used by the UI component to listen to changes to the data through the Observer pattern.
  • +
  • Events: Used by components to marshal information around and inform other components that things have happened. (i.e. a form of Event Driven design)
  • +
  • LogsCenter: Used by many classes to write log messages to the App's log file.
  • +
+

3.5.2 Util#

The util module contains many different helper methods and classes used throughout the application. The things that can, and should be reused can be found in here.

+ +
    +
  • TimeUtil: Used by Ui to display time that is readable and user friendly.
  • +
  • XmlUtil: Used by storage to save and read .xml files.
  • +
+

3.5.3 Exceptions#

The exceptions module contains all common exceptions that will be used and thrown throughout the application.

+ +
    +
  • IllegalValueException: Used by Logic and Model internally to signal that a single value is invalid
  • +
  • ValidationException : Used by Logic and Model classes to signal that the command or model parsed is invalid.
  • +
+``` +###### \DeveloperGuide.md +``` md + +## Introduction + +Welcome to the Uncle Jim's Discount To-do App! + +This guide will teach you how to set up your development environment, explain the basic architecture of the application, teach you how to perform some common development tasks, as well as provide contact information for the times when you require additional help. + +It also provides you with the tools for you to contribute further to this project in te spirit of open source software. + +We hope you enjoy working with our product! + +``` +###### \DeveloperGuide.md +``` md + +### Common modules + +Modules used by multiple components are in the `seedu.todo.commons` package. + +#### Core + +The core module contains many important classes used throughout the application. + +* `UnmodifiableObservableList`: Used by the UI component to listen to changes to the data through the Observer pattern. +* `Events`: Used by components to marshal information around and inform other components that things have happened. (i.e. a form of _Event Driven_ design) +* `LogsCenter`: Used by many classes to write log messages to the App's log file. + +#### Util + +The util module contains many different helper methods and classes used throughout the application. The things that can, and should be reused can be found in here. + +* `TimeUtil`: Used by Ui to display time that is readable and user friendly. +* `XmlUtil`: Used by storage to save and read .xml files. + +#### Exceptions + +The exceptions module contains all common exceptions that will be used and thrown throughout the application. + +* `IllegalValueException`: Used by Logic and Model internally to signal that a single value is invalid +* `ValidationException` : Used by Logic and Model classes to signal that the command or model parsed is invalid. + +``` +###### \UserGuide.html +``` html +

3.2 Entering in dates#

Uncle Jim supports flexible date formats so you can enter dates in the formats specified below:

+ +
    +
  • +

    Formal dates

    +

    Dates in a standard numerical form such as DDMMYYYY or DD/MM/YY.

    +
    +

    Example

    +

    1978-01-28
    +29/10/94

    +
    +
  • +
  • +

    Relaxed dates

    +
  • +
+

It is not always necessary to write it in full formal date formats - Uncle Jim allows relaxed date formats as well. If you don't include the full date, we will use the current month or year

+
!!! example 
+    Oct 12 9pm
+    9 May
+
+
    +
  • +

    Relative dates

    +

    We also understand days of the week and even relative date and time.

    +
    +

    Example

    +

    next Thursday
    +tomorrow evening
    +in two days

    +
    +
  • +
+
+

Note

+
+

If no time is specified when entering a date, Uncle Jim will use the current time by default

+

3.3 Viewing help: help#

Format: help

+ +

Help allows you to have a quick reference of the commands in case you forgot the format to follow.

+
Help view
Figure 3. The help view for Uncle Jim
+ +``` +###### \UserGuide.html +``` html +

4 Other Features#

4.1 Visual Feedback#

+ +

Our user interface is very interactive and features a lot of visual feedback to the user. For example, for commands such as add, edit and show, you can see upon which task your action acts upon by viewing which task is highlighted.

+

Also, Uncle Jim distinguishes between tasks and events, not only by different tag colors but by also handling them differently. For example, if you have a task that is overdue, Uncle Jim will instantly highlight it red.

+

For events which are ongoing, they will be highlighted green.

+

For events which have passed, they will be faded, but we leave it to you to mark the events complete.

+

4.2 Intelligent Commandâ„¢#

As you get more advanced with Uncle Jim, you might find typing the full command slightly cumbersome. To speed up your workflow you can type our just the first few characters of a command, because our Intelligent Commandâ„¢ system can recognise commands even if you do not type them out fully.

+ +
+

Example

+
+
com1
+
Marks the first task on the current list as complete. Our system recognises it as the complete command
+
+
+

If the commands are ambiguous, our system will ask you for clarification of which command you wish to execute.

+

For example, typing just e could refer to edit or exit so a message as shown below will be displayed to you.

+
Feedback when an ambiguous command is entered
Figure 11. The app will prompt you if it cannot figure out the command you are entering
+ +

4.3 Command Previewâ„¢#

We also understand that it takes time for new users to remember all the commands. As such, we have a CommandPreviewâ„¢ which tells you the parameters of the command you have to enter. For example, if you type ad the system will process your keystrokes and display to you what commands it thinks you are going to execute and displays them as shown below:

+ +
CommandPreviewâ„¢
Figure 12. No need for remembering commands when Uncle Jim does the remembering for you.
+ +

5 FAQ#

Q: Is my data secure?
+A: Your data is stored locally on your hard drive. So, your data is as secure as your hard drive. We do not have access to your To-do list.

+ +

Q: How do I back up my data?
+A: As your data is saved to the file that you specified, you can simply copy this file to a back up storage of your choice.

+

Q: How do I sync my data with multiple devices?
+A: Simply load the file to a cloud sync service like Dropbox or Google Drive, and all updates will be reflected automatically to all devices using the file.

+

Q: How do I pay for this project?
+A: Donations can be made via PayPal or Kashmi. Cash donations are fine too. Basically if you wish to donate we will find a way for us to receive your money.

+

6 Command Summary#

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandFormat
Helphelp
AddaddNAME [/d DEADLINE or START END] [/m DESCRIPTION] [/p] [/t TAG 1, TAG 2...]
Clearclear
DeletedeleteINDEX
CompletecompleteINDEX or /all
PinpinINDEX
ShowshowINDEX
EditeditINDEX [NAME] [/d DEADLINE or START END] [/m DESCRIPTION] [/p]
TagtagINDEX TAG 1, [TAG 2...] or [INDEX] /r OLD NEW or [INDEX] /d TAG
FindfindKEYWORD [MORE KEYWORDS...] or /t TAG [MORE TAGS ...] or both
Undoundo
Redoredo
LoadloadFILENAME
Savesave[FILENAME]
ViewviewVIEW
+ + + + +``` +###### \UserGuide.md +``` md +### Entering in dates + +Uncle Jim supports flexible date formats so you can enter dates in the formats specified below: + +* **Formal dates** + + Dates in a standard numerical form such as DDMMYYYY or DD/MM/YY. + + !!! example + 1978-01-28 + 29/10/94 + +* **Relaxed dates** + + It is not always necessary to write it in full formal date formats - Uncle Jim allows relaxed date formats as well. If you don't include the full date, we will use the current month or year + + !!! example + Oct 12 9pm + 9 May + + +* **Relative dates** + + We also understand days of the week and even relative date and time. + + !!! example + next Thursday + tomorrow evening + in two days + +!!! note If no time is specified when entering a date, Uncle Jim will use the current time by default + + +### Viewing help: **`help`** + +Format: **`help`** + +Help allows you to have a quick reference of the commands in case you forgot the format to follow. + +Help view + +
The help view for Uncle Jim
+ +``` +###### \UserGuide.md +``` md + +## Other Features + +### Visual Feedback + +Our user interface is very interactive and features a lot of visual feedback to the user. For example, for commands such as add, edit and show, you can see upon which task your action acts upon by viewing which task is highlighted. + +Also, Uncle Jim distinguishes between tasks and events, not only by different tag colors but by also handling them differently. For example, if you have a task that is overdue, Uncle Jim will instantly highlight it red. + +For events which are ongoing, they will be highlighted green. + +For events which have passed, they will be faded, but we leave it to you to mark the events complete. + +### Intelligent Commandâ„¢ + +As you get more advanced with Uncle Jim, you might find typing the full command slightly cumbersome. To speed up your workflow you can type our just the first few characters of a command, because our Intelligent Commandâ„¢ system can recognise commands even if you do not type them out fully. + +!!! example + **`com`**`1` + : Marks the first task on the current list as complete. Our system recognises it as the complete command + +If the commands are ambiguous, our system will ask you for clarification of which command you wish to execute. + +For example, typing just `e` could refer to `edit` or `exit` so a message as shown below will be displayed to you. + +Feedback when an ambiguous command is entered + +
The app will prompt you if it cannot figure out the command you are entering
+ +### Command Previewâ„¢ + +We also understand that it takes time for new users to remember all the commands. As such, we have a CommandPreviewâ„¢ which tells you the parameters of the command you have to enter. For example, if you type `ad` the system will process your keystrokes and display to you what commands it thinks you are going to execute and displays them as shown below: + +CommandPreviewâ„¢ + +
No need for remembering commands when Uncle Jim does the remembering for you.
+ +## FAQ + +**Q**: Is my data secure? +**A**: Your data is stored locally on your hard drive. So, your data is as secure as your hard drive. We do not have access to your To-do list. + +**Q**: How do I back up my data? +**A**: As your data is saved to the file that you specified, you can simply copy this file to a back up storage of your choice. + +**Q**: How do I sync my data with multiple devices? +**A**: Simply load the file to a cloud sync service like Dropbox or Google Drive, and all updates will be reflected automatically to all devices using the file. + +**Q**: How do I pay for this project? +**A**: Donations can be made via PayPal or Kashmi. Cash donations are fine too. Basically if you wish to donate we will find a way for us to receive your money. + +## Command Summary + +Command | Format +-------- | :-------- +Help | **`help`** +Add | **`add`**` NAME [/d DEADLINE or START END] [/m DESCRIPTION] [/p] [/t TAG 1, TAG 2...]` +Clear | **`clear`** +Delete | **`delete`**` INDEX` +Complete | **`complete`**` INDEX` or `/all` +Pin | **`pin`**` INDEX` +Show | **`show`**` INDEX` +Edit | **`edit`**` INDEX [NAME] [/d DEADLINE or START END] [/m DESCRIPTION] [/p]` +Tag | **`tag`**` INDEX TAG 1, [TAG 2...]` or `[INDEX] /r OLD NEW` or `[INDEX] /d TAG` +Find | **`find`**` KEYWORD [MORE KEYWORDS...] or /t TAG [MORE TAGS ...] or both` +Undo | **`undo`** +Redo | **`redo`** +Load | **`load`**` FILENAME` +Save | **`save`**` [FILENAME]` +View | **`view`**` VIEW` + +[java]: https://www.java.com/en/download/ +[releases]: https://github.com/CS2103AUG2016-W10-C4/main/releases +``` diff --git a/collated/docs/A0135805H.md b/collated/docs/A0135805H.md new file mode 100644 index 000000000000..94cf527d8248 --- /dev/null +++ b/collated/docs/A0135805H.md @@ -0,0 +1,420 @@ +# A0135805H +###### \DeveloperGuide.html +``` html +

3.2 UI component#

UI Component UML diagram
Figure 5. The relation between the UI subcomponents
+ + +

The UI component handles the interaction between the user and application. If you refer to the architecture diagram in the above sections, you will realise that the UI is responsible for passing the textual command input from the user to the Logic for execution, and displaying the outcome of the execution to the user via the GUI.

+

Some of the GUI view classes are shown in the UI components diagram above. You may also find the implementation for the GUI in the package seedu.todo.ui.

+
Figure 6. Visual identification of view elements in the UI
+ +

API : Ui.java

+

Referring to the diagram above, you may notice that the UI mainly consists of a MainWindow. This is where most of the interactions between the user and the application happen here. The MainWindow contains several major view elements that are discussed in greater detail below:

+

3.2.1 Command Line Interface#

The UI aims to imitate the Command Line Interface (CLI) closely by accepting textual commands from users, and displaying textual feedback back to the users. The CLI consists of:

+ +
    +
  • CommandInputView - a text box for users to key in their commands
  • +
  • CommandPreviewView - a list of suggested commands from user inputs
  • +
  • CommandFeedbackView - a single line text that provides a response to their commands
  • +
  • CommandErrorView - a detailed breakdown of any erroneous commands presented with a table
  • +
+

These view classes are represented by the CommandXView class in the UML diagram above.

+

The CommandController class is introduced to link the three classes together, so they can work and communicate with each other. The CommandController:

+
    +
  1. Obtains a user-supplied command string from the CommandInputView
  2. +
  3. Submits the command string to Logic for execution
  4. +
  5. Receives a CommandResult from Logic after the execution
  6. +
  7. Displays the execution outcome via the CommandResult to the CommandFeedbackView and CommandErrorView
  8. +
+

In the meantime, the CommandPreviewView listens to any user input through an event bus ShowPreviewEvent located in the UiManager and displays the suggested commands as required.

+

3.2.2 To-do List Display#

A to-do list provides a richer representation of the tasks than the CLI to the users. The To-do List Display consists of:

+ +
    +
  • TodoListView - a ListView that displays a list of TaskCard
  • +
  • TaskCardView - an item in the TodoListView that displays details of a specific task
  • +
  • EmptyListView - a view overlapping the TodoListView which indicates that no task is displayed
  • +
+

Specifically, the TodoListView attaches an ObservableList of ImmutableTask from the Model and listens to any changes that are made to the ObservableList. If there are any modifications made, the TaskCard and TodoListView are updated automatically.

+

Also, the EmptyListView listens to the ObservableList of ImmutableTask if the list is empty. If the list is empty, the EmptyListView listens to the ObservableProperty of TaskViewFilter for the appropriate message to be displayed. You may try switching the views to see the messages yourself.

+

3.2.3 Additional Information#

The remaining view classes that are not mentioned are as follows:

+ +
    +
  • FilterBarView - shows a list of available views users can switch to, and indicates the view that the user is currently at
  • +
  • GlobalTagView - shows a global list of tags
  • +
  • HelpView - shows a list of commands that users can use
  • +
  • SearchStatusView - shows the information regarding a search that user has performed
  • +
+

All these view classes, including the MainWindow, inherit from the abstract UiPart class. You may load these view classes using the utility class UiPartLoader.

+

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 CommandInputView is specified in CommandInputView.fxml

+

Other than through CommandResult and ObservableList, you may also invoke changes to the GUI outside the scope of UI components by raising an event. UiManager will then call specific view elements to update the GUI accordingly. For example, you may show the HelpView by raising a ShowHelpPanel via the EventsCentre.

+``` +###### \DeveloperGuide.html +``` html +

5.6 GUI Testing#

As briefly described above, our GUI tests are System Tests. We crafted our GUI tests such that we are able to test every component of the application and the interactions between each component. This is done through the simulation of the user actions on the GUI itself. You may run these GUI tests in the package guitests to see the simulation in real life.

+ +

5.6.1 Structure#

If you look inside the guitests package, you may realise that we have grouped our test script files into two major locations:

+ +
    +
  • guitests - the actual test script that contains sequences of user actions
  • +
  • guitests.guihandles - view handles that provide direct access to the contents of the displayed view elements
  • +
+

5.6.2 Creating GUI Handles#

When you create a new view for your GUI, you may want to create a handle for your newly created view. Simply extend your view handle from GuiHandle class, and you may start working on your view handle.

+ +

The GuiHandle class allows you to search for your view elements by their id, and you may retrieve the contents of the view element just like how you do so for any JavaFX view nodes.

+

You may view sample codes from handles such as TaskCardViewHandle and CommandErrorViewHandle in the guitests.guihandles package.

+

5.6.3 Creating GUI Tests#

When you want to write an automated test script for simulating user actions, you may do so with a JUnit test files. You may refer to examples such as AddCommandTest and DeleteCommandTest located in the guitests package.

+ +

All our GUI JUnit tests are inherited from TodoListGuiTest. The TodoListGuiTest class provides useful methods based on the TestFX library that makes automation easier. You may refer to the TestFX documentation for more details.

+``` +###### \DeveloperGuide.md +``` md + +### UI component + +UI Component UML diagram + +
The relation between the UI subcomponents
+ +The UI component handles the interaction between the user and application. If you refer to the architecture diagram in the above sections, you will realise that the UI is responsible for passing the textual command input from the user to the `Logic` for execution, and displaying the outcome of the execution to the user via the GUI. + +Some of the GUI view classes are shown in the UI components diagram above. You may also find the implementation for the GUI in the package `seedu.todo.ui`. + + + +
Visual identification of view elements in the UI
+ +**API** : [`Ui.java`](../src/main/java/seedu/todo/ui/Ui.java) + +Referring to the diagram above, you may notice that the UI mainly consists of a `MainWindow`. This is where most of the interactions between the user and the application happen here. The `MainWindow` contains several major view elements that are discussed in greater detail below: + +#### Command Line Interface + +The UI aims to imitate the Command Line Interface (CLI) closely by accepting textual commands from users, and displaying textual feedback back to the users. The CLI consists of: + +- `CommandInputView` - a text box for users to key in their commands +- `CommandPreviewView` - a list of suggested commands from user inputs +- `CommandFeedbackView` - a single line text that provides a response to their commands +- `CommandErrorView` - a detailed breakdown of any erroneous commands presented with a table + +These view classes are represented by the `CommandXView` class in the UML diagram above. + +The `CommandController` class is introduced to link the three classes together, so they can work and communicate with each other. The `CommandController`: + +1. Obtains a user-supplied command string from the `CommandInputView` +2. Submits the command string to `Logic` for execution +3. Receives a `CommandResult` from `Logic` after the execution +4. Displays the execution outcome via the `CommandResult` to the `CommandFeedbackView` and `CommandErrorView` + +In the meantime, the `CommandPreviewView` listens to any user input through an event bus `ShowPreviewEvent` located in the `UiManager` and displays the suggested commands as required. + +#### To-do List Display + +A to-do list provides a richer representation of the tasks than the CLI to the users. The To-do List Display consists of: + +- `TodoListView` - a [`ListView`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html) that displays a list of `TaskCard` +- `TaskCardView` - an item in the `TodoListView` that displays details of a specific task +- `EmptyListView` - a view overlapping the `TodoListView` which indicates that no task is displayed + +Specifically, the `TodoListView` attaches an `ObservableList` of `ImmutableTask` from the `Model` and listens to any changes that are made to the `ObservableList`. If there are any modifications made, the `TaskCard` and `TodoListView` are updated automatically. + +Also, the `EmptyListView` listens to the `ObservableList` of `ImmutableTask` if the list is empty. If the list is empty, the `EmptyListView` listens to the `ObservableProperty` of `TaskViewFilter` for the appropriate message to be displayed. You may try switching the views to see the messages yourself. + +#### Additional Information + +The remaining view classes that are not mentioned are as follows: + +- `FilterBarView` - shows a list of available views users can switch to, and indicates the view that the user is currently at +- `GlobalTagView` - shows a global list of tags +- `HelpView` - shows a list of commands that users can use +- `SearchStatusView` - shows the information regarding a search that user has performed + +All these view classes, including the `MainWindow`, inherit from the abstract `UiPart` class. You may load these view classes using the utility class `UiPartLoader`. + +The UI component uses [JavaFX](http://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-overview.htm#JFXST784) 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 [`CommandInputView`](../src/main/java/seedu/todo/ui/view/CommandInputView.java) is specified in [`CommandInputView.fxml`](../src/main/resources/view/CommandInputView.fxml) + +Other than through `CommandResult` and `ObservableList`, you may also invoke changes to the GUI outside the scope of UI components by raising an event. `UiManager` will then call specific view elements to update the GUI accordingly. For example, you may show the `HelpView` by raising a `ShowHelpPanel` via the `EventsCentre`. + +``` +###### \DeveloperGuide.md +``` md + +### GUI Testing + +As briefly described above, our GUI tests are _System Tests_. We crafted our GUI tests such that we are able to test every component of the application and the interactions between each component. This is done through the simulation of the user actions on the GUI itself. You may run these GUI tests in the package `guitests` to see the simulation in real life. + +#### Structure + +If you look inside the `guitests` package, you may realise that we have grouped our test script files into two major locations: + +- `guitests` - the actual test script that contains sequences of user actions +- `guitests.guihandles` - view handles that provide direct access to the contents of the displayed view elements + +#### Creating GUI Handles + +When you create a new view for your GUI, you may want to create a handle for your newly created view. Simply extend your view handle from `GuiHandle` class, and you may start working on your view handle. + +The `GuiHandle` class allows you to search for your view elements by their `id`, and you may retrieve the contents of the view element just like how you do so for any JavaFX view nodes. + +You may view sample codes from handles such as `TaskCardViewHandle` and `CommandErrorViewHandle` in the `guitests.guihandles` package. + +#### Creating GUI Tests + +When you want to write an automated test script for simulating user actions, you may do so with a JUnit test files. You may refer to examples such as `AddCommandTest` and `DeleteCommandTest` located in the `guitests` package. + +All our GUI JUnit tests are inherited from `TodoListGuiTest`. The `TodoListGuiTest` class provides useful methods based on the [TestFX](https://github.com/TestFX/TestFX) library that makes automation easier. You may refer to the [TestFX](https://github.com/TestFX/TestFX) documentation for more details. + +``` +###### \UserGuide.html +``` html +

1 Introduction#

In today's hectic society, our lives feel like a never-ending procession of tasks, deadlines, events and anniversaries to keep up with. Tracking these daily activities on a to-do list can be daunting. Many task management applications today have too many buttons that you have to click through just to add a task, and user interfaces that are so cumbersome it is hard for you to make it a habit to use.

+ +

Ever wished for a tool that can manage all your daily activities in distinct categories, and suggest to you which one you want to complete first? Well, look no further as Uncle Jim's Discount To-do List is here to save your day.

+

Uncle Jim's Discount To-do List (Uncle Jim in short) is a revolutionary mouse-free personal task manager that helps you to keep track of your daily activities through the power of your keyboard. Gone are the days when you had to click through several pages of menus just to add a simple task to your schedule. Our command line interface is not only flexible but remarkably easy to use. Just type the command and hit enter!

+

Moreover, we know that you understand your activities better. So Uncle Jim allows you to create your very own categories to organise your activities. Uncle Jim is also capable of managing both tasks and events so you don't have to use two different applications to be productive. Our unique product will intelligently sieve out urgent deadlines and serve up reminders for you, so you will no longer overlook another significant activity.

+

Sounds exciting? Then let's get started!

+``` +###### \UserGuide.html +``` html +

3.9 Managing tags: tag#

Format:
+tag
+tagINDEX TAG1, TAG2, ...
+tag[INDEX] /r OLD NEW
+tag[INDEX] /d TAG

+ +

You can use tags to organize your tasks easily. Each task may have up to 5 tags. Tags are alpha-numeric and case-insensitive, and you can use hyphens "-" and underscores "_" in your tags too. You can use the tag command to add, edit or delete tags from one or all of the tasks. Here are some of the examples of what you can do with the tag command:

+

3.9.1 Viewing all tags used so far#

Sometimes you might not want to reuse tags you have used for other tasks previously. To check what tags are already used, simply type tag with no flags, and Uncle Jim will show you all tags that are used in the app.

+ +
Global Tag List
Figure 7. All your tags in one place
+ +

3.9.2 Adding tags to a task#

If you wish to add tags to a task, simply type tag with the INDEX and the tag names you wish to add to a task. This does not overwrite any existing tags on a task:

+ +
+

Example

+
+
tag1 cs2103T
+
Adds the cs2103T tag to the first task on the list
+
+
+

3.9.3 Editing tags#

You can rename tags for either a single task, or all tasks using /r:

+ +
+

Example

+
+
tag/r Business Pleasure
+
Renames the Business tag to Pleasure
+
tag1 /r Business Pleasure
+
Renames the Business tag to Pleasure only in task 1.
+
+
+
+

Note

+

Note that without the INDEX parameter, doing this will affect all tasks tagged with the specified tag, such as Business in the example above.

+
+

3.9.4 Deleting tags#

You can also remove a tag from either a single task, or all tasks at once with /d:

+ +
+

Example

+
+
tag1 /d important
+
Removes the important tag from the first task
+
tag/d important
+
Removes the important tag from all tasks. Remember you can always undo if you make a mistake.
+
+
+
+

Keep it simple!

+

Tags are only really necessary if you have a lot of tasks. Uncle Jim works just as well even if you don't use tags. For maximum productivity, keep your tags short and simple, and keep them broad so that you can apply them to many tasks. In this manner, we have imposed a maximum of five tags per task.

+
+

3.10 Showing details of a task: show#

Format: showINDEX

+ +

Descriptions of tasks and events are hidden by default. In order to display them, you can use this command to toggle between the expanded version of a task and its compact form, as shown below:

+
Expanding a task for more details
Figure 8. Get details of any task you need easily
+ +

3.11 Switching views: view#

Format: viewVIEW

+ +

You can drill down into your To-do List and see specific tasks, such as your incomplete tasks or today's schedule, using the view command:

+
+

Example

+
+
viewcompleted
+
Show completed tasks only
+
+
+
View of completed tasks
Figure 9. Find important and soon overdue tasks
+ +

You can also use the underlined character in the view's name as the shortcut when switching views.

+
+

Example

+
+
viewi
+
Show incomplete tasks only, since i is the underlined character as shown in the image above.
+
+
+

3.12 Finding tasks: find#

Format: findKEYWORD [MORE KEYWORDS] [/t TAGS]

+ +

You can search for tasks using the find command. The search is case insensitive and the order of the keywords does not matter. Add the /t flag allows you to specifically search for those tags.

+
+

Note

+

Keywords are separated by spaces, not comma

+
+
+

Example

+
+
findGithub
+
Returns tasks with Fix Github Issue #119
+
find/t code
+
Returns any task tagged with code in the tags
+
findgit development code
+
Returns any task with either git, development, or code in the title, such as + Fix the app bug in Github #119, Watch Tutorial on iOS development, Improve code quality with SLAP and so on and so forth.
+
+
+
Find results view
Figure 10. How the above search result using `find` would look like.
+ +

3.13 Exiting the app: exit#

Format: exit

+ +

If you wish to exit the program, simply type exit.

+``` +###### \UserGuide.md +``` md + +## Introduction + +In today's hectic society, our lives feel like a never-ending procession of tasks, deadlines, events and anniversaries to keep up with. Tracking these daily activities on a to-do list can be daunting. Many task management applications today have too many buttons that you have to click through just to add a task, and user interfaces that are so cumbersome it is hard for you to make it a habit to use. + +Ever wished for a tool that can manage all your daily activities in distinct categories, and suggest to you which one you want to complete first? Well, look no further as Uncle Jim's Discount To-do List is here to save your day. + +Uncle Jim's Discount To-do List (Uncle Jim in short) is a revolutionary mouse-free personal task manager that helps you to keep track of your daily activities through the power of your keyboard. Gone are the days when you had to click through several pages of menus just to add a simple task to your schedule. Our command line interface is not only flexible but remarkably easy to use. Just type the command and hit enter! + +Moreover, we know that you understand your activities better. So Uncle Jim allows you to create your very own categories to organise your activities. Uncle Jim is also capable of managing both tasks *and* events so you don't have to use two different applications to be productive. Our unique product will intelligently sieve out urgent deadlines and serve up reminders for you, so you will no longer overlook another significant activity. + +Sounds exciting? Then let's get started! + +``` +###### \UserGuide.md +``` md + +### Managing tags: **`tag`** + +Format: +**`tag`** +**`tag`**` INDEX TAG1, TAG2, ...` +**`tag`**` [INDEX] /r OLD NEW` +**`tag`**` [INDEX] /d TAG` + +You can use tags to organize your tasks easily. Each task may have up to 5 tags. Tags are alpha-numeric and case-insensitive, and you can use hyphens "-" and underscores "\_" in your tags too. You can use the `tag` command to add, edit or delete tags from one or all of the tasks. Here are some of the examples of what you can do with the `tag` command: + +#### Viewing all tags used so far + +Sometimes you might not want to reuse tags you have used for other tasks previously. To check what tags are already used, simply type `tag` with no flags, and Uncle Jim will show you all tags that are used in the app. + +Global Tag List + +
All your tags in one place
+ + +#### Adding tags to a task + +If you wish to add tags to a task, simply type `tag` with the `INDEX` and the tag names you wish to add to a task. This does not overwrite any existing tags on a task: + +!!! example + **`tag`**` 1 cs2103T` + : Adds the `cs2103T` tag to the first task on the list + + +#### Editing tags + +You can rename tags for either a single task, or all tasks using `/r`: + +!!! example + **`tag`**` /r Business Pleasure` + : Renames the `Business` tag to `Pleasure` + + **`tag`**` 1 /r Business Pleasure` + : Renames the `Business` tag to `Pleasure` only in task 1. + +!!! note + Note that without the *INDEX* parameter, doing this will affect all tasks tagged with the specified tag, such as *Business* in the example above. + +#### Deleting tags + +You can also remove a tag from either a single task, or all tasks at once with `/d`: + +!!! example + **`tag`**` 1 /d important` + : Removes the `important` tag from the first task + + **`tag`**` /d important` + : Removes the `important` tag from **all** tasks. Remember you can always `undo` if you make a mistake. + +!!! note "Keep it simple!" + Tags are only really necessary if you have a *lot* of tasks. Uncle Jim works just as well even if you don't use tags. For maximum productivity, keep your tags short and simple, and keep them broad so that you can apply them to many tasks. In this manner, we have imposed a maximum of **five** tags per task. + +### Showing details of a task: **`show`** + + +Format: **`show`**` INDEX` + +Descriptions of tasks and events are hidden by default. In order to display them, you can use this command to toggle between the expanded version of a task and its compact form, as shown below: + +Expanding a task for more details + +
Get details of any task you need easily
+ + + +### Switching views: **`view`** + +Format: **`view`**` VIEW` + +You can drill down into your To-do List and see specific tasks, such as your incomplete tasks or today's schedule, using the `view` command: + +!!! example + **`view`**` completed` + : Show completed tasks only + + +View of completed tasks + +
Find important and soon overdue tasks
+ +You can also use the underlined character in the view's name as the shortcut when switching views. + +!!! example + **`view`**`i` + : Show incomplete tasks only, since `i` is the underlined character as shown in the image above. + + +### Finding tasks: **`find`** + +Format: **`find`**` KEYWORD [MORE KEYWORDS] [/t TAGS]` + +You can search for tasks using the `find` command. The search is case insensitive and the order of the keywords does not matter. Add the `/t` flag allows you to specifically search for those tags. + +!!! note + Keywords are separated by spaces, not comma + +!!! example + + **`find`**`Github` + : Returns tasks with ** Fix Github Issue #119 ** + + **`find`**` /t code` + : Returns any task tagged with **code** in the tags + + **`find`**` git development code` + : Returns any task with either **git**, **development**, or **code** in the title, such as + **Fix the app bug in Github #119**, **Watch Tutorial on iOS development**, **Improve code quality with SLAP** and so on and so forth. + +Find results view + +
How the above search result using `find` would look like.
+ + +### Exiting the app: **`exit`** + +Format: **`exit`** + +If you wish to exit the program, simply type `exit`. + +``` diff --git a/collated/docs/A0135805Hreused.md b/collated/docs/A0135805Hreused.md new file mode 100644 index 000000000000..458c07ca51af --- /dev/null +++ b/collated/docs/A0135805Hreused.md @@ -0,0 +1,17 @@ +# A0135805Hreused +###### \DeveloperGuide.html +``` html +

5.6.4 Headless GUI Testing#

Thanks to the 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 to learn how to run tests in headless mode.

+``` +###### \DeveloperGuide.md +``` md + +#### 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. + +``` diff --git a/collated/docs/A0135817B.md b/collated/docs/A0135817B.md new file mode 100644 index 000000000000..705b491a1d94 --- /dev/null +++ b/collated/docs/A0135817B.md @@ -0,0 +1,1480 @@ +# A0135817B +###### \build\convertor.py +``` py +files = { + "DeveloperGuide": { + "title": "Developer Guide", + "toc": [3, 4, 5, 15, 18, 19, 20, 22, 23, 27, 28, 29, 30], + }, + "UserGuide": { + "title": "User Guide", + "toc": [9, 10, 11, 22, 24, 35], + }, +} + +base = path.dirname(path.abspath(__file__)) +output_dir = path.join(base, "../") + +md = markdown.Markdown(extensions=[ + "markdown.extensions.extra", + "markdown.extensions.codehilite", + "markdown.extensions.admonition", + TocExtension(permalink='#'), + "markdown.extensions.sane_lists", +]) + +with open(path.join(base, "template.html"), encoding="utf-8") as f: + template = f.read() + +for filename, attrs in files.items(): + input_path = path.join(output_dir, filename + ".md") + output_path = path.join(output_dir, filename + ".html") + + with open(input_path, encoding="utf-8") as input: + source = input.read() + + html = md.reset().convert(source) + soup = BeautifulSoup(html, 'html.parser') + + # Addition figure captions + for i, figcaption in enumerate(soup.find_all('figcaption')): + # Wrap the figure and the caption in a
+ figure = figcaption.find_previous_sibling(True) + if not figure: + print("Cannot find figure for " + str(figcaption)) + continue + + if figure.name == 'p': + figure.name = 'figure' + else: + figure = figure.wrap(soup.new_tag('figure')) + figure.append(figcaption) + + # Give it a count + caption_count = soup.new_tag('strong') + caption_count.string = 'Figure {}. '.format(i + 1) + figcaption.insert(0, caption_count) + + # Adding numbering and no-page-break wrappers around headings + sections = [0, 0, 0] + for h in soup.find_all(['h2', 'h3', 'h4']): + # Number the heading + if h.name == 'h2': + sections = [sections[0] + 1, 0, 0] + number = '{} '.format(sections[0]) + elif h.name == 'h3': + sections[1] += 1 + sections[2] = 0 + number = '{}.{} '.format(*sections[0:2]) + elif h.name == 'h4': + sections[2] += 1 + number = '{}.{}.{} '.format(*sections) + + number_tag = soup.new_tag('span') + number_tag.string = number + h.insert(0, number_tag) + + # Wrap the tag + next_tag = h.find_next_sibling(True) + if not next_tag: + continue + no_break = h.wrap(soup.new_tag('div')) + no_break['class'] = 'no-page-break' + no_break.append(next_tag) + + # Add page numbers to toc + for i, a in enumerate(soup.select('.toc > ul > li > a')): + numbering = soup.new_tag('span') + if i >= len(attrs['toc']): + break + numbering.string = str(attrs['toc'][i]) + a.insert_after(numbering) + + output_content = template.format(title=attrs['title'], html=soup, classname=filename.lower()) + + with open(output_path, mode="w", encoding="utf-8") as output: + output.write(output_content) + + print("Converting {} to {}".format(input_path, output_path)) +``` +###### \build\template.html +``` html + + + + {title} | Uncle Jim's Discount Todo List App by Reminding Logical Apartment + + + + + + + + + +
+

Uncle Jim's Discount To-do List

+

{title}

+ {html} +
+ + +``` +###### \css\print.css +``` css +html { + font-size: 50%; +} + +.container { + width: 100%; + padding-left: 6rem; + orphans: 3; + widows: 3; +} + + +.toc > ul > li { + position: relative; + overflow: hidden; +} + +.toc span { + display: block; + float: right; + font-weight: bold; + z-index: -2; + padding: 0 1rem; + margin-right: -.5rem; + background: #fff; +} + +.toc > ul > li > a { + background: #fff; + padding-right: .5rem; +} + +.toc > ul > li:after { + position: absolute; + top: 0; + left: 3rem; + right: 2rem; + white-space: nowrap; + z-index: -1; + font-weight: bold; + content: + "· · · · · · · · · · · · · · · · · · · · · · · · " + "· · · · · · · · · · · · · · · · · · · · · · · · " + "· · · · · · · · · · · · · · · · · · · · · · · · " + "· · · · · · · · · · · · · · · · · · · · · · · · " + "· · · · · · · · · · · · · · · · · · · · · · · · " + "· · · · · · · · · · · · · · · · · · · · · · · · "; +} + +table, img, .admonition, .no-page-break { + page-break-inside: avoid; +} + +h2 { + font-size: 3rem; + page-break-before: always; +} + +h3, h4 { + page-break-after: avoid; +} + +h3 { + margin-top: 3rem; +} + +h2:before, h3:before, h4:before { + color: #ccc; +} + +abbr[title] { + border-bottom: 0; +} + +code { + border: 0; + padding: 0; + margin: 0; + font-size: 90%; +} + +a { + color: inherit; +} + +.userguide strong + code { + margin-left: .6rem; +} + +.userguide .admonition p code:first-child, +.userguide .admonition li code:first-child, +.userguide .admonition dt code:first-child { + margin-left: 0; +} + +a.print-url:after { + content: ' (' attr(href) ')'; + font-weight: normal; + color: #ccc; +} +``` +###### \css\styles.css +``` css +abbr[title] { + cursor: help; + border-bottom: 1px dotted; + text-decoration: none; +} + +pre { + font-size: 80%; + background: #eee; + border: 1px solid #ccc; + border-radius: .4rem; + padding: .7rem 1.5rem; + line-height: 1.3; +} + +/* Make the TOC tigher than normal lists */ +.toc ul { + list-style: none; + counter-reset: toc; + line-height: 1.4; +} + +.toc > ul { + padding-left: 0; +} + +.toc > ul > li > a { + font-weight: bold; +} + +.toc ul li:before { + display: inline-block; + width: 1.75rem; + margin-right: 0.5rem; + text-align: right; + counter-increment: toc; + content: counters(toc, '.') '. '; +} + +.toc > ul > li:before { + font-weight: bold; +} + +.toc ul li { + margin-bottom: .1rem; +} + +.toc ul li li:before { + width: 3rem; +} + +.toc ul li li li:before { + width: 3.75rem; +} + +.toc ul ul ul { + display: none; +} + + +.toc ul ul { + margin: .1rem 0 .5rem .25rem; +} + +.toc span { + display: none; +} + + +/* Headings */ + +h2 span, +h3 span, +h4 span { + display: inline-block; + position: absolute; + margin-left: -1rem; + color: #999; + transform: translateX(-100%); +} + +.headerlink { + padding: 0 .5rem; + text-decoration: none; + display: none; +} + + +h2:hover .headerlink, +h3:hover .headerlink, +h4:hover .headerlink { + display: inline-block; +} + +/* Admonition styles */ +.admonition { + padding: 1rem 2rem; + margin-bottom: 2rem; + border: 1px solid transparent; + border-radius: 4px; + font-size: 1.4rem; + line-height: 1.4; +} + +ul .admonition, +ol .admonition { + margin-top: 1.5rem; +} + +.admonition.example { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.admonition.note { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.admonition.warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} + +.admonition.danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.admonition .admonition-title { + font-weight: bold; + margin-bottom: .2rem; + font-size: 1.5rem; +} + +.admonition ul, +.admonition ol, +.admonition dl, +.admonition p { + margin: 0; +} + + +.admonition dl dd { + margin: 0 0 .75rem 0; +} + +.admonition dl dd:last-child { + margin-bottom: .5rem; +} + +/* Figures */ +figure { + margin: 2rem 0; +} + +figcaption { + margin-top: 1rem; + font-style: italic; +} + +/* User Guide styles */ +.userguide strong + code { + margin-left: -.6rem; + border-left: 0; +} + +.userguide .admonition code { + background: none; + border: 0; + font-size: 100%; + white-space: normal; +} + +.userguide .admonition dt { + font-weight: normal; +} + +.userguide .admonition p code:first-child, +.userguide .admonition li code:first-child, +.userguide .admonition dt code:first-child { + margin-left: -.4rem; +} + + +.userguide .admonition p code:first-child:before, +.userguide .admonition li code:first-child:before, +.userguide .admonition dt code:first-child:before { + content: '>> '; +} + +kbd { + display: inline-block; + padding: .3rem .5rem; + font-size: 85%; + color: #555; + vertical-align: middle; + background-color: #fcfcfc; + border: solid 1px #ccc; + border-bottom-color: #bbb; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #bbb; +} + +.text-faded { + color: #999; +} + +.text-green { + color: #689f38; +} + +.text-red { + color: #e53935; +} +``` +###### \DeveloperGuide.html +``` html + + + + Developer Guide | Uncle Jim's Discount Todo List App by Reminding Logical Apartment + + + + + + + + + +
+

Uncle Jim's Discount To-do List

+

Developer Guide

+ +``` +###### \DeveloperGuide.html +``` html +

The UI, Logic and Model components each define their API in an interface of the same name and is bootstrapped at launch by MainApp.

+

For example, the Logic component (see the class diagram given below) defines its API in the Logic.java interface and is implemented by TodoLogic.java class.

+
Figure 2. Example of a Logic class diagram exposing its API to other components
+ +
Figure 3. The interaction of major components in the application through a sequence diagram
+ +

You can see how the components interact with each other when the user issues a command by looking at the above diagram.

+

The diagram below shows how the EventsCenter reacts to a help command event, where the UI does not know or contain any business side logic.

+
Figure 4. A sequence diagram showing how EventsCenter work
+ +
+

Event Driven Design

+

Note how the event is propagated through the EventsCenter to 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.

+
+

The sections below will provide you with more details for each component.

+``` +###### \DeveloperGuide.html +``` html +

3.3 Logic component#

Figure 7. The relation between the Logic subcomponents
+ + +
Figure 8. Continuation of the relation between the Logic subcomponents
+ +

API : Logic.java

+

You can think of the logic component as the glue sitting between the UI and the data model, or as the controller in the traditional MVC pattern. All subcomponents in logic are stateless, which reduces complexity and makes it easier to reason about. Our application uses the thin controller/fat model design pattern, and therefore we delegate as much of the data manipulation and state handling to the model. Logic consists of three separate subcomponents, each of which also defines their API using interfaces or abstract classes:

+
    +
  • Parser - turns user input into command and arguments
  • +
  • Dispatcher - maps parser results to commands
  • +
  • Command - validates arguments and execute command
  • +
+

When the Logic component is instantiated, each of these subcomponents is injected via dependency injection. This allows them to be tested more easily as the dependencies can be replaced with stub implementations.

+

The flow of a command being executed is:

+
    +
  1. Logic parses the user input into a ParseResult object
  2. +
  3. The ParseResult is sent to the dispatcher which instantiates a new Command object representing the command the user called
  4. +
  5. Logic binds the model and arguments to the Command object and executes it
  6. +
  7. The command execution can affect the Model (e.g. adding a person), and/or raise events.
  8. +
+
Figure 9. The process of deleting a task within the Logic component
+ +

Given above is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call. See the implementation section below for the implementation details of the logic component.

+``` +###### \DeveloperGuide.html +``` html +

4 Implementation#

4.1 Logic#

+ +

See the Logic component architecture section for the high level overview of the Logic component.

+

4.1.1 Parser#

The TodoParser sub-component implements the Parser interface, which defines a single parse function that takes in the user input as a string and returns an object implementing the ParseResult interface. The implementing class for TodoParser can be found as an inner class inside TodoParser.

+ +

The parser tokenizes the user input by whitespace characters then splits it into three parts:

+
    +
  • Command string - the first word of the user input
  • +
  • Positional argument string - everything from the command to the first flag
  • +
  • Named argument Map<String, String> - a map of flags to values
  • +
+

For example, the command add The Milk /d tomorrow 3pm /p will produce

+
command: "add"
+positional: "The Milk" 
+named: 
+  d: "tomorrow 3pm"
+  p: "" # Empty String
+
+

This is then passed on to the dispatcher.

+

4.1.2 Dispatcher#

The TodoDispatcher sub-component implements the Dispatcher interface, which defines a single dispatch function. The dispatch function simply tries to find the command that has a matching name to the user input, instantiates and returns a new instance of the command.

+ +

4.1.3 Command#

All commands implement the BaseCommand abstract class, which provides argument binding and validation. If any error were found at the

+ +

If you need to do additional argument validation, you can also override the validateArgument command, which is run after all arguments have been set.

+

4.1.4 Arguments#

Command arguments are defined using argument objects. Representing arguments as objects have several benefits - it makes them declarative, it allows argument parsing and validation code to be reused across multiple commands, and the fluent setter allows each individual property to be set independently of each other. The argument object's main job is to convert the user input from string into the type which the command object can use, as well as contain information about the argument that the program can show to the user.

+ +

The generic type T represents the return type of the command argument. To implement a new argument type, extend the abstract base class Argument, then implement the setValue function. Remember to call the super class's setValue function so that the required argument check works.

+``` +###### \DeveloperGuide.html +``` html +

To use the logger in your code, simply include

+
private static final Logger logger = LogsCenter.getLogger(<YOUR CLASS>.class);
+
+

at the top of your class, and replace <YOUR CLASS> with the class the logger is used in.

+

4.3.1 Logging Levels#

+ + + + + + + + + + + + + + + + + + + + + + + + +
LevelUsed for
SEVERECritical problem detected which may possibly cause the termination of the application
WARNINGCan continue, but with caution
INFOInformation showing the noteworthy actions by the App
FINEDetails that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size
+ +``` +###### \DeveloperGuide.html +``` html +

5.3 Command Tests#

To write tests for commands,

+ +

5.4 Task Builder#

To create tasks quickly, you can use the TaskBuilder class to quickly create ImmutableTask objects. An additional benefit of the using the TaskBuilder is that the creation time of consecutively constructed task objects will be set one second apart - if you try to use the Task constructor you may get tasks created on the same millisecond, which will cause problems with sorting.

+ +

5.5 Task Factory#

To generate a lot of tasks all at once, you can use the TaskFactory class to get a large number of randomly generated tasks. Use this class instead of TaskBuilder if fine control over the content of each task is not needed, for instance when testing out storage or conducting GUI testing.

+ +``` +###### \DeveloperGuide.html +``` html +

6.3 Making a Release#

Here are the steps to create a new release.

+ +
    +
  1. Update the Version number in MainApp.java
  2. +
  3. Generate a JAR file using Gradle
  4. +
  5. Tag the repo with the version number. e.g. v0.1
  6. +
  7. Crete a new release using GitHub and upload the JAR file you created
  8. +
  9. Update README to link to the new release
  10. +
+

6.4 Managing Dependencies#

Our project depends on a number of third-party libraries. We use Gradle to automate our dependency management.

+ +

To add a new dependency, look for instructions on the project's documentation for Gradle configuration required. For most projects it will look something like this:

+
// https://mvnrepository.com/artifact/org.mockito/mockito-all
+compile group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta'
+
+

This should be copied into the dependencies section of the build.gradle file. If you wish to, you can split the version string out so that the shorter compile property can be used instead, but this is not strictly necessary.

+
project.ext {
+    // ... other variables 
+    mockitoVersion = '8.40.11'
+}
+
+dependencies {
+    // .. other dependencies
+    compile "org.mockito:mockito-all:mockitoVersion"
+}
+
+

After that rerun the Gradle build command and make sure the file has been edited properly, then right click on the project folder in Eclipse, select Gradle > Refresh Gradle Project. You should now be able to use the package in your code. Remember to inform all other team members to refresh their Gradle Project when the commit is merged back into master.

+
+

Add external dependencies with care

+

Be mindful of the impact external dependencies can have on project build time and maintenance costs. Adding an external package should always be discussed with the other project members in the issue tracker.

+
+

7 Documentation#

Our documentation and user guides are written in GitHub Flavor Markdown with a number of extensions including tables, definition lists and warning blocks that help enable richer styling options for our documentation. These extensions are documented on the Extensions page of the Python Markdown package, the package we use to help transform the Markdown into HTML. We use HTML because it allows greater flexibility in styling and is generally more user friendly than raw Markdown. To set up the script:

+ +
    +
  1. Make sure you have Python 3.5+ installed. Run python3 --version to check that it is installed correctly.
  2. +
  3. Install the dependencies - pip3 install markdown pygments beautifulsoup4
  4. +
  5. Run the script from the project root - python3 docs/build/converter.py
  6. +
+

7.1 Syntax Highlighting#

To get syntax highlighting for your code, use three backticks before and after your code without any additional indentation, and indicate the language on the opening backticks. This is also known as fenced code block.

+ +

For example,

+
public class YourCommand extends BaseCommand {
+    // TODO: Define additional parameters here
+    private Argument<Integer> index = new IntArgument("index").required();
+
+    @Override
+    protected Parameter[] getArguments() {
+        // TODO: List all command argument objects inside this array
+        return new Parameter[]{ index };
+    }
+}
+
+

is produced by

+
``` java
+public class YourCommand extends BaseCommand {
+    // TODO: Define additional parameters here
+    private Argument<Integer> index = new IntArgument("index").required();
+
+    @Override
+    protected Parameter[] getArguments() {
+        // TODO: List all command argument objects inside this array
+        return new Parameter[]{ index };
+    }
+}
+```
+
+

7.2 Admonition blocks#

To draw reader's attention to specific things of interest, use the admonition extension

+ +
+

Warning

+

This is an example of an admonition block

+
+

The syntax for this the block above is

+
!!! warning
+    This is an example of an admonition block
+
+

warning is the style of the box, and also used by default as the title. To add a custom title, add the title after the style in quotation marks.

+
!!! warning "This is a block with a custom title"
+    This is an example of an admonition block
+
+

The following styles are available

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StyleColorUsed for
noteBlueDrawing reader's attention to specific points of interest
warningYellowWarning the reader about something that may be harmful
dangerRedA stronger warning to the reader about things they should not do
exampleGreenShowing the reader an example of something, like a command in the user guide
+

7.3 Captions#

To add a caption to a table, image or a piece of code, use the <figcaption> HTML tag.

+ +
<img src="..." alt="...">
+
+<figcaption>
+  The sequence of function calls resulting from the user input 
+  <code>execute 1</code>
+</figcaption>
+
+

The document processor will automatically wrap the <figcaption> and the preceding element in a <figure> element and automatically add a count to it, producing the following code:

+
<figure>
+  <img src="..." alt="...">
+
+  <figcaption>
+    <strong>Figure 2.</strong>
+    The sequence of function calls resulting from the user input 
+    <code>execute 1</code>
+  </figcaption>
+</figure>
+
+

Note that Markdown is not processed inside HTML, so you must use HTML to write any additional inline markup you need inside the caption.

+``` +###### \DeveloperGuide.md +``` md + +The UI, Logic and Model components each define their API in an `interface` of the same name and is bootstrapped at launch by `MainApp`. + +For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and is implemented by `TodoLogic.java` class. + + + +
Example of a Logic class diagram exposing its API to other components
+ + + +
The interaction of major components in the application through a sequence diagram
+ +You can see how the components interact with each other when the user issues a command by looking at the above diagram. + +The diagram below shows how the `EventsCenter` reacts to a `help` command event, where the UI does not know or contain any business side logic. + + + +
A sequence diagram showing how EventsCenter work
+ +!!! note "Event Driven Design" + + Note how the event is propagated through the `EventsCenter` to `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. + +The sections below will provide you with more details for each component. + +``` +###### \DeveloperGuide.md +``` md + +### Logic component + + + +
The relation between the Logic subcomponents
+ + + +
Continuation of the relation between the Logic subcomponents
+ +**API** : [`Logic.java`](../src/main/java/seedu/todo/logic/Logic.java) + +You can think of the logic component as the glue sitting between the UI and the data model, or as the controller in the traditional MVC pattern. All subcomponents in logic are stateless, which reduces complexity and makes it easier to reason about. Our application uses the thin controller/fat model design pattern, and therefore we delegate as much of the data manipulation and state handling to the model. Logic consists of three separate subcomponents, each of which also defines their API using interfaces or abstract classes: + +- `Parser` - turns user input into command and arguments +- `Dispatcher` - maps parser results to commands +- `Command` - validates arguments and execute command + +When the Logic component is instantiated, each of these subcomponents is injected via dependency injection. This allows them to be tested more easily as the dependencies can be replaced with stub implementations. + +The flow of a command being executed is: + +1. `Logic` parses the user input into a `ParseResult` object +2. The `ParseResult` is sent to the dispatcher which instantiates a new `Command` object representing the command the user called +3. `Logic` binds the model and arguments to the `Command` object and executes it +4. The command execution can affect the `Model` (e.g. adding a person), and/or raise events. + + + +
The process of deleting a task within the Logic component
+ +Given above is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. See [the implementation section](#logic) below for the implementation details of the logic component. + +``` +###### \DeveloperGuide.md +``` md + +## Implementation + +### Logic + +See the [Logic component architecture](#logic-component) section for the high level overview of the Logic component. + +#### Parser + +The `TodoParser` sub-component implements the `Parser` interface, which defines a single `parse` function that takes in the user input as a string and returns an object implementing the `ParseResult` interface. The implementing class for `TodoParser` can be found as an inner class inside `TodoParser`. + +The parser tokenizes the user input by whitespace characters then splits it into three parts: + +- Command `string` - the first word of the user input +- Positional argument `string` - everything from the command to the first flag +- Named argument `Map` - a map of flags to values + +For example, the command `add The Milk /d tomorrow 3pm /p` will produce + +``` yaml +command: "add" +positional: "The Milk" +named: + d: "tomorrow 3pm" + p: "" # Empty String +``` + +This is then passed on to the dispatcher. + +#### Dispatcher + +The `TodoDispatcher` sub-component implements the `Dispatcher` interface, which defines a single `dispatch` function. The dispatch function simply tries to find the command that has a matching name to the user input, instantiates and returns a new instance of the command. + +#### Command + +All commands implement the `BaseCommand` abstract class, which provides argument binding and validation. If any error were found at the + +If you need to do additional argument validation, you can also override the `validateArgument` command, which is run after all arguments have been set. + +#### Arguments + +Command arguments are defined using argument objects. Representing arguments as objects have several benefits - it makes them declarative, it allows argument parsing and validation code to be reused across multiple commands, and the fluent setter allows each individual property to be set independently of each other. The argument object's main job is to convert the user input from string into the type which the command object can use, as well as contain information about the argument that the program can show to the user. + +The generic type `T` represents the return type of the command argument. To implement a new argument type, extend the abstract base class `Argument`, then implement the `setValue` function. Remember to call the super class's `setValue` function so that the `required` argument check works. + + +``` +###### \DeveloperGuide.md +``` md + +To use the logger in your code, simply include + +``` java +private static final Logger logger = LogsCenter.getLogger(.class); +``` + +at the top of your class, and replace `` with the class the logger is used in. + +#### Logging Levels + +Level | Used for +---------- | ------------------------------------------------- + `SEVERE` | Critical problem detected which may possibly cause the termination of the application + `WARNING` | Can continue, but with caution + `INFO` | Information showing the noteworthy actions by the App + `FINE` | Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size + +``` +###### \DeveloperGuide.md +``` md + +### Command Tests + +To write tests for commands, + +### Task Builder + +To create tasks quickly, you can use the `TaskBuilder` class to quickly create `ImmutableTask` objects. An additional benefit of the using the `TaskBuilder` is that the creation time of consecutively constructed task objects will be set one second apart - if you try to use the `Task` constructor you may get tasks created on the same millisecond, which will cause problems with sorting. + +### Task Factory + +To generate a lot of tasks all at once, you can use the `TaskFactory` class to get a large number of randomly generated tasks. Use this class instead of `TaskBuilder` if fine control over the content of each task is not needed, for instance when testing out storage or conducting GUI testing. + +``` +###### \DeveloperGuide.md +``` md + +### Making a Release + +Here are the steps to create a new release. + + 1. Update the `Version` number in `MainApp.java` + 2. Generate a JAR file [using Gradle](#creating-the-jar-file) + 3. Tag the repo with the version number. e.g. `v0.1` + 4. [Crete a new release using GitHub](https://help.github.com/articles/creating-releases/) and upload the JAR file you created + 5. Update `README` to link to the new release + +### Managing Dependencies + +Our project depends on a number of third-party libraries. We use Gradle to automate our dependency management. + +To add a new dependency, look for instructions on the project's documentation for Gradle configuration required. For most projects it will look something like this: + +``` gradle +// https://mvnrepository.com/artifact/org.mockito/mockito-all +compile group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta' +``` + +This should be copied into the `dependencies` section of the `build.gradle` file. If you wish to, you can split the version string out so that the shorter `compile` property can be used instead, but this is not strictly necessary. + +```gradle +project.ext { + // ... other variables + mockitoVersion = '8.40.11' +} + +dependencies { + // .. other dependencies + compile "org.mockito:mockito-all:mockitoVersion" +} +``` + +After that rerun the Gradle `build` command and make sure the file has been edited properly, then right click on the project folder in Eclipse, select `Gradle` > `Refresh Gradle Project`. You should now be able to use the package in your code. Remember to inform all other team members to refresh their Gradle Project when the commit is merged back into master. + +!!! warning "Add external dependencies with care" + Be mindful of the impact external dependencies can have on project build time and maintenance costs. Adding an external package should always be discussed with the other project members in the issue tracker. + +## Documentation + +Our documentation and user guides are written in [GitHub Flavor Markdown][gfm] with a number of extensions including tables, definition lists and warning blocks that help enable richer styling options for our documentation. These extensions are documented on the [Extensions page of the Python Markdown package][py-markdown], the package we use to help transform the Markdown into HTML. We use HTML because it allows greater flexibility in styling and is generally more user friendly than raw Markdown. To set up the script: + +1. Make sure you have Python 3.5+ installed. Run `python3 --version` to check that it is installed correctly. +2. Install the dependencies - `pip3 install markdown pygments beautifulsoup4` +3. Run the script from the project root - `python3 docs/build/converter.py` + +### Syntax Highlighting + +To get syntax highlighting for your code, use three backticks before and after your code without any additional indentation, and indicate the language on the opening backticks. This is also known as fenced code block. + +For example, + +``` java +public class YourCommand extends BaseCommand { + // TODO: Define additional parameters here + private Argument index = new IntArgument("index").required(); + + @Override + protected Parameter[] getArguments() { + // TODO: List all command argument objects inside this array + return new Parameter[]{ index }; + } +} +``` + +is produced by + + ``` java + public class YourCommand extends BaseCommand { + // TODO: Define additional parameters here + private Argument index = new IntArgument("index").required(); + + @Override + protected Parameter[] getArguments() { + // TODO: List all command argument objects inside this array + return new Parameter[]{ index }; + } + } + ``` + + +### Admonition blocks + +To draw reader's attention to specific things of interest, use the admonition extension + +!!! warning + This is an example of an admonition block + +The syntax for this the block above is + + !!! warning + This is an example of an admonition block + +`warning` is the style of the box, and also used by default as the title. To add a custom title, add the title after the style in quotation marks. + + + !!! warning "This is a block with a custom title" + This is an example of an admonition block + +The following styles are available + +Style | Color | Used for +-------- | -------- | -------------------------------------------------- +note | Blue | Drawing reader's attention to specific points of interest +warning | Yellow | Warning the reader about something that may be harmful +danger | Red | A stronger warning to the reader about things they should not do +example | Green | Showing the reader an example of something, like a command in the user guide + +### Captions + +To add a caption to a table, image or a piece of code, use the [`
`][figcaption] HTML tag. + +```html +... + +
+ The sequence of function calls resulting from the user input + execute 1 +
+``` + +The document processor will automatically wrap the `
` and the preceding element in a `
` element and automatically add a count to it, producing the following code: + +```html +
+ ... + +
+ Figure 2. + The sequence of function calls resulting from the user input + execute 1 +
+
+``` + +Note that Markdown is not processed inside HTML, so you must use HTML to write any additional inline markup you need inside the caption. + +``` +###### \UserGuide.html +``` html + + + + User Guide | Uncle Jim's Discount Todo List App by Reminding Logical Apartment + + + + + + + + + +
+

Uncle Jim's Discount To-do List

+

User Guide

+ +``` +###### \UserGuide.html +``` html +

3.3.1 Adding a task or event: add#

Format:
+addTASK NAME [/d DEADLINE] [/m DESCRIPTION] [/p] [/t TAG 1, TAG 2...]
+addEVENT NAME /d START END [/m DESCRIPTION] [/l LOCATION] [/p] [/t TAG 1, TAG 2...]

+ +

You can add new tasks or events to the To-do List using the add command.

+

Although the list of parameters above looks intimidating, all of them except the name of the task are optional. Tasks will be turned into events automatically if there are two dates specified under the /d flag.

+

Here are some common scenarios where you would use the various parameters:

+

3.3.2 Adding a task#

You can add a task by simply giving a name.

+ +
+

Example

+

addFinish up developer guide for CS2101

+
+
Example of adding a task
Figure 4. How the newly added task is displayed on Uncle Jim
+ +
+

Note

+

Try to keep titles short. If you need more details, do put them in the description.

+
+

3.3.3 Adding an event#

You can add events by specifying a start time and end time with the /d flag. Event locations can be specified by adding them with the /l flag.

+ +
+

Example

+

addMusic at the park /d 11 Dec 6pm to 8pm /l Botanic Gardens /p

+
+

3.3.4 Adding a deadline#

If you need something done by a specific time, add a deadline to your task by specifying a single date and time with the /d flag.

+ +
+

Example

+

addSubmit Project Proposal V0.0 /d 5 Oct 2359

+
+

3.3.5 Adding descriptions to a task#

If you need to add more details to a task, you can add them under the /m flag. Note that these descriptions will be hidden from default view, once the task is added. To learn how to see these details again refer to the show command.

+ +
+

Example

+

addDestroy the Earth /m Going to need a lot of TNT for this. Remember to get them on sale on Friday - 50% discount on bulk orders!

+
+

3.3.6 Pinning a task#

Have something you don't want to forget? These tasks or events can be pinned to the top of the list using the /p flag. See the pin command for more details.

+ +
+

Example

+

addMeet Li Kai at Friday Hacks! /d 21 Oct 6pm to 8pm /p

+
+

3.3.7 Organizing tasks using tags#

If you have a lot of tasks you can use tags to organize them. See the tag command for more detail.

+ +
+

Example

+

addFinish tutorial 6 /d 10 Oct /t CS2106, School

+
+

3.3.8 Parameter reference#

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FlagParameterUsed to
/dDEADLINESpecify a deadline for the task
/dSTART ENDSpecify the start and end time for the event
/mDESCRIPTIONAdd a long description to the task or event
/lLOCATIONAdd a location to the event
/p-Pins the task to the top of the list
/tTAG 1, TAG 2, ...Tags to help organize your tasks
+ +

3.4 Deleting a task: delete#

Format: deleteINDEX

+ +

This allows you to delete the task specified by the INDEX parameter. The index refers to the index number shown in the current view.

+
+

Note

+

If you accidentally deleted the wrong task, not to worry! You can undo the action later on. See the undo command for more information.

+
+
+

Example

+
+
delete2
+
Deletes the 2nd task on the list
+
findY2S1
+
delete1
+
Deletes the 1st task in the results of the findY2S1 command.
+
+
+``` +###### \UserGuide.html +``` html +

3.14 Undoing an action: undo#

Format: undo

+ +

If you make any mistake, simply typing undo to undo your last action.

+
+

Note

+

Only applies to commands which have made changes to the To-do list like add, edit and delete

+
+

3.15 Redoing an action: redo#

Format: redo

+ +

Similar to the undo command, you can redo your most recent action that you revoked as well.

+

3.16 Loading an existing data file: load#

Format: loadFILENAME

+ +

You can use load to load another .xml file into the application. This can be used to restore a backup or switch to different lists. You can also use this to manage different To-do lists, such as having one for home and another for work.

+
+

Example

+

loadmyDiscountTodo.xml

+
+

3.17 Changing the save location: save#

Format: save[FILENAME]

+ +

By default, to-do list data are saved in a file called discountTodo.xml in the data folder. You can change the save file by specifying the file path as the first argument when running the program, or by using the save command.

+

The save command will show you the location of the current save file if you use it without specifying a FILENAME.

+
+

Autosave

+

Your to-do list is saved automatically every time it is updated. There is no need to save manually after each edit.

+
+``` +###### \UserGuide.md +``` md + +#### Adding a task or event: **`add`** + +Format: +**`add`**` TASK NAME [/d DEADLINE] [/m DESCRIPTION] [/p] [/t TAG 1, TAG 2...]` +**`add`**` EVENT NAME /d START END [/m DESCRIPTION] [/l LOCATION] [/p] [/t TAG 1, TAG 2...]` + +You can add new tasks or events to the To-do List using the `add` command. + +Although the list of parameters above looks intimidating, all of them except the name of the task are optional. Tasks will be turned into events automatically if there are two dates specified under the `/d` flag. + +Here are some common scenarios where you would use the various parameters: + +#### Adding a task + +You can add a task by simply giving a name. + +!!! example + **`add`**` Finish up developer guide for CS2101` + +Example of adding a task + +
How the newly added task is displayed on Uncle Jim
+ +!!! note + Try to keep titles short. If you need more details, do put them in the [description](#adding-descriptions-to-a-task). + + +#### Adding an event + +You can add events by specifying a start time and end time with the `/d` flag. Event locations can be specified by adding them with the `/l` flag. + +!!! example + **`add`**` Music at the park /d 11 Dec 6pm to 8pm /l Botanic Gardens /p` + +#### Adding a deadline + +If you need something done by a specific time, add a deadline to your task by specifying a single date and time with the `/d` flag. + +!!! example + **`add`**` Submit Project Proposal V0.0 /d 5 Oct 2359` + +#### Adding descriptions to a task + +If you need to add more details to a task, you can add them under the `/m` flag. Note that these descriptions will be hidden from default view, once the task is added. To learn how to see these details again refer to [the `show` command](#showing-details-of-a-task-show). + +!!! example + **`add`**` Destroy the Earth /m Going to need a lot of TNT for this. Remember to get them on sale on Friday - 50% discount on bulk orders!` + +#### Pinning a task + +Have something you don't want to forget? These tasks or events can be pinned to the top of the list using the `/p` flag. See [the `pin` command](#pinning-a-task-pin) for more details. + +!!! example + **`add`**` Meet Li Kai at Friday Hacks! /d 21 Oct 6pm to 8pm /p` + +#### Organizing tasks using tags + +If you have a lot of tasks you can use tags to organize them. See [the `tag` command](#manage-tags-tag) for more detail. + +!!! example + **`add`**` Finish tutorial 6 /d 10 Oct /t CS2106, School` + + +#### Parameter reference + +Flag | Parameter | Used to +-----| ---------------- | ---------------------- +`/d` | `DEADLINE` | Specify a deadline for the task +`/d` | `START END` | Specify the start and end time for the event +`/m` | `DESCRIPTION` | Add a long description to the task or event +`/l` | `LOCATION` | Add a location to the event +`/p` | - | Pins the task to the top of the list +`/t` | `TAG 1, TAG 2, ...` | Tags to help organize your tasks + +### Deleting a task: **`delete`** + +Format: **`delete`**` INDEX` + +This allows you to delete the task specified by the `INDEX` parameter. The index refers to the index number shown in the current view. + +!!! note + If you accidentally deleted the wrong task, not to worry! You can undo the action later on. See [the undo command](#undoing-an-action-undo) for more information. + +!!! example + + **`delete`**` 2` + : Deletes the 2nd task on the list + + **`find`**` Y2S1` + **`delete`**` 1` + : Deletes the 1st task in the results of the **`find`**` Y2S1` command. + +``` +###### \UserGuide.md +``` md + +### Undoing an action: **`undo`** + +Format: **`undo`** + +If you make any mistake, simply typing `undo` to undo your last action. + +!!! note + Only applies to commands which have made changes to the To-do list like `add`, `edit` and `delete` + +### Redoing an action: **`redo`** + +Format: **`redo`** + +Similar to the `undo` command, you can redo your most recent action that you revoked as well. + +### Loading an existing data file: **`load`** + +Format: **`load`**` FILENAME` + +You can use `load` to load another `.xml` file into the application. This can be used to restore a backup or switch to different lists. You can also use this to manage different To-do lists, such as having one for home and another for work. + +!!! example + + **`load`**` myDiscountTodo.xml` + +### Changing the save location: **`save`** + +Format: **`save`**` [FILENAME]` + +By default, to-do list data are saved in a file called `discountTodo.xml` in the `data` folder. You can change the save file by specifying the file path as the first argument when running the program, or by using the `save` command. + +The `save` command will show you the location of the current save file if you use it without specifying a `FILENAME`. + +!!! note "Autosave" + Your to-do list is saved automatically every time it is updated. There is no need to save manually after each edit. + +``` diff --git a/collated/docs/A0135817Breused.md b/collated/docs/A0135817Breused.md new file mode 100644 index 000000000000..a54dc5c81486 --- /dev/null +++ b/collated/docs/A0135817Breused.md @@ -0,0 +1,1061 @@ +# A0135817Breused +###### \DeveloperGuide.html +``` html +

2.1.1 Importing the project into Eclipse#

+

Note

+

Ensure that you have installed the e(fx)clipse and buildship plugins as listed in the prerequisites above.

+
+ +
    +
  1. Click File > Import
  2. +
  3. Click Gradle > Gradle Project > Next > Next
  4. +
  5. Click Browse, then locate the project's directory
  6. +
  7. Click Finish
  8. +
+
+

Note

+
    +
  • If you are asked whether to 'keep' or 'overwrite' config files, choose to 'keep'.
  • +
  • Depending on your connection speed and server load, this step may take up to 30 minutes to finish + (This is because Gradle downloads library files from servers during the project set up process)
  • +
  • If Eclipse has changed any settings files during the import process, you can discard those changes.
  • +
+
+

2.2 Contributing#

We use the feature branch git workflow. Thus when you are working on a task, please remember to assign the relevant issue to yourself on the issue tracker and branch off from master. When the task is completed, do remember to push the branch to GitHub and create a new pull request so that the integrator can review the code. For large features that impact multiple parts of the code it is best to open a new issue on the issue tracker so that the design of the code can be discussed first.

+ +

Test driven development is encouraged but not required. If possible, all of your incoming code should have 100% accompanying tests - Coveralls will fail any incoming pull request which causes coverage to fall.

+

2.3 Coding Style#

We use the Java coding standard found at https://oss-generic.github.io/process/codingstandards/coding-standards-java.html.

+ +``` +###### \DeveloperGuide.html +``` html +

4.3 Logging#

We are using the java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

+ +
    +
  • The logging level can be controlled using the logLevel setting in the configuration file (See 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 to a log file.
  • +
  • The logs roll over at 5MB such that every log file is smaller than 5MB. Five log files are kept, after which the oldest will be deleted.
  • +
+``` +###### \DeveloperGuide.html +``` html +

4.4 Configuration#

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json)

+ +

5 Testing#

Tests can be found in the ./src/test/java folder.

+ +

5.1 In Eclipse#

    +
  • 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.
  • +
+ +
+

Note

+

If you are not using a recent Eclipse version (Neon or later), enable assertions in JUnit tests +as described in this Stack Overflow question (url: http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option).

+
+

5.2 Using Gradle#

See UsingGradle.md for how to run tests using Gradle.

+ +

We have two types of tests:

+
    +
  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. +
  3. +

    Non-GUI Tests - These are tests not involving the GUI. They include,

    +
      +
    1. Unit tests - targeting the lowest level methods/classes.
      + e.g. seedu.todo.commons.UrlUtilTest
    2. +
    3. Integration tests - that are checking the integration of multiple code units (those code units are assumed to be working).
      + e.g. seedu.todo.model.TodoModelTest
    4. +
    5. Hybrids of unit and integration tests. These tests are checking multiple code units as well as how the are connected together.
      + e.g. seedu.todo.logic.BaseCommandTest
    6. +
    +
  4. +
+``` +###### \DeveloperGuide.html +``` html +

6 Dev Ops#

6.1 Build Automation#

+ +

We use Gradle for build automation. Gradle handles project dependencies, build tasks and testing. If you have configured Eclipse by importing the project as shown in the setting up section Gradle should already be properly configured and can be executing from within Eclipse to build, test and package the project from the Run menu.

+

See the appendix Using Gradle for all of the details and Gradle commands.

+

6.2 Continuous Integration#

We use Travis CI to perform Continuous Integration on our projects. See UsingTravis.md for more details.

+ +``` +###### \DeveloperGuide.html +``` html +

8 Appendix A : User Stories#

Priorities: High (must have) - * , Medium (nice to have) - , Low (unlikely to have) - *

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PriorityAs a ...I want to ...So that I can...
***new usersee usage instructionsrefer to instructions when I forget how to use the app
***useradd a new task
***usermark a task as completeso I know which are the tasks are not complete.
***userdelete a taskremove entries that I no longer need
***useredit a taskchange or update details for the task
***userset a deadline for a tasktrack down deadlines of my tasks
***userset events with start and end dateskeep track of events that will happen
***userview taskssee all the tasks I have
***userview incomplete tasks onlyto know what are the tasks I have left to do.
***user with multiple computerssave the todo list file to a specific locationmove the list to other computers
**user with a lot tasksadd tags to my tasksorganize my tasks
**userset recurring tasksdo not need to repeatedly add tasks
**usersort tasks by various parametersorganize my tasks and locate a task easily
**userset reminders for a taskdo not need to mentally track deadlines
*userknow the number of tasks I have leftgauge how many tasks I have left to do.
*userbe notified about upcoming deadlines without opening the appso that I can receive timely reminders
+

9 Appendix B : Use Cases#

(For all use cases below, the System is the TodoApp and the Actor is the user, unless specified otherwise)

+ +

9.1 Adding an event#

MSS

+ +
    +
  1. User types out an event with start time, end time and location
  2. +
  3. TodoApp adds event with specified fields and saves it to disk
  4. +
+

Use case ends.

+

Extensions

+

1a. The task has no title

+
+

1a1. TodoApp shows an error message
+ Use case resumes at step 1

+
+

1b. The task's date field is empty

+
+

1b1. TodoApp creates a task with no start and end date
+ Use case resumes at step 2

+
+

1c. The task has a start time later than end time

+
+

1c1. TodoApp assumes the dates are inverted
+ Use case resumes at step 2

+
+

1d. The event's timing overlaps with an existing event's timing

+
+

1d1. TodoApp displays a warning to the user that he has another event at the same time
+ Use case resumes at step 2

+
+

9.2 Adding a task with deadline#

MSS

+ +
    +
  1. User enters a task while specifying a deadline for the task.
  2. +
  3. TodoApp creates new todo item with deadline specified and saves it to disk
  4. +
+

Use case ends.

+

Extensions

+

1a. The task has no title

+
+

1a1. TodoApp shows an error message
+ Use case resumes at step 1

+
+

1b. The task's date field is empty

+
+

1b1. TodoApp creates a task with no deadline
+ Use case ends

+
+

9.3 Adding a recurring task#

MSS

+ +
    +
  1. User enters a task with a recurring time period
  2. +
  3. TodoApp creates a new recurring todo item with the specified time period
  4. +
  5. At the start of the specified time period (eg. every week, month) TodoApp creates a copy of the original task for the user
  6. +
+

Use case ends.

+

Extensions

+

2a. The given recurring time period is invalid

+
+

2a1. TodoApp shows an error message
+ Use case resumes at step 1

+
+

9.4 Marking a task complete#

MSS

+ +
    +
  1. User requests to see a list of uncompleted tasks.
  2. +
  3. TodoApp shows a list of uncompleted tasks.
  4. +
  5. User marks complete a specific task in the list.
  6. +
  7. TodoApp marks the task as complete by striking through the task and saving its new state to disk
  8. +
+

Use case ends.

+

Extensions

+

1a. User uses another method to list tasks (e.g. search)

+
+

1a1. TodoApp shows the list of tasks requested
+ Use case resumes at step 2

+
+

2a. The list is empty

+
+

1a1. TodoApp informs the user the list is empty
+ Use case ends

+
+

3a. The given index is invalid

+
+

3a1. TodoApp shows an error message
+ Use case resumes at step 2

+
+

3b. The given index is a task which has already been completed

+
+

3b1. TodoApp informs the user the task has already been completed + Use case ends

+
+

9.5 Delete task#

MSS

+ +
    +
  1. User requests to delete a specific task from the list
  2. +
  3. TodoApp deletes the person
  4. +
+

Use case ends.

+

Extensions

+

1a. The given index is invalid

+
+

1a1. TodoApp shows an error message
+ Use case resumes at step 1

+
+

9.6 Viewing a specific tab (i.e. intelligent views)#

MSS

+ +
    +
  1. User requests to view specific tab
  2. +
  3. TodoApp shows a list of tasks under specific tab
  4. +
+

Use case ends.

+

Extensions

+

1a. User enters invalid view (eg. a view that doesn't exist )

+
+

1a1. TodoApp shows an error message
+ Use case ends

+
+

9.7 Finding for a task#

MSS

+ +
    +
  1. User searches for task with specific tag or fragmented title
  2. +
  3. TodoApp returns a list of tasks matching search fragment
  4. +
+

Use case ends.

+

Extensions

+

1a. User enters an invalid tag/search fragment

+
+

1a1. TodoApp returns an empty list
+ Use case ends

+
+

9.8 Editing a task#

    +
  1. User searches for specific task to edit
  2. +
  3. TodoApp returns list of tasks matching search query
  4. +
  5. User edits specific task on the list, changing any of its fields
  6. +
  7. TodoApp accepts changes, reflects them on the task and
  8. +
+ +

Extensions

+

2a. List returned is empty

+
+

Use case ends

+
+

3a. User enters invalid task index

+
+

3a1. TodoApp shows error message indicating invalid index
+Use case resumes at Step 2

+
+

3b. User enters invalid arguments to edit fields

+
+

3b1. TodoApp shows error message indicating invalid fields
+Use case resumes at Step 2

+
+

9.9 Pinning a task#

MSS

+ +
    +
  1. User searches for specific task to pin using the find command
  2. +
  3. TodoApp returns a list of tasks matching the search query
  4. +
  5. User selects a specific task to pin
  6. +
  7. TodoApp pins selected task and updates the storage file on disk
  8. +
+

Use case ends.

+

Extensions

+

2a. List returned by TodoApp is empty

+
+

Use case ends

+
+

3a. Selected task is already pinned

+
+

3a1. TodoApp unpins selected task + Use case ends

+
+

3b. User provides an invalid index

+
+

3b1. TodoApp shows an error message
+Use case resumes at Step 3

+
+

9.10 Undoing an action#

    +
  1. User carries out a mutating command (see glossary)
  2. +
  3. User finds they have made a mistake and instructs TodoApp to undo last action
  4. +
  5. TodoApp rolls back the todolist to the previous state and updates the stored todolist on disk
  6. +
+ +

Extensions

+

2a1. The user calls the undo command without having made any changes

+
+

2a1. TodoApp shows an error message
+Use case ends

+
+

10 Appendix C : Non Functional Requirements#

The project should -

+ +
    +
  1. work on any mainstream OS as long as it has Java 8 or higher installed.
  2. +
  3. use a command line interface as the primary input mode
  4. +
  5. have a customizable colour scheme.
  6. +
  7. be able to hold up to 100 todos, events and deadlines.
  8. +
  9. come with automated unit tests.
  10. +
  11. have competitive performance with commands being executed within 5 seconds of typing into the CLI
  12. +
  13. be open source.
  14. +
+

11 Appendix D : Glossary#

+
Mainstream OS
+
+

Windows, OS X

+
+
Task
+
+

A single todo task, deadline or item

+
+
Pinning
+
+

Marking a task with higher importance/priority than others. Pinned tasks will always appear first in any view.

+
+
Mutating Command
+
+

Any command which causes a change in the state of the the TodoApp (E.g. add, delete, edit, pin, complete)

+
+
+ +

12 Appendix E : Product Survey#

Basic Todo Lists e.g. Sticky Notes, Notepad

+ +

Very flexible and easy to use, but hard to organise tasks on them. Also, data can only be saved locally.

+

Online/Cloud Based Todo lists

+

Apps such as Google Calendar, Asana and Trello offer a wide range of effective features that help manage your To-do lists. However, most of them require heavy mouse usage and the constant context switching might break user concentration. Our target audience are users who prefer not use the mouse at all, and that makes some of these applications almost unusable. Also, it is hard to sync without a constant internet connection.

+

13 Appendix F: Using Gradle#

Gradle is a build automation tool for Java projects. It can automate build-related tasks such as

+ +
    +
  • Running tests
  • +
  • Managing library dependencies
  • +
  • Analyzing code for style compliance
  • +
  • Packaging code for release
  • +
+

The gradle configuration for this project is defined in the build script build.gradle.

+
+

Note

+

To learn more about gradle build scripts refer to Build Scripts Basics.

+
+

13.1 Running Gradle Commands#

To run a Gradle command, open a command window on the project folder and enter the Gradle command. Gradle commands look like this:

+ +
    +
  • On Windows :gradlew <task1> <task2> ... e.g. gradlew clean allTests
  • +
  • On Mac/Linux: ./gradlew <task1> <task2>... e.g. ./gradlew clean allTests
  • +
+
+

Note

+

If you do not specify any tasks, Gradlew will run the default tasks clean headless allTests coverage

+
+

13.2 Cleaning the Project#

clean - Deletes the files created during the previous build tasks (e.g. files in the build folder).
+e.g. ./gradlew clean

+ +
+

clean to force Gradle to execute a task

+

When running a Gradle task, Gradle will try to figure out if the task needs running at all. If Gradle determines that the output of the task will be same as the previous time, it will not run the task. For example, it will not build the JAR file again if the relevant source files have not changed since the last time the JAR file was built. If we want to force Gradle to run a task, we can combine that task with clean. Once the build files have been cleaned, Gradle has no way to determine if the output will be same as before, so it will be forced to execute the task.

+
+

13.3 Creating the JAR file#

shadowJar - Creates the addressbook.jar file in the build/jar folder, if the current file is outdated.
+e.g. ./gradlew shadowJar

+ +

To force Gradle to create the JAR file even if the current one is up-to-date, you can 'clean' first.
+e.g. ./gradlew clean shadowJar

+
+

Why do we create a fat JAR?

+

If we package only our own class files into the JAR file, it will not work properly unless the user has all the other JAR files (i.e. third party libraries) our classes depend on, which is rather inconvenient. Therefore, we package all dependencies into a single JAR files, creating what is also known as a fat JAR file. To create a fat JAR file, we use the shadow jar Gradle plugin.

+
+

13.4 Running Tests#

allTests - Runs all tests.

+ +

guiTests - Runs all tests in the guitests package

+

nonGuiTests - Runs all non-GUI tests in the seedu.address package

+

headless - Sets the test mode as headless. The mode is effective for that Gradle run only so it should be combined with other test tasks.

+

Here are some examples:

+
    +
  • ./gradlew headless allTests -- Runs all tests in headless mode
  • +
  • ./gradlew clean nonGuiTests -- Cleans the project and runs non-GUI tests
  • +
+

13.5 Updating Dependencies#

There is no need to run these Gradle tasks manually as they are called automatically by other relevant Gradle tasks.

+ +

compileJava - Checks whether the project has the required dependencies to compile and run the main program, and download any missing dependencies before compiling the classes.

+

See build.gradle > allprojects > dependencies > compile for the list of dependencies required.

+

compileTestJava - Checks whether the project has the required dependencies to perform testing, and download any missing dependencies before compiling the test classes.

+

See build.gradle > allprojects > dependencies > testCompile for the list of dependencies required.

+
+ + +``` +###### \DeveloperGuide.md +``` md + +#### Importing the project into Eclipse + +0. Fork this repository, and clone the fork to your computer with Git. +1. Open Eclipse +!!! note + + Ensure that you have installed the **e(fx)clipse** and **buildship** plugins as listed 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` + +!!! note + + * If you are asked whether to 'keep' or 'overwrite' config files, choose to 'keep'. + * Depending on your connection speed and server load, this step may take up to 30 minutes to finish + (This is because Gradle downloads library files from servers during the project set up process) + * If Eclipse has changed any settings files during the import process, you can discard those changes. + +### Contributing + +We use the [feature branch git workflow][workflow]. Thus when you are working on a task, please remember to assign the relevant issue to yourself [on the issue tracker][issues] and branch off from `master`. When the task is completed, do remember to push the branch to GitHub and [create a new pull request][pr] so that the integrator can review the code. For large features that impact multiple parts of the code it is best to open a new issue on the issue tracker so that the design of the code can be discussed first. + +[Test driven development][tdd] is encouraged but not required. If possible, all of your incoming code should have 100% accompanying tests - Coveralls will fail any incoming pull request which causes coverage to fall. + +### Coding Style + +We use the Java coding standard found at . + + +``` +###### \DeveloperGuide.md +``` md + +### Logging + +We are using the [`java.util.logging`][jul] package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. + +* 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 to a log file. +* The logs roll over at 5MB such that every log file is smaller than 5MB. Five log files are kept, after which the oldest will be deleted. + +``` +###### \DeveloperGuide.md +``` md + +### Configuration + +Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`) + + +## Testing + +Tests can be found in the `./src/test/java` folder. + +### In Eclipse + +* 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. + +!!! note + If you are not using a recent Eclipse version (Neon or later), enable assertions in JUnit tests + as described [in this Stack Overflow question](http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option) (url: http://stackoverflow.com/questions/2522897/eclipse-junit-ea-vm-option). + + +### Using Gradle + +See [UsingGradle.md](#appendix-f-using-gradle) for how to run tests using Gradle. + +We have two types of tests: + +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.todo.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.todo.model.TodoModelTest` + 3. **Hybrids of unit and integration tests.** These tests are checking multiple code units as well as how the are connected together. + e.g. `seedu.todo.logic.BaseCommandTest` + +``` +###### \DeveloperGuide.md +``` md + +## Dev Ops + +### Build Automation + +We use [Gradle][gradle] for build automation. Gradle handles project dependencies, build tasks and testing. If you have configured Eclipse by importing the project as shown in the [setting up](#setting-up) section Gradle should already be properly configured and can be executing from within Eclipse to build, test and package the project from the Run menu. + +See the appendix [Using Gradle](#appendix-f-using-gradle) for all of the details and Gradle commands. + +### Continuous Integration + +We use [Travis CI][travis] to perform Continuous Integration on our projects. See [UsingTravis.md](UsingTravis.md) for more details. + +``` +###### \DeveloperGuide.md +``` md + +## 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 | +*** | user | mark a task as complete | so I know which are the tasks are not complete. +*** | user | delete a task | remove entries that I no longer need +*** | user | edit a task | change or update details for the task +*** | user | set a deadline for a task | track down deadlines of my tasks +*** | user | set events with start and end dates | keep track of events that will happen +*** | user | view tasks | see all the tasks I have +*** | user | view incomplete tasks only | to know what are the tasks I have left to do. +*** | user with multiple computers | save the todo list file to a specific location | move the list to other computers +** | user with a lot tasks | add tags to my tasks | organize my tasks +** | user | set recurring tasks | do not need to repeatedly add tasks +** | user | sort tasks by various parameters| organize my tasks and locate a task easily +** | user | set reminders for a task | do not need to mentally track deadlines +* | user | know the number of tasks I have left | gauge how many tasks I have left to do. +* | user | be notified about upcoming deadlines without opening the app | so that I can receive timely reminders + + +## Appendix B : Use Cases + +(For all use cases below, the **System** is the `TodoApp` and the **Actor** is the `user`, unless specified otherwise) + +### Adding an event + +**MSS** + +1. User types out an event with start time, end time and location +2. TodoApp adds event with specified fields and saves it to disk + +Use case ends. + +**Extensions** + +1a. The task has no title + +> 1a1. TodoApp shows an error message + Use case resumes at step 1 + +1b. The task's date field is empty + +> 1b1. TodoApp creates a task with no start and end date + Use case resumes at step 2 + +1c. The task has a start time later than end time + +> 1c1. TodoApp assumes the dates are inverted + Use case resumes at step 2 + +1d. The event's timing overlaps with an existing event's timing + +> 1d1. TodoApp displays a warning to the user that he has another event at the same time + Use case resumes at step 2 + +### Adding a task with deadline + +**MSS** + +1. User enters a task while specifying a deadline for the task. +2. TodoApp creates new todo item with deadline specified and saves it to disk + +Use case ends. + +**Extensions** + +1a. The task has no title + +> 1a1. TodoApp shows an error message + Use case resumes at step 1 + +1b. The task's date field is empty + +> 1b1. TodoApp creates a task with no deadline + Use case ends + +### Adding a recurring task + +**MSS** + +1. User enters a task with a recurring time period +2. TodoApp creates a new recurring todo item with the specified time period +3. At the start of the specified time period (eg. every week, month) TodoApp creates a copy of the original task for the user + +Use case ends. + +**Extensions** + + +2a. The given recurring time period is invalid + +> 2a1. TodoApp shows an error message + Use case resumes at step 1 + +### Marking a task complete + +**MSS** + +1. User requests to see a list of uncompleted tasks. +2. TodoApp shows a list of uncompleted tasks. +3. User marks complete a specific task in the list. +4. TodoApp marks the task as complete by striking through the task and saving its new state to disk + +Use case ends. + +**Extensions** + +1a. User uses another method to list tasks (e.g. search) + +> 1a1. TodoApp shows the list of tasks requested + Use case resumes at step 2 + +2a. The list is empty + +> 1a1. TodoApp informs the user the list is empty + Use case ends + +3a. The given index is invalid + +> 3a1. TodoApp shows an error message + Use case resumes at step 2 + +3b. The given index is a task which has already been completed + +> 3b1. TodoApp informs the user the task has already been completed + Use case ends + +### Delete task + +**MSS** + +1. User requests to delete a specific task from the list +2. TodoApp deletes the person + +Use case ends. + +**Extensions** + +1a. The given index is invalid + +> 1a1. TodoApp shows an error message + Use case resumes at step 1 + +### Viewing a specific tab (i.e. intelligent views) + +**MSS** + +1. User requests to view specific tab +2. TodoApp shows a list of tasks under specific tab + +Use case ends. + +**Extensions** + +1a. User enters invalid view (eg. a view that doesn't exist ) + +> 1a1. TodoApp shows an error message + Use case ends + +### Finding for a task + +**MSS** + +1. User searches for task with specific tag or fragmented title +2. TodoApp returns a list of tasks matching search fragment + +Use case ends. + +**Extensions** + +1a. User enters an invalid tag/search fragment + +> 1a1. TodoApp returns an empty list + Use case ends + +### Editing a task + +1. User searches for specific task to edit +2. TodoApp returns list of tasks matching search query +3. User edits specific task on the list, changing any of its fields +4. TodoApp accepts changes, reflects them on the task and + +**Extensions** + +2a. List returned is empty +> Use case ends + +3a. User enters invalid task index + +> 3a1. TodoApp shows error message indicating invalid index +> Use case resumes at Step 2 + +3b. User enters invalid arguments to edit fields + +> 3b1. TodoApp shows error message indicating invalid fields +> Use case resumes at Step 2 + +### Pinning a task + +**MSS** + +1. User searches for specific task to pin using the find command +2. TodoApp returns a list of tasks matching the search query +3. User selects a specific task to pin +4. TodoApp pins selected task and updates the storage file on disk + +Use case ends. + +**Extensions** + +2a. List returned by TodoApp is empty + +> Use case ends + +3a. Selected task is already pinned + +> 3a1. TodoApp unpins selected task +> Use case ends + +3b. User provides an invalid index + +> 3b1. TodoApp shows an error message +> Use case resumes at Step 3 + + +### Undoing an action + +1. User carries out a mutating command (see [glossary](#appendix-d-glossary)) +2. User finds they have made a mistake and instructs TodoApp to undo last action +3. TodoApp rolls back the todolist to the previous state and updates the stored todolist on disk + +**Extensions** + +2a1. The user calls the undo command without having made any changes + +> 2a1. TodoApp shows an error message +> Use case ends + + +## Appendix C : Non Functional Requirements + +The project should - + +1. work on any mainstream OS as long as it has Java 8 or higher installed. +2. use a command line interface as the primary input mode +3. have a customizable colour scheme. +4. be able to hold up to 100 todos, events and deadlines. +5. come with automated unit tests. +6. have competitive performance with commands being executed within 5 seconds of typing into the CLI +7. be open source. + +## Appendix D : Glossary + +Mainstream OS + +: Windows, OS X + +Task + +: A single todo task, deadline or item + +Pinning + +: Marking a task with higher importance/priority than others. Pinned tasks will always appear first in any view. + +Mutating Command + +: Any command which causes a change in the state of the the TodoApp (E.g. add, delete, edit, pin, complete) + + +## Appendix E : Product Survey + +**Basic Todo Lists** e.g. Sticky Notes, Notepad + +Very flexible and easy to use, but hard to organise tasks on them. Also, data can only be saved locally. + +**Online/Cloud Based Todo lists** + +Apps such as Google Calendar, Asana and Trello offer a wide range of effective features that help manage your To-do lists. However, most of them require heavy mouse usage and the constant context switching might break user concentration. Our target audience are users who prefer not use the mouse at all, and that makes some of these applications almost unusable. Also, it is hard to sync without a constant internet connection. + +## Appendix F: Using Gradle + +[Gradle][gradle] is a build automation tool for Java projects. It can automate build-related tasks such as + +* Running tests +* Managing library dependencies +* Analyzing code for style compliance +* Packaging code for release + +The gradle configuration for this project is defined in the build script [`build.gradle`](../build.gradle). + +!!! note + To learn more about gradle build scripts refer to [Build Scripts Basics](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html). + +### Running Gradle Commands + +To run a Gradle command, open a command window on the project folder and enter the Gradle command. Gradle commands look like this: + +* On Windows :`gradlew ...` e.g. `gradlew clean allTests` +* On Mac/Linux: `./gradlew ...` e.g. `./gradlew clean allTests` + +!!! note + If you do not specify any tasks, Gradlew will run the default tasks `clean` `headless` `allTests` `coverage` + +### Cleaning the Project + +**`clean`** - Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
+e.g. `./gradlew clean` + +!!! note "`clean` to force Gradle to execute a task" + When running a Gradle task, Gradle will try to figure out if the task needs running at all. If Gradle determines that the output of the task will be same as the previous time, it will not run the task. For example, it will not build the JAR file again if the relevant source files have not changed since the last time the JAR file was built. If we want to force Gradle to run a task, we can combine that task with `clean`. Once the build files have been `clean`ed, Gradle has no way to determine if the output will be same as before, so it will be forced to execute the task. + +### Creating the JAR file + +**`shadowJar`** - Creates the `addressbook.jar` file in the `build/jar` folder, if the current file is outdated. +e.g. `./gradlew shadowJar` + +To force Gradle to create the JAR file even if the current one is up-to-date, you can '`clean`' first. +e.g. `./gradlew clean shadowJar` + +!!! note "Why do we create a fat JAR?" + If we package only our own class files into the JAR file, it will not work properly unless the user has all the other JAR files (i.e. third party libraries) our classes depend on, which is rather inconvenient. Therefore, we package all dependencies into a single JAR files, creating what is also known as a _fat_ JAR file. To create a fat JAR file, we use the [shadow jar](https://github.com/johnrengelman/shadow) Gradle plugin. + +### Running Tests + +**`allTests`** - Runs all tests. + +**`guiTests`** - Runs all tests in the `guitests` package + +**`nonGuiTests`** - Runs all non-GUI tests in the `seedu.address` package + +**`headless`** - Sets the test mode as _headless_. The mode is effective for that Gradle run only so it should be combined with other test tasks. + +Here are some examples: + +* `./gradlew headless allTests` -- Runs all tests in headless mode +* `./gradlew clean nonGuiTests` -- Cleans the project and runs non-GUI tests + + +### Updating Dependencies + +There is no need to run these Gradle tasks manually as they are called automatically by other relevant Gradle tasks. + +**`compileJava`** - Checks whether the project has the required dependencies to compile and run the main program, and download any missing dependencies before compiling the classes. + +See `build.gradle` > `allprojects` > `dependencies` > `compile` for the list of dependencies required. + +**`compileTestJava`** - Checks whether the project has the required dependencies to perform testing, and download any missing dependencies before compiling the test classes. + +See `build.gradle` > `allprojects` > `dependencies` > `testCompile` for the list of dependencies required. + +*[CRUD]: Create, Retrieve, Update, Delete +*[GUI]: Graphical User Interface +*[UI]: User interface +*[IDE]: Integrated Development Environment + +[repo]: https://github.com/CS2103AUG2016-W10-C4/main/ + +[sourcetree]: https://www.sourcetreeapp.com/ +[jdk]: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html +[eclipse]: https://eclipse.org/downloads/ +[travis]: https://travis-ci.org/CS2103AUG2016-W10-C4/main +[coveralls]: https://coveralls.io/github/CS2103AUG2016-W10-C4/main +[codacy]: https://www.codacy.com/app/Logical-Reminding-Apartment/main/dashboard +[gradle]: https://gradle.org/ + +[workflow]: https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow/ +[issues]: https://github.com/CS2103AUG2016-W10-C4/main/issues +[pr]: https://github.com/CS2103AUG2016-W10-C4/main/compare +[tdd]: https://en.wikipedia.org/wiki/Test-driven_development + +[jul]: https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html +[property]: https://docs.oracle.com/javase/8/javafx/api/javafx/beans/property/Property.html + +[gfm]: https://guides.github.com/features/mastering-markdown/ +[py-markdown]: https://pythonhosted.org/Markdown/extensions/index.html +[figcaption]: https://developer.mozilla.org/en/docs/Web/HTML/Element/figcaption +``` +###### \UserGuide.html +``` html +

2 Quick Start#

    +
  1. +

    Ensure you have Java version 8 update 60 or later installed on your computer.

    +
    +

    This application will not work with earlier versions of Java 8

    +
    +
  2. +
  3. +

    Download the latest copy of Uncle_Jim.jar from our releases page.

    +
  4. +
  5. Save the file to the folder you want to use for this application.
  6. +
  7. +

    Double-click the file to start the application. You should see something like this

    +

    Example of UI once launched
    Figure 1. Initial launch screen of Uncle Jim

    +
  8. +
  9. +

    Type in the command box and press Enter to execute it.

    +
  10. +
  11. +

    Here are some example commands you can try:

    +
      +
    • addFinish CS2103T homework /d next Friday - + adds a new task with the deadline set for next Friday
    • +
    • delete3 - deletes the 3rd task shown in the current list
    • +
    • exit - exits the app
    • +
    +
  12. +
  13. +

    Refer to the commands reference section below for details of each command.

    +
  14. +
+ +

3 Command Reference#

You can refer to the section below for the full list of commands that are available in Uncle Jim. For quick reference, you can also refer to the command summary at the end of this guide or use the help command when using the app.

+ +

3.1 Notes regarding command format#

    +
  • Words in UPPERCASE are the parameters.
  • +
  • Items in [SQUARE BRACKETS] are optional.
  • +
  • To specify parameters, such as the deadline for a task, use flags. Flags follow the Windows DOS command format - single dash (eg. /f) for short flags.
  • +
  • Items with ... within each parameter means you can add more items within the same parameters than specified.
  • +
  • Most commands that refer to a particular task or event in the list require an INDEX. This is a number indicated on the left of a task or event as shown in the screenshot below:
  • +
+ +
Index Number Location
Figure 2. Use the number on the side to choose the task for your command
+ +``` +###### \UserGuide.md +``` md + +## Quick Start + +1. Ensure you have [**Java version 8 update 60**][java]{: .print-url } or later installed on your computer. + + !!! warning "This application will not work with earlier versions of Java 8" + +2. Download the latest copy of `Uncle_Jim.jar` from our [releases][releases]{: .print-url } page. +3. Save the file to the folder you want to use for this application. +4. Double-click the file to start the application. You should see something like this + + Example of UI once launched
Initial launch screen of Uncle Jim
+ +5. Type in the command box and press Enter to execute it. +6. Here are some example commands you can try: + + * **`add`**` Finish CS2103T homework /d next Friday` - + adds a new task with the deadline set for next Friday + * **`delete`**` 3` - deletes the 3rd task shown in the current list + * **`exit`** - exits the app + +7. Refer to the [commands reference](#command-reference) section below for details of each command. + + +## Command Reference + +You can refer to the section below for the full list of commands that are available in Uncle Jim. For quick reference, you can also refer to the [command summary](#command-summary) at the end of this guide or use the `help` command when using the app. + +### Notes regarding command format + +* Words in `UPPERCASE` are the parameters. +* Items in `[SQUARE BRACKETS]` are optional. +* To specify parameters, such as the deadline for a task, use flags. Flags follow the Windows DOS command format - single dash (eg. `/f`) for short flags. +* Items with `...` within each parameter means you can add more items within the same parameters than specified. +* Most commands that refer to a particular task or event in the list require an `INDEX`. This is a number indicated on the left of a task or event as shown in the screenshot below: + +Index Number Location + +
Use the number on the side to choose the task for your command
+ +``` diff --git a/collated/docs/A0139021U.md b/collated/docs/A0139021U.md new file mode 100644 index 000000000000..b6c9aa61263a --- /dev/null +++ b/collated/docs/A0139021U.md @@ -0,0 +1,310 @@ +# A0139021U +###### \DeveloperGuide.html +``` html +

1.1 Tooling#

This project uses:

+ +
    +
  • Git - Version control
  • +
  • Eclipse - IDE
  • +
  • Gradle - Build automation
  • +
  • Travis, Coveralls and Codacy - Continuous integration and quality control
  • +
  • GitHub - Source code hosting and issue tracking
  • +
+

2 Setting up#

2.1 Prerequisites#

+ +
    +
  1. Git client + If you are using Linux, you should already have one installed on your command line. If you are using Windows or OS X you can use SourceTree if you are more comfortable with using a GUI.
  2. +
  3. JDK 1.8.0_60 or later + Please use Oracle's jdk because it comes with JavaFX, which is needed for developing the application's UI.
  4. +
  5. Eclipse IDE
  6. +
  7. e(fx)clipse plugin for Eclipse + Perform steps 2 onwards as listed in this page to install the plugin.
  8. +
  9. Buildship Gradle Integration plugin from the Eclipse Marketplace + You can find Eclipse Marketplace from Eclipse's Help toolbar.
  10. +
+``` +###### \DeveloperGuide.html +``` html +

3 Design#

3.1 Architecture#

+ +

Now let us explore the architecture of Uncle Jim's Discount To-do App to help you understand how it works.

+
Figure 1. Simplistic overview of the application
+ +

The architecture diagram above explains the high-level design of the application. Here is a quick overview of each component:

+
    +
  • +

    Main has only one class called MainApp. It is responsible for:

    +
      +
    • Bootstrapping the application at app launch by initializing the components in the correct sequence and injecting the dependencies needed for each component.
    • +
    • Shutting down the components and invoke cleanup method where necessary during shut down.
    • +
    +
  • +
  • +

    Commons represents a collection of modules used by multiple other components

    +
  • +
  • UI: The user facing elements of tha App, representing the view layer
  • +
  • Logic: The parser and command executer, representing the controller
  • +
  • Model: Data manipulation and storage, representing the model and data layer
  • +
+``` +###### \DeveloperGuide.html +``` html +

3.4 Model component#

Figure 10. The relation between the Model subcomponents
+ + +

API : Model.java

+

The model component represents the application's state and data layer. It is implemented by TodoModel, which is a composite of of the individual data models for the application, as well as higher level information about the state of the application itself, such as the current view and the undo/redo stack. Model has two subcomponents:

+
    +
  • TodoList - represents the todolist tasks
  • +
  • UserPrefs - represents saved user preferences
  • +
+

Each individual data model handles their own CRUD operations, with the Model acting as a simplified and uniform interface for external components to work with. Each of the data model holds an injectable Storage object that exposes a simple interface for reading and writing to the appropriate storage mechanism.

+

To avoid tight coupling with the command classes, the model exposes only a small set of generic functions. The UI component binds to the model through a set of getters, such as getObservableList, which expose JavaFX Property objects that the UI can listen to for changes.

+

The model ensure safety by exposing as much of its internal state as possible as immutable objects using interfaces such as ImmutableTask.

+
Figure 11. The relation between the Storage subcomponents
+ +

The storage component represents the persistence layer of the data. It is implemented by TodoListStorage which holds and contains ImmutableTodoList. Similarly, JsonUserPrefsStorage stores the user preferences.

+

Both classes implement FixedStorage, which exposes methods to read and save data from storage. Users can choose to move their storage file, hence MovableStorage is exposed to allow them to do so. User preferences cannot be exported.

+``` +###### \DeveloperGuide.html +``` html +

4.2 Model#

See the Model component architecture section for the high level overview of the Model and Storage components.

+ +

4.2.1 BaseTask#

The BaseTask is a simple abstract class to identify each task uniquely via Java's UUID class. Do note that this implementation means that two tasks with exact same content (fields) can be considered as two different tasks.

+ +

4.2.2 ImmutableTask#

This interface is used frequently to expose fields of a Task to external components. It prevents external components from having access to the setters.

+ +

4.2.3 Task#

A Task is a representation of a task or event in the todolist. Task inherits from BaseTask for a way to declare each task as unique. This object implements MutableTask which allows us to edit the fields.

+ +

4.2.4 ValidationTask#

A ValidationTask is a representation of a task or event in the todolist. This class allows us to verify the fields of the task, and check that they are valid, as the name implies. This class is used mainly for adding and updating a task, as Task is supposed to be immutable.

+ +

4.2.5 TodoList#

This class represents the todolist inside the memory and implements the TodoListModel. This interface is internal to Model and represents only CRUD operations to the todolist.

+ +

4.2.6 TodoModel#

This class represents the data layer of the application and implements the Model interface. The TodoModel handles any interaction with the application state that is not persisted, such as the view (sort and filtering), undo and redo commands.

+ +

4.2.7 ErrorBag#

The ErrorBag is a wrapper around errors produced while handling a command. To use the ErrorBag, simply create a new instance of it and put errors into it. At the end of the validation routine, call validate to let the bag throw a ValidationException if there are any.

+ +

4.2.8 FixedStorage#

This interface represents the persistence mechanism for a file whose location is cannot be set by the user. You can use this interface for storing preferences and other files which the user does not need to change the location for.

+ +

4.2.9 MovableStorage#

This interface enables us to declare a storage mechanism as movable. This means that the file path of the object can be changed when desired. The only class that implements this interface is TodoListStorage, as the configuration and user preference files have minimal impact and thus unimportant for the user.

+ +

4.2.10 TodoListStorage#

The main class that is exposed to the Model. In addition to reading and saving, methods are exposed to enable user to switch where the storage file is saved and read.

+ +

4.2.11 Xml Classes#

Classes prefixed with Xml are classes used to enable serialization of the Model. As the prefix suggests, the critical data is stored in the .xml file format and uses JAXB to read and save to the persistence layer. JAXB marshals Java primitives readily, however, if you wish to marshal any other classes, be it self-implemented or from a library, you would need to declare a XmlAdapter. See LocalDateTimeAdapter for reference.

+ +``` +###### \DeveloperGuide.md +``` md + +### Tooling + +This project uses: + +- **Git** - Version control +- **[Eclipse][eclipse]** - IDE +- **Gradle** - Build automation +- **[Travis][travis], [Coveralls][coveralls] and [Codacy][codacy]** - Continuous integration and quality control +- **[GitHub][repo]** - Source code hosting and issue tracking + +## Setting up + +### Prerequisites + +1. **Git client** + If you are using Linux, you should already have one installed on your command line. If you are using Windows or OS X you can use [SourceTree][sourcetree] if you are more comfortable with using a GUI. +2. [**JDK 1.8.0_60**][jdk] or later + Please use Oracle's jdk because it comes with JavaFX, which is needed for developing the application's UI. +3. **Eclipse** IDE +4. **e(fx)clipse** plugin for Eclipse + Perform steps 2 onwards as listed in [this page](http://www.eclipse.org/efxclipse/install.html#for-the-ambitious){: .print-url } to install the plugin. +5. **Buildship Gradle Integration** plugin from the Eclipse Marketplace + You can find Eclipse Marketplace from Eclipse's `Help` toolbar. + +``` +###### \DeveloperGuide.md +``` md + +## Design + +### Architecture + +Now let us explore the architecture of Uncle Jim's Discount To-do App to help you understand how it works. + + + +
Simplistic overview of the application
+ +The architecture diagram above explains the high-level design of the application. Here is a quick overview of each component: + +* `Main` has only one class called [`MainApp`](../src/main/java/seedu/todo/MainApp.java). It is responsible for: + + * Bootstrapping the application at app launch by initializing the components in the correct sequence and injecting the dependencies needed for each component. + * Shutting down the components and invoke cleanup method where necessary during shut down. + +* [**`Commons`**](#common-modules) represents a collection of modules used by multiple other components +* [**`UI`**](#ui-component): The user facing elements of tha App, representing the view layer +* [**`Logic`**](#logic-component): The parser and command executer, representing the controller +* [**`Model`**](#model-component): Data manipulation and storage, representing the model and data layer + + +``` +###### \DeveloperGuide.md +``` md + +### Model component + + + +
The relation between the Model subcomponents
+ +**API** : [`Model.java`](../src/main/java/seedu/todo/model/Model.java) + +The model component represents the application's state and data layer. It is implemented by `TodoModel`, which is a composite of of the individual data models for the application, as well as higher level information about the state of the application itself, such as the current view and the undo/redo stack. Model has two subcomponents: + +- `TodoList` - represents the todolist tasks +- `UserPrefs` - represents saved user preferences + +Each individual data model handles their own CRUD operations, with the `Model` acting as a simplified and uniform interface for external components to work with. Each of the data model holds an injectable `Storage` object that exposes a simple interface for reading and writing to the appropriate storage mechanism. + +To avoid tight coupling with the command classes, the model exposes only a small set of generic functions. The UI component binds to the model through a set of getters, such as `getObservableList`, which expose JavaFX [`Property`][property] objects that the UI can listen to for changes. + +The model ensure safety by exposing as much of its internal state as possible as immutable objects using interfaces such as `ImmutableTask`. + + + +
The relation between the Storage subcomponents
+ +The storage component represents the persistence layer of the data. It is implemented by `TodoListStorage` which holds and contains `ImmutableTodoList`. Similarly, `JsonUserPrefsStorage` stores the user preferences. + +Both classes implement `FixedStorage`, which exposes methods to read and save data from storage. Users can choose to move their storage file, hence `MovableStorage` is exposed to allow them to do so. User preferences cannot be exported. + +``` +###### \DeveloperGuide.md +``` md + +### Model + +See the [Model component architecture](#model-component) section for the high level overview of the Model and Storage components. + +#### BaseTask + +The `BaseTask` is a simple abstract class to identify each task uniquely via Java's `UUID` class. Do note that this implementation means that two tasks with exact same content (fields) can be considered as two different tasks. + +#### ImmutableTask + +This interface is used frequently to expose fields of a `Task` to external components. It prevents external components from having access to the setters. + +#### Task + +A `Task` is a representation of a task or event in the todolist. `Task` inherits from `BaseTask` for a way to declare each task as unique. This object implements `MutableTask` which allows us to edit the fields. + +#### ValidationTask + +A `ValidationTask` is a representation of a task or event in the todolist. This class allows us to verify the fields of the task, and check that they are valid, as the name implies. This class is used mainly for adding and updating a task, as `Task` is supposed to be immutable. + + +#### TodoList + +This class represents the todolist inside the memory and implements the `TodoListModel`. This interface is internal to Model and represents only CRUD operations to the todolist. + +#### TodoModel + +This class represents the data layer of the application and implements the `Model` interface. The `TodoModel` handles any interaction with the application state that is not persisted, such as the view (sort and filtering), undo and redo commands. + +#### ErrorBag + +The `ErrorBag` is a wrapper around errors produced while handling a command. To use the `ErrorBag`, simply create a new instance of it and `put` errors into it. At the end of the validation routine, call `validate` to let the bag throw a `ValidationException` if there are any. + +#### FixedStorage + +This interface represents the persistence mechanism for a file whose location is cannot be set by the user. You can use this interface for storing preferences and other files which the user does not need to change the location for. + +#### MovableStorage + +This interface enables us to declare a storage mechanism as movable. This means that the file path of the object can be changed when desired. The only class that implements this interface is `TodoListStorage`, as the configuration and user preference files have minimal impact and thus unimportant for the user. + +#### TodoListStorage + +The main class that is exposed to the Model. In addition to reading and saving, methods are exposed to enable user to switch where the storage file is saved and read. + +#### Xml Classes + +Classes prefixed with `Xml` are classes used to enable serialization of the Model. As the prefix suggests, the critical data is stored in the `.xml` file format and uses `JAXB` to read and save to the persistence layer. `JAXB` marshals Java primitives readily, however, if you wish to marshal any other classes, be it self-implemented or from a library, you would need to declare a `XmlAdapter`. See `LocalDateTimeAdapter` for reference. + +``` +###### \UserGuide.html +``` html +

3.5 Marking a task complete: complete#

Format:
+completeINDEX
+complete/all

+ +

After finishing a task, you can mark it complete by specifying the index of the task you wish to mark complete in the INDEX parameter. Completed tasks have their title struckthrough.

+

If you wish to mark a batch of task in the current view as complete, you can use the /all flag to mark them all as complete.

+
Example of a Completed Task
Figure 5. The satisfying view of a completed task
+ +

Note that this is a toggle command, so if the specified task is already completed, running this will mark the task as incomplete again.

+

3.6 Clearing tasks: clear#

Format:
+clear

+ +

If you start to have a lot of completed tasks building up, you can clear the clutter by deleting all of them with clear. Note that this only clears completed tasks in the current view.

+

3.7 Pinning a task: pin#

Format: pinINDEX

+ +

If a particular task or event is important, you can pin it to the top of every list the item appears in using this command. You can also use this command to unpin any pinned task.

+
Pinned Task
Figure 6. Prioritize important tasks with pinned tasks.
+ +

3.8 Editing a task: edit#

Format:
+editINDEX [NAME] [/d DEADLINE] [/m DESCRIPTION] [/r TIME] [/p]
+editINDEX [NAME] [/d START END] [/m DESCRIPTION] [/l LOCATION] [/p]

+ +

You can edit tasks or events using the edit command. This command accepts the same parameters as the add command with the addition of INDEX, which specifies which task or event you want to edit.

+

Note that edits are automatically saved, and any errors can be undone using undo.

+``` +###### \UserGuide.md +``` md + +### Marking a task complete: **`complete`** + +Format: +**`complete`**` INDEX` +**`complete`**` /all` + +After finishing a task, you can mark it complete by specifying the index of the task you wish to mark complete in the `INDEX` parameter. Completed tasks have their title struckthrough. + +If you wish to mark a batch of task in the current view as complete, you can use the `/all` flag to mark them all as complete. + +Example of a Completed Task + +
The satisfying view of a completed task
+ +Note that this is a toggle command, so if the specified task is already completed, running this will mark the task as incomplete again. + + +### Clearing tasks: **`clear`** + +Format: +**`clear`** + +If you start to have a lot of completed tasks building up, you can clear the clutter by deleting all of them with `clear`. Note that this only clears completed tasks in the current view. + +### Pinning a task: **`pin`** + +Format: **`pin`**` INDEX` + +If a particular task or event is important, you can pin it to the top of every list the item appears in using this command. You can also use this command to unpin any pinned task. + +Pinned Task + +
Prioritize important tasks with pinned tasks.
+ +### Editing a task: **`edit`** + +Format: +**`edit`**` INDEX [NAME] [/d DEADLINE] [/m DESCRIPTION] [/r TIME] [/p]` +**`edit`**` INDEX [NAME] [/d START END] [/m DESCRIPTION] [/l LOCATION] [/p]` + +You can edit tasks or events using the `edit` command. This command accepts the same parameters as the `add` command with the addition of `INDEX`, which specifies which task or event you want to edit. + +Note that edits are automatically saved, and any errors can be undone using `undo`. + +``` diff --git a/collated/docs/A0139021Ureused.md b/collated/docs/A0139021Ureused.md new file mode 100644 index 000000000000..e4deb66a56c6 --- /dev/null +++ b/collated/docs/A0139021Ureused.md @@ -0,0 +1,857 @@ +# A0139021Ureused +###### \css\normalize.css +``` css +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 20px auto; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} +``` +###### \css\skeleton.css +``` css +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 700px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + background: #fff; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 1.5rem; + margin-bottom: 1rem; + font-weight: 300; } +h1 { font-size: 3.6rem; line-height: 1.5; margin-top: 7rem; } +h2 { font-size: 2.6rem; line-height: 1.25; margin-top: 10rem; } +h3 { font-size: 2.2rem; line-height: 1.3; margin-top: 2rem; } +h4 { font-size: 1.8rem; line-height: 1.35; } +h5 { font-size: 1.6rem; font-weight: bold; } + +p { + margin-top: 0; } + +table { + line-height: 1.3; + width: 100%; +} + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #1EAEDB; text-decoration: none; } +a:hover { + color: #0FA0CE; text-decoration: underline; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + height: 38px; + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border-radius: 4px; + border: 1px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus { + color: #333; + border-color: #888; + outline: 0; } +.button.button-primary, +button.button-primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #33C3F0; + border-color: #33C3F0; } +.button.button-primary:hover, +button.button-primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #1EAEDB; + border-color: #1EAEDB; } + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle outside; } +ol { + list-style: decimal outside; } +ol, ul { + padding-left: 2.5rem; + margin-left: 0; + line-height: 1.3; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: .5rem 0; + font-size: 95%; } +li { + margin-bottom: .6rem; } + +ul p, ol p { + margin: 0; +} + +dl dt { + font-weight: bold; +} + +dl dd { + border-left: .5rem solid rgba(0, 0, 0, 0.1); + margin: .5rem 0 1.8rem; + padding: .25rem 2rem .5rem; + line-height: 1.3; +} + +dl dd p { + margin: 0; +} + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .15rem .5rem; + margin: 0 .2rem; + font-size: 80%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } +a code { + display: inline-block; + text-decoration: none; +} + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +form { + margin-bottom: 2rem; } + +p, +ul, +ol { + margin-botton: 1rem; +} + +blockquote { + border-left: .5rem solid #ddd; + margin-left: .5rem; + padding: 0 0 .25rem 1.5rem; +} + +blockquote p, +blockquote ul, +blockquote ol { + margin-bottom: 0; +} + +img { + display: inline-block; + max-width: 100%; +} + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + + +/* Larger than mobile */ +@media (min-width: 400px) {} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) {} + +/* Larger than tablet */ +@media (min-width: 750px) {} + +/* Larger than desktop */ +@media (min-width: 1000px) {} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} +``` +###### \css\styles.css +``` css +.codehilite .hll { background-color: #ffffcc } +.codehilite .c { color: #408080; font-style: italic } /* Comment */ +.codehilite .err { border: 1px solid #FF0000 } /* Error */ +.codehilite .k { color: #008000; font-weight: bold } /* Keyword */ +.codehilite .o { color: #666666 } /* Operator */ +.codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ +.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */ +.codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ +.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */ +.codehilite .gd { color: #A00000 } /* Generic.Deleted */ +.codehilite .ge { font-style: italic } /* Generic.Emph */ +.codehilite .gr { color: #FF0000 } /* Generic.Error */ +.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.codehilite .gi { color: #00A000 } /* Generic.Inserted */ +.codehilite .go { color: #888888 } /* Generic.Output */ +.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.codehilite .gs { font-weight: bold } /* Generic.Strong */ +.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.codehilite .gt { color: #0044DD } /* Generic.Traceback */ +.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.codehilite .kp { color: #008000 } /* Keyword.Pseudo */ +.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.codehilite .kt { color: #B00040 } /* Keyword.Type */ +.codehilite .m { color: #666666 } /* Literal.Number */ +.codehilite .s { color: #BA2121 } /* Literal.String */ +.codehilite .na { color: #7D9029 } /* Name.Attribute */ +.codehilite .nb { color: #008000 } /* Name.Builtin */ +.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.codehilite .no { color: #880000 } /* Name.Constant */ +.codehilite .nd { color: #AA22FF } /* Name.Decorator */ +.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.codehilite .nf { color: #0000FF } /* Name.Function */ +.codehilite .nl { color: #A0A000 } /* Name.Label */ +.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.codehilite .nv { color: #19177C } /* Name.Variable */ +.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.codehilite .w { color: #bbbbbb } /* Text.Whitespace */ +.codehilite .mb { color: #666666 } /* Literal.Number.Bin */ +.codehilite .mf { color: #666666 } /* Literal.Number.Float */ +.codehilite .mh { color: #666666 } /* Literal.Number.Hex */ +.codehilite .mi { color: #666666 } /* Literal.Number.Integer */ +.codehilite .mo { color: #666666 } /* Literal.Number.Oct */ +.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ +.codehilite .sc { color: #BA2121 } /* Literal.String.Char */ +.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ +.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.codehilite .sx { color: #008000 } /* Literal.String.Other */ +.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */ +.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ +.codehilite .ss { color: #19177C } /* Literal.String.Symbol */ +.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.codehilite .vc { color: #19177C } /* Name.Variable.Class */ +.codehilite .vg { color: #19177C } /* Name.Variable.Global */ +.codehilite .vi { color: #19177C } /* Name.Variable.Instance */ +.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ + +``` diff --git a/collated/generated.md b/collated/generated.md deleted file mode 100644 index 0d8494c796e9..000000000000 --- a/collated/generated.md +++ /dev/null @@ -1,793 +0,0 @@ -# generated -###### \build\resources\main\view\CommandErrorView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \build\resources\main\view\CommandFeedbackView.fxml -``` fxml - - - - - - - - - - -``` -###### \build\resources\main\view\CommandInputView.fxml -``` fxml - - - - - - - - -``` -###### \build\resources\main\view\CommandPreviewView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \build\resources\main\view\FilterBarView.fxml -``` fxml - - - - - - - - - -``` -###### \build\resources\main\view\HelpView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \build\resources\main\view\MainWindow.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \build\resources\main\view\SearchStatusView.fxml -``` fxml - - - - - - - - Searching for: - - - - -``` -###### \build\resources\main\view\TaskCardView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \build\resources\main\view\TodoListView.fxml -``` fxml - - - - - - - - - - -``` -###### \src\main\resources\view\CommandErrorView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \src\main\resources\view\CommandFeedbackView.fxml -``` fxml - - - - - - - - - - -``` -###### \src\main\resources\view\CommandInputView.fxml -``` fxml - - - - - - - - -``` -###### \src\main\resources\view\CommandPreviewView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \src\main\resources\view\FilterBarView.fxml -``` fxml - - - - - - - - - -``` -###### \src\main\resources\view\HelpView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \src\main\resources\view\MainWindow.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \src\main\resources\view\SearchStatusView.fxml -``` fxml - - - - - - - - Searching for: - - - - -``` -###### \src\main\resources\view\TaskCardView.fxml -``` fxml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -###### \src\main\resources\view\TodoListView.fxml -``` fxml - - - - - - - - - - -``` diff --git a/collated/main/A0092382A.md b/collated/main/A0092382A.md new file mode 100644 index 000000000000..7a91a04f3d01 --- /dev/null +++ b/collated/main/A0092382A.md @@ -0,0 +1,501 @@ +# A0092382A +###### \java\seedu\todo\commons\util\TimeUtil.java +``` java + public boolean isOngoing(LocalDateTime startTime, LocalDateTime endTime) { + if (endTime == null) { + logger.log(Level.WARNING, "endTime in isOngoing(..., ...) is null."); + return false; + } + + if (startTime == null) { + logger.log(Level.WARNING, "startTime in isOngoing(..., ...) is null"); + return false; + } + + return now.isAfter(startTime) && now.isBefore(endTime); + } + + public boolean isToday(ImmutableTask task) { + LocalDateTime timeToCompareTo; + if (task.getStartTime().isPresent()) { + timeToCompareTo = task.getStartTime().get(); + } else if (task.getEndTime().isPresent()) { + timeToCompareTo = task.getEndTime().get(); + } else { + return false; + } + return isToday(now, timeToCompareTo); + } + +``` +###### \java\seedu\todo\logic\commands\CompleteCommand.java +``` java +public class CompleteCommand extends BaseCommand { + + private Argument index = new IntArgument("index"); + + private Argument updateAllFlag = new StringArgument("all").flag("all"); + + @Override + protected Parameter[] getArguments() { + return new Parameter[] { index, updateAllFlag }; + } + + @Override + public String getCommandName() { + return "complete"; + } + + @Override + protected void validateArguments() { + if (updateAllFlag.hasBoundValue() && index.hasBoundValue()) { + errors.put("You must either specify an index or an /all flag, not both!"); + } else if (!index.hasBoundValue() && !updateAllFlag.hasBoundValue()) { + errors.put("You must specify an index or a /all flag. You have specified none!"); + } + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Mark task as completed", getCommandName(), getArgumentSummary())); + } + + @Override + public CommandResult execute() throws ValidationException { + if (index.hasBoundValue()) { + ImmutableTask task = this.model.update(index.getValue(), t -> t.setCompleted(!t.isCompleted())); + eventBus.post(new HighlightTaskEvent(task)); + return new CommandResult(); + } else { + this.model.updateAll(t -> t.setCompleted(true)); + return new CommandResult("All tasks marked as completed"); + } + } + +} +``` +###### \java\seedu\todo\logic\commands\EditCommand.java +``` java +public class EditCommand extends BaseCommand { + + // These parameters will be sorted out manually by overriding setPositionalArgument + private Argument index = new IntArgument("index").required(); + private Argument title = new StringArgument("title"); + + private Argument description = new StringArgument("description") + .flag("m"); + + private Argument pin = new FlagArgument("pin") + .flag("p"); + + private Argument location = new StringArgument("location") + .flag("l"); + + private Argument date = new DateRangeArgument("date") + .flag("d"); + + @Override + protected Parameter[] getArguments() { + return new Parameter[] { index, title, date, description, pin, location }; + } + + @Override + public String getCommandName() { + return "edit"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Edit task", getCommandName(), + getArgumentSummary())); + + } + + @Override + protected void setPositionalArgument(String argument) { + String[] tokens = argument.trim().split("\\s+", 2); + Parameter[] positionals = new Parameter[]{ index, title }; + + for (int i = 0; i < tokens.length; i++) { + try { + positionals[i].setValue(tokens[i].trim()); + } catch (IllegalValueException e) { + errors.put(positionals[i].getName(), e.getMessage()); + } + } + } + + @Override + public CommandResult execute() throws ValidationException { + ImmutableTask editedTask = this.model.update(index.getValue(), task -> { + if (title.hasBoundValue()) { + task.setTitle(title.getValue()); + } + + if (description.hasBoundValue()) { + task.setDescription(description.getValue()); + } + + if (pin.hasBoundValue()) { + task.setPinned(pin.getValue()); + } + + if (location.hasBoundValue()) { + task.setLocation(location.getValue()); + } + + if (date.hasBoundValue()) { + task.setStartTime(date.getValue().getStartTime()); + task.setEndTime(date.getValue().getEndTime()); + } + }); + eventBus.post(new HighlightTaskEvent(editedTask)); + if (description.hasBoundValue()) { + eventBus.post(new ExpandCollapseTaskEvent(editedTask, false)); + } + return new CommandResult(); + } + +} +``` +###### \java\seedu\todo\logic\commands\PinCommand.java +``` java +public class PinCommand extends BaseCommand { + + private Argument index = new IntArgument("index").required(); + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{ index }; + } + + @Override + public String getCommandName() { + return "pin"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Pin task to top of list", getCommandName(), + getArgumentSummary())); + } + + @Override + public CommandResult execute() throws ValidationException { + ImmutableTask task = this.model.update(index.getValue(), t -> t.setPinned(!t.isPinned())); + + // Run the event later to prevent highlight event race condition + Platform.runLater(() -> { + eventBus.post(new HighlightTaskEvent(task)); + }); + return new CommandResult(); + } + +} +``` +###### \java\seedu\todo\logic\commands\ViewCommand.java +``` java +public class ViewCommand extends BaseCommand { + + private Argument view = new StringArgument("view").required(); + + private TaskViewFilter viewSpecified; + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{ view }; + } + + @Override + public String getCommandName() { + return "view"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Switch tabs", getCommandName(), getArgumentSummary())); + } + + @Override + protected void validateArguments() { + if (!view.hasBoundValue()) { + return; + } + + TaskViewFilter[] viewArray = TaskViewFilter.all(); + String viewSpecified = view.getValue().trim().toLowerCase(); + + for (TaskViewFilter filter : viewArray) { + String viewName = filter.name; + char shortcut = viewName.charAt(filter.shortcutCharPosition); + boolean matchesShortcut = viewSpecified.length() == 1 && viewSpecified.charAt(0) == shortcut; + + if (viewName.contentEquals(viewSpecified) || matchesShortcut) { + this.viewSpecified = filter; + return; + } + } + + String error = String.format("The view %s does not exist", view.getValue()); + errors.put("view", error); + } + + @Override + public CommandResult execute() throws ValidationException { + //dismisses find if present + model.find(null); + model.view(viewSpecified); + return new CommandResult(); + } + +} +``` +###### \java\seedu\todo\model\property\TaskViewFilter.java +``` java +public class TaskViewFilter { + private static TimeUtil timeUtil = new TimeUtil(); + private static final Comparator CHRONOLOGICAL = (a, b) -> ComparisonChain.start() + .compare(a.getEndTime().orElse(null), b.getEndTime().orElse(null), Ordering.natural().nullsLast()) + .result(); + private static final Comparator CHRONOLOGICAL_EVENT = (a, b) -> ComparisonChain.start() + //completed events are below + .compareFalseFirst(a.isCompleted(), b.isCompleted()) + //then followed by events which have passed/ tasks which are overdue + .compareFalseFirst(timeUtil.isOverdue(a.getEndTime().orElse(LocalDateTime.now())), + timeUtil.isOverdue(b.getEndTime().orElse(LocalDateTime.now()))) + //then by chronological order + .compare(a.getEndTime().orElse(null), b.getEndTime().orElse(null), Ordering.natural().nullsLast()) + .result(); + + private static final Comparator LAST_UPDATED = (a, b) -> + b.getCreatedAt().compareTo(a.getCreatedAt()); + + public static final TaskViewFilter DEFAULT = new TaskViewFilter("all", + null, LAST_UPDATED); + + public static final TaskViewFilter INCOMPLETE = new TaskViewFilter("incomplete", + task -> !task.isCompleted(), CHRONOLOGICAL); + + public static final TaskViewFilter DUE_SOON = new TaskViewFilter("due soon", + task -> !task.isCompleted() && !task.isEvent() && task.getEndTime().isPresent(), CHRONOLOGICAL); + + public static final TaskViewFilter EVENTS = new TaskViewFilter("events", + ImmutableTask::isEvent, CHRONOLOGICAL_EVENT); + + public static final TaskViewFilter COMPLETED = new TaskViewFilter("completed", + ImmutableTask::isCompleted, LAST_UPDATED); + + public static final TaskViewFilter TODAY = new TaskViewFilter("today", + task -> timeUtil.isToday(task) , CHRONOLOGICAL_EVENT); + + public final String name; + + public final Predicate filter; + + public final Comparator sort; + + public final int shortcutCharPosition; + + public TaskViewFilter(String name, Predicate filter, Comparator sort) { + this(name, filter, sort, 0); + } + + public TaskViewFilter(String name, Predicate filter, Comparator sort, int underlineCharPosition) { + this.name = name; + this.filter = filter; + this.sort = sort; + this.shortcutCharPosition = underlineCharPosition; + } + + public static TaskViewFilter[] all() { + return new TaskViewFilter[]{ + DEFAULT, TODAY, DUE_SOON, EVENTS, INCOMPLETE, COMPLETED + }; + } + + @Override + public String toString() { + return name; + } +} +``` +###### \java\seedu\todo\model\TodoList.java +``` java + @Override + public List update(List indexes, Consumer update) throws ValidationException { + + for (Integer index : indexes) { + MutableTask task = tasks.get(index); + ValidationTask validationTask = new ValidationTask(task); + update.accept(validationTask); + validationTask.validate(); + } + + //All updates are validated so second for loop carries out actual updates + List tasksUpdated = new ArrayList<>(); + for (Integer index : indexes) { + MutableTask task = tasks.get(index); + tasksUpdated.add(task); + update.accept(task); + } + saveTodoList(); + return tasksUpdated; + } + + @Override + public ImmutableTask update(int index, Consumer update) throws ValidationException { + List indexes = Lists.newArrayList(index); + //get(0) since list of updated tasks only contain one element + return update(indexes, update).get(0); + } + +``` +###### \java\seedu\todo\model\TodoModel.java +``` java + @Override + public ImmutableTask delete(int index) throws ValidationException { + saveUndoState(); + int taskIndex = getTaskIndex(index); + ImmutableTask taskToDelete = tasks.get(taskIndex); + ImmutableTask taskDeleted = todoList.delete(taskIndex); + //Notification only sent if delete is valid + uniqueTagCollection.notifyTaskDeleted(taskToDelete); + return taskDeleted; + } + + @Override + public List deleteAll() throws ValidationException{ + saveUndoState(); + List indexes = new ArrayList<>(); + for (int i = 1; i <= getObservableList().size(); i++) { + ImmutableTask task = getObservableList().get(i-1); + if (task.isCompleted()){ + indexes.add(getTaskIndex(i)); + } + } + //Notification only sent if deletions are valid + List deletedTasks = todoList.delete(indexes); + for (ImmutableTask task : deletedTasks) { + uniqueTagCollection.notifyTaskDeleted(task); + } + return deletedTasks; + + } + + @Override + public ImmutableTask update(int index, Consumer update) throws ValidationException { + saveUndoState(); + int taskIndex = getTaskIndex(index); + return todoList.update(taskIndex, update); + } + + @Override + public List updateAll(Consumer update) throws ValidationException { + saveUndoState(); + List indexes = new ArrayList<>(); + for (int i = 1; i <= getObservableList().size(); i++) { + indexes.add(getTaskIndex(i)); + } + return todoList.update(indexes, update); + } + +``` +###### \java\seedu\todo\ui\UiManager.java +``` java + @Subscribe + private void handleShowTagsEvent(ShowTagsEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getGlobalTagView().displayGlobalTags(event.getListOfTags()); + } + +``` +###### \java\seedu\todo\ui\view\GlobalTagView.java +``` java +/** + * A view that displays all the help commands in a single view. + */ +public class GlobalTagView extends UiPart { + + /* Constants */ + private static final String FXML = "GlobalTagView.fxml"; + + /* Variables */ + private final Logger logger = LogsCenter.getLogger(GlobalTagView.class); + + /*Layouts*/ + private VBox globalTagViewPanel; + + @FXML private FlowPane tagFlowPane; + + /** + * Loads and initialise the feedback view element to the placeHolder + * @param primaryStage of the application + * @param placeholder where the view element {@link #globalTagViewPanel} should be placed + * @return an instance of this class + */ + public static GlobalTagView load(Stage primaryStage, AnchorPane placeholder) { + GlobalTagView globalTagView = UiPartLoaderUtil.loadUiPart(primaryStage, placeholder, new GlobalTagView()); + globalTagView.configureLayout(); + globalTagView.hideGlobalTagViewPanel(); + return globalTagView; + } + + /** + * Configure the UI layout of {@link GlobalTagView} + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(globalTagViewPanel, 0.0, 0.0, 0.0); + } + + private void appendTag(Tag tag) { + Label tagLabel = constructTagLabel(tag); + tagFlowPane.getChildren().add(tagLabel); + } + + private Label constructTagLabel(Tag tag) { + String tagName = tag.getTagName(); + Label tagLabel = ViewGeneratorUtil.constructRoundedText(tagName); + ViewStyleUtil.addClassStyles(tagLabel, "white"); + return tagLabel; + } + + /** + * Displays a list of tags into the globalTagPanelView + */ + public void displayGlobalTags(Collection globalTags) { + this.showGlobalTagViewPanel(); + tagFlowPane.getChildren().clear(); + globalTags.forEach(this::appendTag); + } + + /* Ui Methods */ + public void hideGlobalTagViewPanel() { + FxViewUtil.setCollapsed(globalTagViewPanel, true); + } + + private void showGlobalTagViewPanel() { + FxViewUtil.setCollapsed(globalTagViewPanel, false); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + this.globalTagViewPanel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \resources\style\DefaultStyle.css +``` css +/*Ongoing*/ +.ongoing { + primary-color: #689f38; + selected-color: #33691e; + font-color: rgba(255, 255, 255, .9); +} + +``` diff --git a/collated/main/A0135805H.md b/collated/main/A0135805H.md new file mode 100644 index 000000000000..bf02b699a27c --- /dev/null +++ b/collated/main/A0135805H.md @@ -0,0 +1,3235 @@ +# A0135805H +###### \java\seedu\todo\commons\events\ui\CommandInputEnterEvent.java +``` java +/** + * An event when the user presses "ENTER" on the keyboard in the command input. + */ +public class CommandInputEnterEvent extends BaseEvent { + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\todo\commons\events\ui\ExpandCollapseTaskEvent.java +``` java +/** + * An event to tell the Ui to collapse or expand a given task in the to-do list. + */ +public class ExpandCollapseTaskEvent extends BaseEvent{ + + public final ImmutableTask task; + + public Boolean toCollapse; + + /** + * Constructs an event that tells the Ui to collapse or expend a given task in the to-do list. + * @param task a single index of the task that is matching to the Ui (index 1 to num of tasks, inclusive) + */ + public ExpandCollapseTaskEvent(ImmutableTask task) { + this(task, null); + } + + public ExpandCollapseTaskEvent(ImmutableTask task, Boolean toCollapse) { + this.task = task; + this.toCollapse = toCollapse; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\todo\commons\events\ui\ShowHelpEvent.java +``` java +/** + * An event requesting to view the help page. + */ +public class ShowHelpEvent extends BaseEvent { + private List commandSummaries; + + public ShowHelpEvent(List commandSummaries) { + this.commandSummaries = commandSummaries; + } + + public List getCommandSummaries() { + return commandSummaries; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\seedu\todo\commons\events\ui\ShowTagsEvent.java +``` java +/** + * An event requesting to view the global tags page. + */ +public class ShowTagsEvent extends BaseEvent { + + private List listOfTags; + + public ShowTagsEvent(List listOfTags) { + this.listOfTags = listOfTags; + } + + /** + * Retrieves the global list of tags sent by the event bus. + */ + public List getListOfTags() { + return listOfTags; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\seedu\todo\commons\util\StringUtil.java +``` java + /** + * Partitions the string into three parts: + * string[0 .. position - 1], string[position], string[position + 1 .. length - 1], all index inclusive + * @param string to be partitioned + * @param position location where the string should be partitioned + * @return a String array containing the three elements stated above, + * where each element must not be null, but can have empty string. + */ + public static String[] partitionStringAtPosition(String string, int position) { + String[] stringArray = new String[3]; + if (string == null || string.isEmpty() || position < 0 || position >= string.length()) { + stringArray[0] = ""; + stringArray[1] = ""; + stringArray[2] = ""; + } else { + stringArray[0] = string.substring(0, position); + stringArray[1] = string.substring(position, position + 1); + stringArray[2] = string.substring(position + 1, string.length()); + } + return stringArray; + } + + /** + * Splits string at only space and comma. + * @return Returns a String array with all the split components of the string. + */ + public static String[] split(String string) { + if (isEmpty(string)) { + return new String[0]; + } else { + return Iterables.toArray(Splitter.on(DELIMITER) + .trimResults() + .omitEmptyStrings() + .split(string), String.class); + } + } + + /** + * Given a string list, gets the text from the list in the following manner: + * apple, pear, pineapple + */ + public static String convertListToString(String[] stringList) { + if (stringList == null || stringList.length == 0) { + return ""; + } + StringJoiner stringJoiner = new StringJoiner(", "); + for (String string : stringList) { + stringJoiner.add(string); + } + return stringJoiner.toString(); + } + + /** + * Given an iterable, join them together with comma as shown: + * apple, pear, pineapple + */ + public static String convertIterableToString( Iterable iterable) { + if (iterable == null) { + return ""; + } + return StringUtils.join(iterable, ", "); + } + +``` +###### \java\seedu\todo\commons\util\TimeUtil.java +``` java +/** + * Utility methods that deals with time. + */ +public class TimeUtil { + + + /* Constants */ + private static final Logger logger = LogsCenter.getLogger(TimeUtil.class); + + private static final String WORD_IN = "in"; + private static final String WORD_BY = "by"; + private static final String WORD_SINCE = "since"; + private static final String WORD_AGO = "ago"; + private static final String WORD_FROM = "from"; + private static final String WORD_TO = "to"; + private static final String WORD_TOMORROW = "tomorrow"; + private static final String WORD_YESTERDAY = "yesterday"; + private static final String WORD_TODAY = "today"; + private static final String WORD_TONIGHT = "tonight"; + private static final String WORD_COMMA = ","; + private static final String WORD_SPACE = " "; + + private static final String DUE_NOW = "due now"; + private static final String DUE_LESS_THAN_A_MINUTE = "in less than a minute"; + + private static final String UNIT_MINUTES = "minutes"; + private static final String VALUE_ONE_MINUTE = "1 minute"; + + private static final String FORMAT_DATE_WITH_YEAR = "d MMMM yyyy"; + private static final String FORMAT_DATE_NO_YEAR = "d MMMM"; + private static final String FORMAT_TIME = "h:mm a"; + + private static final Pattern DATE_REGEX = Pattern.compile("\\b([0123]?\\d)([/-])([01]?\\d)(?=\\2\\d{2,4}|\\s|$)"); + + /* Variables */ + protected Clock clock = Clock.systemDefaultZone(); + protected LocalDateTime now = LocalDateTime.now(clock); + + /*Setters*/ + protected void setClock(Clock clock) { + this.clock = clock; + + } + + protected void setNow (LocalDateTime now){ + this.now = now; + } + /** + * Gets the task deadline expression for the UI. + * @param endTime ending time + * @return a formatted deadline String + */ + public String getTaskDeadlineText(LocalDateTime endTime) { + if (endTime == null) { + logger.log(Level.WARNING, "endTime in getTaskDeadlineText(...) is missing."); + return ""; + } + + LocalDateTime currentTime = now; + if (endTime.isAfter(currentTime)) { + return getDeadlineNotOverdueText(currentTime, endTime); + } else { + return getDeadlineOverdueText(currentTime, endTime); + } + } + + /** + * Helper method of {@link #getTaskDeadlineText(LocalDateTime)} to get deadline text + * when it is still not overdue (currentTime < endTime). + * @param currentTime the time now + * @param endTime the due date and time + * @return a formatted deadline string + */ + private String getDeadlineNotOverdueText(LocalDateTime currentTime, LocalDateTime endTime) { + Duration durationCurrentToEnd = Duration.between(currentTime, endTime); + long minutesToDeadline = durationCurrentToEnd.toMinutes(); + long secondsToDeadline = durationCurrentToEnd.getSeconds(); + + StringJoiner stringJoiner = new StringJoiner(WORD_SPACE); + + if (secondsToDeadline <= 59) { + return DUE_LESS_THAN_A_MINUTE; + } else if (minutesToDeadline <= 59) { + stringJoiner.add(WORD_IN).add(getMinutesText(currentTime, endTime)); + } else { + stringJoiner.add(WORD_BY).add(getDateText(currentTime, endTime) + WORD_COMMA) + .add(getTimeText(endTime)); + } + return stringJoiner.toString(); + } + + /** + * Helper method of {@link #getTaskDeadlineText(LocalDateTime)} to get deadline text + * when it is overdue (currentTime > endTime). + * @param currentTime the time now + * @param endTime the due date and time + * @return a formatted deadline string + */ + private String getDeadlineOverdueText(LocalDateTime currentTime, LocalDateTime endTime) { + Duration durationCurrentToEnd = Duration.between(currentTime, endTime); + long minutesToDeadline = durationCurrentToEnd.toMinutes(); + long secondsToDeadline = durationCurrentToEnd.getSeconds(); + + StringJoiner stringJoiner = new StringJoiner(WORD_SPACE); + + if (secondsToDeadline >= -59) { + return DUE_NOW; + } else if (minutesToDeadline >= -59) { + stringJoiner.add(getMinutesText(currentTime, endTime)).add(WORD_AGO); + } else { + stringJoiner.add(WORD_SINCE).add(getDateText(currentTime, endTime) + WORD_COMMA) + .add(getTimeText(endTime)); + } + return stringJoiner.toString(); + } + + /** + * Gets the event date and time text for the UI + * @param startTime of the event + * @param endTime of the event + * @return a formatted event duration string + */ + public String getEventTimeText(LocalDateTime startTime, LocalDateTime endTime) { + if (startTime == null || endTime == null) { + logger.log(Level.WARNING, "Either startTime or endTime is missing in getEventTimeText(...)"); + return ""; + } else if (startTime.isAfter(endTime)) { + logger.log(Level.WARNING, "Start time is after end time in getEventTimeText(...)"); + return ""; + } + + LocalDateTime currentTime = now; + StringJoiner joiner = new StringJoiner(WORD_SPACE); + if (isSameDay(startTime, endTime)) { + joiner.add(getDateText(currentTime, startTime) + WORD_COMMA) + .add(WORD_FROM).add(getTimeText(startTime)) + .add(WORD_TO).add(getTimeText(endTime)); + } else { + joiner.add(WORD_FROM).add(getDateText(currentTime, startTime) + WORD_COMMA).add(getTimeText(startTime)) + .add(WORD_TO).add(getDateText(currentTime, endTime) + WORD_COMMA).add(getTimeText(endTime)); + } + return joiner.toString(); + } + + + /** + * Gives a formatted text of the dateTime based on the current system time, and then returns one of the following: + * "Yesterday", "Today", "Tonight", "Tomorrow", + * full date without year if this year, + * full date with year if other year. + * + * @param dateTime to format the date with + * @return a formatted date text described above + */ + private String getDateText(LocalDateTime currentTime, LocalDateTime dateTime) { + if (isYesterday(currentTime, dateTime)) { + return WORD_YESTERDAY; + } else if (isTonight(currentTime, dateTime)) { + return WORD_TONIGHT; + } else if (isToday(currentTime, dateTime)) { + return WORD_TODAY; + } else if (isTomorrow(currentTime, dateTime)) { + return WORD_TOMORROW; + } else if (isSameYear(currentTime, dateTime)) { + return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_DATE_NO_YEAR)); + } else { + return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_DATE_WITH_YEAR)); + } + } + + /** + * Returns a formatted string of the time component of dateTime + * @param dateTime to format the time with + * @return a formatted time text (HH:MM A/PM) + */ + private String getTimeText(LocalDateTime dateTime) { + return dateTime.format(DateTimeFormatter.ofPattern(FORMAT_TIME)); + } + + /** + * Counts the number of minutes between the two dateTimes and prints out either: + * "1 minute" or "X minutes", for X != 1, X >= 0. + * @param dateTime1 the first time instance + * @param dateTime2 the other time instance + * @return a formatted string to tell number of minutes left (as above) + */ + private String getMinutesText(LocalDateTime dateTime1, LocalDateTime dateTime2) { + Duration duration = Duration.between(dateTime1, dateTime2); + long minutesToDeadline = Math.abs(duration.toMinutes()); + + if (minutesToDeadline == 1){ + return VALUE_ONE_MINUTE; + } else { + return minutesToDeadline + WORD_SPACE + UNIT_MINUTES; + } + } + + public boolean isTomorrow(LocalDateTime dateTimeToday, LocalDateTime dateTimeTomorrow) { + LocalDate dayBefore = dateTimeToday.toLocalDate(); + LocalDate dayAfter = dateTimeTomorrow.toLocalDate(); + return dayBefore.plusDays(1).equals(dayAfter); + } + + public boolean isToday(LocalDateTime dateTime1, LocalDateTime dateTime2) { + LocalDate date1 = dateTime1.toLocalDate(); + LocalDate date2 = dateTime2.toLocalDate(); + return date1.equals(date2); + } + + private boolean isTonight(LocalDateTime dateTimeToday, LocalDateTime dateTimeTonight) { + return isToday(dateTimeToday, dateTimeTonight) + && dateTimeTonight.toLocalTime().isAfter(LocalTime.of(17, 59, 59)); + } + + private boolean isYesterday(LocalDateTime dateTimeToday, LocalDateTime dateTimeYesterday) { + return isTomorrow(dateTimeYesterday, dateTimeToday); + } + + private boolean isSameDay(LocalDateTime dateTime1, LocalDateTime dateTime2) { + return dateTime1.toLocalDate().equals(dateTime2.toLocalDate()); + } + + private boolean isSameYear(LocalDateTime dateTime1, LocalDateTime dateTime2) { + return dateTime1.getYear() == dateTime2.getYear(); + } + + public boolean isOverdue(LocalDateTime endTime) { + if (endTime == null) { + logger.log(Level.WARNING, "endTime in isOverdue(...) is null."); + return false; + } + return endTime.isBefore(now); + } + +``` +###### \java\seedu\todo\logic\commands\AddCommand.java +``` java + @Override + protected void validateArguments() { + validateTagArguments(); + } + + /** + * Helper method to validate tag arguments. + */ + private void validateTagArguments() { + if (tags.hasBoundValue()) { + String[] tagNames = StringUtil.split(tags.getValue()); + UniqueTagCollectionValidator validator = new UniqueTagCollectionValidator(tags.getName(), errors); + validator.validateAddTags(tagNames); + } + } +} +``` +###### \java\seedu\todo\logic\commands\CommandSummary.java +``` java + /** + * Re-represents this {@link CommandSummary} as an array of strings in the following format: + * [Scenario, Command, Arguments] + */ + public String[] toArray() { + return new String[] {scenario, command, arguments}; + } +} +``` +###### \java\seedu\todo\logic\commands\TagCommand.java +``` java +/** + * This class handles all tagging command + */ +public class TagCommand extends BaseCommand { + + /* Constants */ + private static final String ERROR_INCOMPLETE_PARAMETERS + = "The tag command is unable to recognise your commands."; + private static final String ERROR_TOO_MANY_PARAMETERS + = "You have supplied too many parameters."; + private static final String ERROR_INPUT_INDEX_REQUIRED + = "A task index is required."; + private static final String ERROR_INPUT_ADD_TAGS_REQUIRED + = "A list of tags \"tag1, tag2, ...\" to add is required."; + private static final String ERROR_INPUT_DELETE_TAGS_REQUIRED + = "A list of tags \"tag1, tag2, ...\" to delete is required."; + private static final String ERROR_INPUT_RENAME_TAGS_REQUIRED + = "Please provide an existing tag name and a new tag name."; + private static final String ERROR_TWO_PARAMS + = "You may only provide an existing tag name, and a new tag name for renaming."; + + private static final String SUCCESS_ADD_TAGS = " - tagged successfully"; + private static final String SUCCESS_DELETE_TAGS = " - removed successfully"; + private static final String SUCCESS_RENAME_TAGS = " renamed to "; + + private static final String MESSAGE_ENTER_TO_DISMISS = "Press [Enter] to dismiss."; + private static final String MESSAGE_NO_TAGS + = "You have no tags currently. Use the tag command to tag a task."; + + private static final String DESCRIPTION_SHOW_TAGS = "Shows all tags"; + private static final String DESCRIPTION_ADD_TAGS = "Add tags to a task"; + private static final String DESCRIPTION_DELETE_TAGS = "Remove tags"; + private static final String DESCRIPTION_RENAME_TAGS = "Rename a tag"; + + private static final String ARGUMENTS_SHOW_TAGS = ""; + private static final String ARGUMENTS_ADD_TAGS = "index tag1 [, tag2, ...]"; + private static final String ARGUMENTS_DELETE_TAGS = "[index] /d tag1 [, tag2, ...]"; + private static final String ARGUMENTS_RENAME_TAGS = "[index] /r old_tag_name new_tag_name"; + + private static final int INDEX_OFFSET = 1; + private static final int INDEX_RENAME_OLD_NAME = 0; + private static final int INDEX_RENAME_NEW_NAME = 1; + + /* Variables */ + private Argument index = new IntArgument("index"); + + private Argument addTags = new StringArgument("add"); + + private Argument deleteTags = new StringArgument("delete") + .flag("d"); + + private Argument renameTag = new StringArgument("rename") + .flag("r"); + + @Override + public Parameter[] getArguments() { + return new Parameter[] { + index, deleteTags, addTags, renameTag + }; + } + + /* Methods to get command argument for add tags */ + /** + * Partitions two flag-less parameters index and list of tags by overriding this method. + */ + @Override + protected void setPositionalArgument(String argument) { + String[] tokens = argument.trim().split(" ", 2); + getPossibleIndexForAddCommand(tokens[0]); + if (tokens.length == 2) { + getPossibleTagsForAddCommand(tokens[1]); + } + } + + /** + * If the input parameter resembles the add command with an index, extracts the index of the add command + * input. + */ + private void getPossibleIndexForAddCommand(String argument) { + boolean isFirstArgNumber = StringUtil.isUnsignedInteger(argument); + if (isFirstArgNumber) { + try { + index.setValue(argument); + } catch (IllegalValueException e) { + errors.put(index.getName(), e.getMessage()); + } + } + } + + /** + * If the input parameter resembles the add command with tag names, extracts the tag names of the add + * command input. + */ + private void getPossibleTagsForAddCommand(String argument) { + try { + addTags.setValue(argument); + } catch (IllegalValueException e) { + errors.put(addTags.getName(), e.getMessage()); + } + } + + /* Remaining Override Methods */ + @Override + public String getCommandName() { + return "tag"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of( + new CommandSummary(DESCRIPTION_SHOW_TAGS, getCommandName(), ARGUMENTS_SHOW_TAGS), + new CommandSummary(DESCRIPTION_ADD_TAGS, getCommandName(), ARGUMENTS_ADD_TAGS), + new CommandSummary(DESCRIPTION_DELETE_TAGS, getCommandName(), ARGUMENTS_DELETE_TAGS), + new CommandSummary(DESCRIPTION_RENAME_TAGS, getCommandName(), ARGUMENTS_RENAME_TAGS) + ); + } + + @Override + protected void validateArguments() { + if (!isInputParameterSufficient()) { + handleUnavailableInputParameters(); + } + validateRenameParams(); + validateRenameAllParams(); + super.validateArguments(); + } + + @Override + public CommandResult execute() throws ValidationException { + //Performs the actual execution with the data. Guarantees that only one action gets to run. + guaranteeOneActionExecutes(); + + CommandResult result = chooseFirstAvailable( + performShowTagsWhenApplicable(), + performAddTagToTaskWhenApplicable(), + performDeleteTagsFromTaskWhenApplicable(), + performDeleteTagsGloballyWhenApplicable(), + performRenameTagFromTaskWhenApplicable(), + performRenameTagWhenApplicable()); + + guaranteeCommandResultExists(result); //Just a sanity check. + + highlightTaskWhenComplete(); + return result; + } + + /* User Interface Helper Methods */ + /** + * Highlights the task when a task index is available. + * @throws ValidationException when the index is invalid. + */ + private void highlightTaskWhenComplete() throws ValidationException { + if (index.hasBoundValue()) { + int displayedIndex = index.getValue(); + eventBus.post(new HighlightTaskEvent(model.getTask(displayedIndex))); + eventBus.post(new ExpandCollapseTaskEvent(model.getTask(displayedIndex))); + } else { + //clear all current selections + eventBus.post(new HighlightTaskEvent(null)); + } + } + + /* Show Tags Methods */ + /** + * Returns true if the command parameters matches the action of show global tags + */ + private boolean isShowTags() { + return !index.hasBoundValue() && !addTags.hasBoundValue() && !deleteTags.hasBoundValue() + && !renameTag.hasBoundValue(); + } + + /** + * Performs the following execution if the command indicates so: + * Show a global list of tags + */ + private CommandResult performShowTagsWhenApplicable() { + if (isShowTags() && model.getGlobalTagsList().isEmpty()) { + return new CommandResult(MESSAGE_NO_TAGS); + + } else if (isShowTags()) { + ShowTagsEvent tagsEvent = new ShowTagsEvent(model.getGlobalTagsList()); + EventsCenter.getInstance().post(tagsEvent); + return new CommandResult(MESSAGE_ENTER_TO_DISMISS); + } + return null; + } + + /* Add Tags to Task Method */ + /** + * Returns true if the command parameters matches the action of adding tag(s) to a task. + */ + private boolean isAddTagsToTask() { + return index.hasBoundValue() && addTags.hasBoundValue() && !deleteTags.hasBoundValue() + && !renameTag.hasBoundValue(); + } + + /** + * Performs the following execution if the command indicates so: + * Adds Tags to a particular Task + */ + private CommandResult performAddTagToTaskWhenApplicable() throws ValidationException { + Integer displayedIndex = index.getValue(); + String[] tagsToAdd = StringUtil.split(addTags.getValue()); + + if (isAddTagsToTask()) { + model.addTagsToTask(displayedIndex, tagsToAdd); + ImmutableTask task = model.getObservableList().get(displayedIndex - INDEX_OFFSET); + eventBus.post(new HighlightTaskEvent(task)); + return new CommandResult(StringUtil.convertListToString(tagsToAdd) + SUCCESS_ADD_TAGS); + } + return null; + } + + /* Delete Tags from Task */ + /** + * Returns true if the command parameters matches the action of deleting tag(s) from a task. + */ + private boolean isDeleteTagsFromTask() { + return index.hasBoundValue() && !addTags.hasBoundValue() && deleteTags.hasBoundValue() + && !renameTag.hasBoundValue(); + } + + /** + * Performs the following execution if the command indicates so: + * Deletes tags from a task. + */ + private CommandResult performDeleteTagsFromTaskWhenApplicable() throws ValidationException { + Integer displayedIndex = index.getValue(); + String[] tagsToDelete = StringUtil.split(deleteTags.getValue()); + + if (isDeleteTagsFromTask()) { + model.deleteTagsFromTask(displayedIndex, tagsToDelete); + return new CommandResult(StringUtil.convertListToString(tagsToDelete) + SUCCESS_DELETE_TAGS); + } + return null; + } + + /* Delete Tags from All Tasks */ + /** + * Returns true if the command parameters matches the action of deleting tag(s) from all tasks. + */ + private boolean isDeleteTagsFromAllTasks() { + return !index.hasBoundValue() && !addTags.hasBoundValue() && deleteTags.hasBoundValue() + && !renameTag.hasBoundValue(); + } + + /** + * Performs the following execution if the command indicates so: + * Deletes tags from all tasks. + */ + private CommandResult performDeleteTagsGloballyWhenApplicable() throws ValidationException { + String[] tagsToDelete = StringUtil.split(deleteTags.getValue()); + + if (isDeleteTagsFromAllTasks()) { + model.deleteTags(tagsToDelete); + return new CommandResult(StringUtil.convertListToString(tagsToDelete) + SUCCESS_DELETE_TAGS); + } + return null; + } + + /* Rename Tag from a task */ + /** + * Returns true if the command parameter matches the action of renaming a tag from a task. + */ + private boolean isRenameTagFromTask() { + return index.hasBoundValue() && !addTags.hasBoundValue() && !deleteTags.hasBoundValue() + && renameTag.hasBoundValue(); + } + + /** + * Performs the following execution if the command indicates so: + * Rename a specific tag from a task. + */ + private CommandResult performRenameTagFromTaskWhenApplicable() throws ValidationException { + if (isRenameTagFromTask()) { + String[] renameTagsParam = StringUtil.split(renameTag.getValue()); + String oldName = renameTagsParam[INDEX_RENAME_OLD_NAME]; + String newName = renameTagsParam[INDEX_RENAME_NEW_NAME]; + model.renameTag(index.getValue(), oldName, newName); + return new CommandResult(oldName + SUCCESS_RENAME_TAGS + newName); + } + return null; + } + + /** + * Performs validation to rename params (check for sufficient parameters to rename). + */ + private void validateRenameParams() { + if (isRenameTagFromTask()) { + validateParameterForTwoItems(renameTag.getName(), renameTag.getValue()); + } + } + + /* Renaming Tags From All Tasks*/ + /** + * Returns true if the command parameters matches the action of renaming tags. + */ + private boolean isRenameTagOfAllTasks() { + return !index.hasBoundValue() && !addTags.hasBoundValue() && !deleteTags.hasBoundValue() + && renameTag.hasBoundValue(); + } + + /** + * Performs validation to rename params (check for sufficient parameters to rename). + */ + private void validateRenameAllParams() { + if (isRenameTagOfAllTasks()) { + validateParameterForTwoItems(renameTag.getName(), renameTag.getValue()); + } + } + + /** + * Performs the following execution if the command indicates so: + * Rename a specific tag globally. + */ + private CommandResult performRenameTagWhenApplicable() throws ValidationException { + if (isRenameTagOfAllTasks()) { + String[] renameTagsParam = StringUtil.split(renameTag.getValue()); + String oldName = renameTagsParam[INDEX_RENAME_OLD_NAME]; + String newName = renameTagsParam[INDEX_RENAME_NEW_NAME]; + model.renameTag(oldName, newName); + return new CommandResult(oldName + SUCCESS_RENAME_TAGS + newName); + } + return null; + } + + /* Command Validation Methods */ + /** + * Guarantees that only one action gets to run, otherwise throws {@link ValidationException}. + */ + private void guaranteeOneActionExecutes() throws ValidationException { + if (!isInputParameterSufficient()) { + throw new ValidationException(ERROR_INCOMPLETE_PARAMETERS); + } + } + + /** + * Guarantees that result is not null. Otherwise, throw an error {@link ValidationException}. + */ + private void guaranteeCommandResultExists(CommandResult result) throws ValidationException { + if (result == null) { + throw new ValidationException(ERROR_INCOMPLETE_PARAMETERS); + } + } + + /** + * Checks if the input parameters are sufficient, by checking if it matches any of the input case. + */ + private boolean isInputParameterSufficient() { + return getNumberOfTruth(isShowTags(), isAddTagsToTask(), isDeleteTagsFromTask(), + isDeleteTagsFromAllTasks(), isRenameTagOfAllTasks(), isRenameTagFromTask()) == 1; + } + + /** + * Validates the given {@code param} if there is exactly 2 items. + */ + private void validateParameterForTwoItems(String fieldName, String param) { + String[] params = StringUtil.split(param); + if (params == null || params.length != 2) { + errors.put(fieldName, ERROR_TWO_PARAMS); + } + } + + /** + * Sets error messages for insufficient input parameters, dependent on input parameters supplied. + */ + private void handleUnavailableInputParameters() { + boolean hasIndex = index.hasBoundValue(); + boolean hasAdd = addTags.hasBoundValue(); + boolean hasDelete = deleteTags.hasBoundValue(); + boolean hasRename = renameTag.hasBoundValue(); + + Boolean errorPlaced = chooseFirstTrue( + handleTooManyParameters(hasAdd, hasDelete, hasRename), + handleMissingIndex(hasIndex, hasAdd, hasRename), + handleMissingTagParams(hasIndex, hasAdd, hasDelete, hasRename)); + + handleGenericIncompleteParams(errorPlaced); + } + + /** + * Sets a generic error for the error bag for {@link #handleUnavailableInputParameters()} if specific + * error checks did not spot any param errors. + * @param errorPlaced a boolean value indicated whether an error has been placed already. + */ + private void handleGenericIncompleteParams(Boolean errorPlaced) { + if (errorPlaced != null && !errorPlaced) { + errors.put(ERROR_INCOMPLETE_PARAMETERS); + } + } + + /** + * Sets the error bag {@link #errors} if the parameters expects tags names as parameters but is missing. + * @return True when error is set. + */ + private boolean handleMissingTagParams(boolean hasIndex, boolean hasAdd, boolean hasDelete, boolean hasRename) { + if (hasIndex && !hasAdd && !hasDelete && !hasRename) { + errors.put(addTags.getName(), ERROR_INPUT_ADD_TAGS_REQUIRED); + errors.put(deleteTags.getName(), ERROR_INPUT_DELETE_TAGS_REQUIRED); + errors.put(renameTag.getName(), ERROR_INPUT_RENAME_TAGS_REQUIRED); + return true; + } + return false; + } + + /** + * Sets the error bag {@link #errors} if the parameters expects an index but is missing. + * @return True when error is set. + */ + private boolean handleMissingIndex(boolean hasIndex, boolean hasAdd, boolean hasRename) { + if ((hasAdd && !hasIndex) || (hasRename && !hasIndex)) { + errors.put(index.getName(), ERROR_INPUT_INDEX_REQUIRED); + return true; + } + return false; + } + + /** + * Sets the error bag {@link #errors} if the parameters have too many errors. + * @return True when error is set. + */ + private boolean handleTooManyParameters(boolean hasAdd, boolean hasDelete, boolean hasRename) { + if (getNumberOfTruth(hasAdd, hasDelete, hasRename) > 1) { + errors.put(ERROR_TOO_MANY_PARAMETERS); + return true; + } + return false; + } + + /* Helper Methods */ + /** + * Returns number of true booleans. + */ + private static int getNumberOfTruth(boolean... booleans) { + int count = 0; + for (boolean bool : booleans) { + if (bool) { + count += 1; + } + } + return count; + } + + /** + * Returns the first object that cause the {@code predicate} to be true. Otherwise, return null. + */ + @SuppressWarnings("unchecked") + private static T chooseFirst(Predicate predicate, T... objects) { + Optional optionalObject = Arrays.stream(objects).filter(predicate).findFirst(); + return optionalObject.isPresent() ? optionalObject.get() : null; + } + + /** + * Returns the first object that is available. Otherwise, return null. + */ + @SuppressWarnings("unchecked") + private static T chooseFirstAvailable(T... objects) { + return chooseFirst(t -> t != null, objects); + } + + /** + * Returns the first object that is true. Otherwise, return null. + */ + private static Boolean chooseFirstTrue(Boolean ... objects) { + return chooseFirst(aBoolean -> aBoolean != null && aBoolean, objects); + } +} +``` +###### \java\seedu\todo\model\ErrorBag.java +``` java + /** + * Merge the given error bag in {@code errorBag} to this instance of {@link ErrorBag}. + */ + public void merge(ErrorBag errorBag) { + this.nonFieldErrors.addAll(errorBag.getNonFieldErrors()); + this.fieldErrors.putAll(errorBag.getFieldErrors()); + } +} +``` +###### \java\seedu\todo\model\Model.java +``` java + /** + * Gets an immutable copy of the task + */ + ImmutableTask getTask(int displayedIndex) throws ValidationException; + + /** + * Gets a copy of the list of tags + */ + List getGlobalTagsList(); + + /** + * Adds the supplied list of tags (using tag names) to the specified task. + * Will do validation to the tags and throws error at the class using this method. + * + * @param index The task displayed index. + * @param tagNames The list of tag names to be added. + * @throws ValidationException when the given index is invalid, or the given {@code tagNames} contain + * illegal characters. + */ + void addTagsToTask(int index, String... tagNames) throws ValidationException; + + /** + * Adds the supplied list of tags (using tag names) to the specified task. + * Does not throw any validation error. Assumes that validation has been done at the command level. + * @param task The mutable task. + * @param tagNames The list of tag names to be added. + */ + void addTagsToTask(MutableTask task, String... tagNames); + + /** + * Deletes a list of tags (using tag names) from the specified task. + * + * @param index The task displayed index. + * @param tagNames The list of tag names to be deleted. + * @throws ValidationException when the given index is invalid, or when the tag is not found (no-op). + */ + void deleteTagsFromTask(int index, String... tagNames) throws ValidationException; + + /** + * Deletes the list of tags globally. + * + * @param tagNames The list of tag names to be deleted. + * @throws ValidationException when the tag is not found (no-op). + */ + void deleteTags(String... tagNames) throws ValidationException; + + /** + * Renames a tag across all tasks. + * @param oldName Name of the tag to be renamed. + * @param newName New name that the tag will assume. + * @throws ValidationException when the tag is not found, or the new name clashes with existing names. + */ + void renameTag(String oldName, String newName) throws ValidationException; + + /** + * Renames a tag for a particular task. + * @param index The task displayed index to be renamed. + * @param oldName Name of the tag to be renamed. + * @param newName New name that the tag will assume. + * @throws ValidationException when the tag is not found, or the new name clashes with existing names. + */ + void renameTag(int index, String oldName, String newName) throws ValidationException; +} +``` +###### \java\seedu\todo\model\tag\Tag.java +``` java +/** + * Represents a Tag in a task. + * + * Guarantees: immutable. To rename, a new Tag object must be created. (Renaming method is unsupported) + * + * However, since alphanumeric name is not critical to {@link Tag}, + * the validation is done at {@link seedu.todo.model.TodoModel} + */ +public class Tag { + /* Variables */ + //Stores a unique tag name, that is alphanumeric, and contains dashes and underscores. + private String tagName; + + /** + * Constructs a new tag with the given tag name. + * + * This class is intentional to be package private, so only {@link UniqueTagCollection} + * can construct new tags. + */ + public Tag(String name) { + this.tagName = name; + } + +``` +###### \java\seedu\todo\model\tag\UniqueTagCollection.java +``` java +/** + * A list of tags that enforces no nulls and uniqueness between its elements. + * Also supports minimal set of list operations for the app's features. + * + * Note: This class will disallow external access to {@link #uniqueTagsToTasksMap} so to + * maintain uniqueness of the tag names. + */ +public class UniqueTagCollection implements Iterable, UniqueTagCollectionModel { + + //Stores a list of tags with unique tag names. + private final Map> uniqueTagsToTasksMap = new HashMap<>(); + + /* Constructor */ + /** + * Constructs this tag collection + */ + public UniqueTagCollection(List globalTaskList) { + update(globalTaskList); + } + + /* Interfacing Methods */ + @Override + public void update(List globalTaskList) { + uniqueTagsToTasksMap.clear(); + globalTaskList.forEach(task -> task.getTags().forEach(tag -> associateTaskToTag(task, tag))); + } + + @Override + public void notifyTaskDeleted(ImmutableTask task) { + task.getTags().forEach(tag -> dissociateTaskFromTag(task, tag)); + } + + @Override + public Collection associateTaskToTags(ImmutableTask task, String[] tagNames) { + return Arrays.stream(tagNames) + .map(name -> associateTaskToTag(task, getTagWithName(name))) + .collect(Collectors.toSet()); + } + + @Override + public Collection dissociateTaskFromTags(ImmutableTask task, String[] tagNames) { + //Data validation at TodoModel should have checked if this is available. + //Even if it is not, getTagWithName(...) will recreate the tag which gets deleted again, safe op. + + return Arrays.stream(tagNames) + .map(name -> dissociateTaskFromTag(task, getTagWithName(name))) + .collect(Collectors.toSet()); + } + + @Override + public Collection deleteTags(String[] tagNames) { + //Data validation at TodoModel should have checked if this is available. + //Even if it is not, getTagWithName(...) will recreate the tag which gets deleted again, safe op. + + return Arrays.stream(tagNames) + .map(name -> { + Tag tag = getTagWithName(name); + uniqueTagsToTasksMap.remove(tag); + return tag; + }).collect(Collectors.toSet()); + } + + /* Helper Methods */ + /** + * Links a {@code task} to the {@code tag} in the {@link #uniqueTagsToTasksMap}. + * @return an instance of the {@code tag} + */ + private Tag associateTaskToTag(ImmutableTask task, Tag tag) { + Set setOfTasks = uniqueTagsToTasksMap.get(tag); + if (setOfTasks == null) { + setOfTasks = new HashSet<>(); + uniqueTagsToTasksMap.put(tag, setOfTasks); + } + setOfTasks.add(task); + return tag; + } + + /** + * Removes the association between the {@code task} from the {@code tag} in {@link #uniqueTagsToTasksMap}. + * If the tag do not have any associated task, the tag will be removed from the map. + * @return an instance of the {@code tag} + */ + private Tag dissociateTaskFromTag(ImmutableTask task, Tag tag) { + Set setOfTasks = uniqueTagsToTasksMap.get(tag); + if (setOfTasks != null) { + setOfTasks.remove(task); + + if (setOfTasks.isEmpty()) { + uniqueTagsToTasksMap.remove(tag); + } + } + return tag; + } + + /** + * Obtains an instance of {@link Tag} with the supplied {@code tagName} from the + * {@link #uniqueTagsToTasksMap}. + * + * Note: If such an instance is not found, a new {@link Tag} instance will be added to the + * {@link #uniqueTagsToTasksMap}. + * + * @param tagName The name of the {@link Tag}. + * @return A {@link Tag} object that has the name {@code tagName}. + */ + private Tag getTagWithName(String tagName) { + Optional possibleTag = uniqueTagsToTasksMap.keySet().stream() + .filter(tag -> tag.getTagName().equals(tagName)) + .findAny(); + + Tag targetTag; + if (possibleTag.isPresent()) { + targetTag = possibleTag.get(); + } else { + targetTag = new Tag(tagName); + uniqueTagsToTasksMap.put(targetTag, new HashSet<>()); + } + return targetTag; + } + + /** + * Simply finds a tag with the {@code tagName}. + */ + private Optional findTagWithName(String tagName) { + return uniqueTagsToTasksMap.keySet().stream() + .filter(tag -> tag.getTagName().equals(tagName)) + .findAny(); + } + + /* Interfacing Getters */ + @Override + public List getUniqueTagList() { + return new ArrayList<>(uniqueTagsToTasksMap.keySet()); + } + + @Override + public Set getTasksLinkedToTag(String tagName) { + Optional possibleTag = findTagWithName(tagName); + if (possibleTag.isPresent()) { + //We are getting a copy of the set of tasks. + Set tasks = uniqueTagsToTasksMap.get(possibleTag.get()); + return new HashSet<>(tasks); + } else { + return new HashSet<>(); + } + } + +``` +###### \java\seedu\todo\model\tag\UniqueTagCollectionModel.java +``` java +/** + * An interface that spells out the available methods for maintaining a unique tag list. + */ +public interface UniqueTagCollectionModel { + + /* Model Interfacing Methods*/ + /** + * Update the {@link UniqueTagCollectionModel} with the main to-do list {@code globalTaskList} + * stored in {@link seedu.todo.model.TodoModel}, for a new set of {@link Tag} + * + * @param globalTaskList To extract the unique list of {@link Tag}s from. + */ + void update(List globalTaskList); + + /** + * Notifies the {@link UniqueTagCollectionModel} that the given {@code task} is deleted, + * so that the {@link UniqueTagCollectionModel} can update the relations accordingly. + */ + void notifyTaskDeleted(ImmutableTask task); + + + /* Tag Command Interfacing Methods */ + /** + * Registers the given {@code task}s to a {@link Tag} in the {@link UniqueTagCollectionModel}. + * However, this method will not save the tags to the {@code task} since it is immutable. + * Note: that data validation is done at the {@link seedu.todo.model.Model} side. + * + * @param task The immutable task to be attached under the {@link Tag}. + * @param tagNames The list of tag names. + * @return Returns the corresponding collections of {@link Tag}s object with {@code tagName} + * so this tag can be added to the {@code task}.\ + */ + Collection associateTaskToTags(ImmutableTask task, String[] tagNames); + + + /* Old Tag Command Interfacing Methods */ + /** + * Unregisters the given {@code task} from the {@link Tag}s in the {@link UniqueTagCollectionModel}. + * This method will not remove the tags to the task since it is immutable. + * Also, data validation should be done at the {@link seedu.todo.model.Model} side. + * + * @param task The task to be detached from the {@link Tag}. + * @param tagNames The names of the {@link Tag} to be deleted. + * @return Returns the corresponding {@link Tag} object with {@code tagName} + * so this tag can be removed from the {@code task}. + */ + Collection dissociateTaskFromTags(ImmutableTask task, String[] tagNames); + + /** + * Deletes the list of tags with {@code tagNames} from the data structure. + * @return a collection of deleted tags. + */ + Collection deleteTags(String[] tagNames); + + /** + * Gets a copy of the list of tags. + */ + List getUniqueTagList(); + + /** + * Gets a copy of list of task associated with the {@link Tag} with the name {@code tagName} + */ + Set getTasksLinkedToTag(String tagName); +} +``` +###### \java\seedu\todo\model\tag\UniqueTagCollectionValidator.java +``` java +/** + * Handles the data validation for {@link UniqueTagCollection} in both + * {@link seedu.todo.logic.commands.AddCommand} and {@link seedu.todo.logic.commands.TagCommand} + */ +public class UniqueTagCollectionValidator { + + /* Constants */ + private static final int MAX_ALLOWED_TAGS = 5; + + private static final String YOU_SUPPLIED = " They are: "; + + private static final String ERROR_MAX_TAGS_ALLOWED = "You have added too many tags. " + + "Each task may have up to " + MAX_ALLOWED_TAGS + " tags."; + private static final String ERROR_TAGS_ILLEGAL_CHAR + = "Tags may only include alphanumeric characters, including dashes and underscores."; + private static final String ERROR_TAGS_TOO_LONG + = "Tags may only be at most 20 characters long."; + private static final String ERROR_TAGS_DUPLICATED + = "You might have keyed in duplicated tag names."; + private static final String ERROR_TAGS_DO_NOT_EXIST + = "The tag names you have entered do not exist."; + private static final String ERROR_TAGS_EXIST + = "The tag names you have entered are already in the task."; + private static final String ERROR_TAGS_EMPTY + = "You need to supply tag names."; + + private static final Pattern TAG_VALIDATION_REGEX = Pattern.compile("^[\\w\\d_-]+$"); + + /* Variables */ + private String parameterName; + private ErrorBag errorBag; + + /* Constructor */ + /** + * Constructs a validator with the name of the parameter {@code parameterName} and a reference to + * {@link ErrorBag} + */ + public UniqueTagCollectionValidator(String parameterName, ErrorBag errorBag) { + this.parameterName = parameterName; + this.errorBag = errorBag; + } + + /** + * Constructs a validator with the name of the parameter but without an external error bag. + */ + public UniqueTagCollectionValidator(String parameterName) { + this(parameterName, new ErrorBag()); + } + + /* Public Methods */ + /** + * Throws an exception if there is something in the error bag. + */ + public void throwsExceptionIfNeeded() throws ValidationException { + //Message not required. + errorBag.validate(""); + } + + /** + * Validates the add tags command for {@link seedu.todo.logic.commands.AddCommand}. + */ + public void validateAddTags(String[] tagNames) { + validateAddTags(null, tagNames); + } + + /** + * Validates the add command. + */ + public void validateAddTags(ImmutableTask task, String[] tagNames) { + validateIllegalNameChar(tagNames); + validateNameCharLimit(tagNames); + validateDuplicatedNameTag(tagNames); + validateNumberOfTags(task, tagNames); + validateTagNameMissing(tagNames); + validateTagNamesDoNotExist(task, tagNames); + } + + /** + * Validates the delete tag from task command. + */ + public void validateDeleteTags(ImmutableTask task, String[] tagNames) { + validateTagNamesExist(task, tagNames); + validateTagNameMissing(tagNames); + } + + /** + * Validates the delete tag globally command. + */ + public void validateDeleteTags(UniqueTagCollectionModel tagCollection, String[] tagNames) { + validateTagNamesExist(tagCollection.getUniqueTagList(), tagNames); + validateTagNameMissing(tagNames); + } + + /** + * Validates the rename tag from task command. + */ + public void validateRenameCommand(ImmutableTask task, String oldName, String newName) { + validateTagNamesExist(task.getTags(), oldName); + validateTagNamesDoNotExist(task.getTags(), newName); + validateIllegalNameChar(newName); + validateNameCharLimit(newName); + validateTagNameMissing(oldName, newName); + } + + /** + * Validates the global rename command. + */ + public void validateGlobalRenameCommand(UniqueTagCollectionModel tagCollection, String oldName, String newName) { + validateTagNamesExist(tagCollection.getUniqueTagList(), oldName); + validateTagNamesDoNotExist(tagCollection.getUniqueTagList(), newName); + validateIllegalNameChar(newName); + validateNameCharLimit(newName); + validateTagNameMissing(oldName, newName); + } + + /* Parameter Validation Helper */ + /** + * Checks if the total number of tags after adding is within {@link #MAX_ALLOWED_TAGS}. + */ + private void validateNumberOfTags(ImmutableTask task, String[] tagNames) { + Set tags = new HashSet<>(toLowerCaseList(tagNames)); + if (task != null) { + tags.addAll(Tag.getLowerCaseNames(task.getTags())); + } + + if (tags.size() > MAX_ALLOWED_TAGS) { + errorBag.put(parameterName, ERROR_MAX_TAGS_ALLOWED); + } + } + + /** + * Checks to ensure that given tag names are alphanumeric, which also can contain dashes and underscores. + */ + private void validateIllegalNameChar(String... tagNames) { + List possibleViolations = Arrays.stream(tagNames).filter(tagName -> !isValidTagName(tagName)) + .collect(Collectors.toList()); + + if (!possibleViolations.isEmpty()) { + errorBag.put(parameterName, ERROR_TAGS_ILLEGAL_CHAR + YOU_SUPPLIED + + StringUtil.convertIterableToString(possibleViolations)); + } + } + + /** + * Checks to ensure that the provided character limit is within 20 characters. + */ + private void validateNameCharLimit(String... tagNames) { + List possibleViolations = Arrays.stream(tagNames).filter(tagName -> tagName.length() > 20) + .collect(Collectors.toList()); + + if (!possibleViolations.isEmpty()) { + errorBag.put(parameterName, ERROR_TAGS_TOO_LONG + YOU_SUPPLIED + + StringUtil.convertIterableToString(possibleViolations)); + } + } + + /** + * Checks to ensure that given tag names have no duplicated entries. + */ + private void validateDuplicatedNameTag(String... tagNames) { + if (!CollectionUtil.elementsAreUnique(toLowerCaseList(tagNames))) { + errorBag.put(parameterName, ERROR_TAGS_DUPLICATED); + } + } + + /** + * Checks to ensure that tag names exist in the {@code tagPool}. + */ + private void validateTagNamesExist(Collection tagPool, String... tagNames) { + List missingTags = toLowerCaseList(tagNames); + missingTags.removeAll(Tag.getLowerCaseNames(tagPool)); + + if (!missingTags.isEmpty()) { + errorBag.put(parameterName, ERROR_TAGS_DO_NOT_EXIST + YOU_SUPPLIED + + StringUtil.convertIterableToString(missingTags)); + } + } + + /** + * Checks to ensure that tag names found in {@code task} exist in the {@code tagPool}. + */ + private void validateTagNamesExist(ImmutableTask task, String... tagNames) { + if (task != null) { + validateTagNamesExist(task.getTags(), tagNames); + } + } + + /** + * Checks to ensure that tag names do not exist in the {@code tagPool}. + */ + private void validateTagNamesDoNotExist(Collection tagPool, String... tagNames) { + if (!Collections.disjoint(Tag.getLowerCaseNames(tagPool), toLowerCaseList(tagNames))) { + errorBag.put(parameterName, ERROR_TAGS_EXIST + YOU_SUPPLIED + Arrays.toString(tagNames)); + } + } + + /** + * Checks to ensure that tag names found in {@code task} do not exist in the {@code tagPool}. + */ + private void validateTagNamesDoNotExist(ImmutableTask task, String... tagNames) { + if (task != null) { + validateTagNamesDoNotExist(task.getTags(), tagNames); + } + } + + /** + * Checks to prevent the case where the tag names provided are actually empty. + */ + private void validateTagNameMissing(String... tagNames) { + if (tagNames == null || tagNames.length == 0) { + errorBag.put(parameterName, ERROR_TAGS_EMPTY); + } + } + + /* Private Helper Methods */ + /** + * Returns true if a given string is a valid tag name (alphanumeric, can contain dashes and underscores) + * Originated from {@link Tag} + */ + private static boolean isValidTagName(String test) { + return TAG_VALIDATION_REGEX.matcher(test).matches(); + } + + /** + * Returns a list of tag names in lower case. + */ + private static List toLowerCaseList(String... tagNames) { + return Arrays.stream(tagNames) + .map(String::toLowerCase) + .collect(Collectors.toCollection(ArrayList::new)); + } + + /* Public Helper Methods */ + /** + * Automates the process for performing data validation. + * + * @param actionName Name of the Command action + * @param consumer The method call that performs the actual validation. + */ + public static UniqueTagCollectionValidator validate(String actionName, + Consumer consumer) throws ValidationException { + + UniqueTagCollectionValidator validator = new UniqueTagCollectionValidator(actionName); + consumer.accept(validator); + validator.throwsExceptionIfNeeded(); + return validator; + } +} +``` +###### \java\seedu\todo\model\TodoList.java +``` java + @Override + public void updateAll(Consumer update) throws ValidationException { + //Perform one round of validation first. + for (MutableTask task : tasks) { + ValidationTask validationTask = new ValidationTask(task); + update.accept(validationTask); + validationTask.validate(); + } + + //When there is no errors, actually do it. + tasks.forEach(update::accept); + } + +``` +###### \java\seedu\todo\model\TodoListModel.java +``` java + /** + * Updates every single task, visible and non-visible, that are found inside the {@link TodoListModel}. + * Refer to {@link #update(List, Consumer)} for more details on usage. + */ + void updateAll(Consumer update) throws ValidationException; + +``` +###### \java\seedu\todo\model\TodoModel.java +``` java + @Override + public List getGlobalTagsList() { + return uniqueTagCollection.getUniqueTagList(); + } + + @Override + public void addTagsToTask(int index, String... tagNames) throws ValidationException { + saveUndoState(); + addTagsToTaskHelper(index, tagNames); + } + + @Override + public void addTagsToTask(MutableTask task, String... tagNames) { + saveUndoState(); + //Do not perform validation (disallowed by Consumer interface). Perform actual tag adding. + addTagsToTaskHelper(task, tagNames); + } + + @Override + public void deleteTagsFromTask(int index, String... tagNames) throws ValidationException { + saveUndoState(); + deleteTagsFromTaskHelper(index, tagNames); + } + + @Override + public void deleteTags(String... tagNames) throws ValidationException { + saveUndoState(); + deleteTagsHelper(tagNames); + } + + @Override + public void renameTag(String oldName, String newName) throws ValidationException { + saveUndoState(); + renameTagHelper(oldName, newName); + } + + @Override + public void renameTag(int index, String oldName, String newName) throws ValidationException { + saveUndoState(); + renameTagFromTaskHelper(index, oldName, newName); + } + + /** + * Gets the {@link ImmutableTask} object at the respective displayed index. + * @throws ValidationException when the index is invalid. + */ + @Override + public ImmutableTask getTask(int displayedIndex) throws ValidationException { + int index = getTaskIndex(displayedIndex); + return tasks.get(index); + } + + /* Helper Method */ + /** + * Helper method that validate and handles tag adding operation + */ + private void addTagsToTaskHelper(int index, String... tagNames) + throws ValidationException { + int taskIndex = getTaskIndex(index); + ImmutableTask task = getTask(index); + + UniqueTagCollectionValidator + .validate("add tags", validator -> validator.validateAddTags(task, tagNames)); + + todoList.update(taskIndex, mutableTask -> { + assert mutableTask.equals(task); //Just a sanity check. + addTagsToTaskHelper(mutableTask, tagNames); + }); + } + + /** + * Helper method that handles tag adding operation without validation + */ + private void addTagsToTaskHelper(MutableTask task, String... tagNames) { + Set newTags = new HashSet<>(uniqueTagCollection.associateTaskToTags(task, tagNames)); + newTags.addAll(task.getTags()); + task.setTags(newTags); + } + + /** + * Helper method that validates and handles tag deletion from task operation + */ + private void deleteTagsFromTaskHelper(int index, String... tagNames) throws ValidationException { + int taskIndex = getTaskIndex(index); + ImmutableTask task = getTask(index); + + UniqueTagCollectionValidator + .validate("delete tags", validator -> validator.validateDeleteTags(task, tagNames)); + + todoList.update(taskIndex, mutableTask -> { + Set tagsFromTask = new HashSet<>(mutableTask.getTags()); + Collection deletedTags = uniqueTagCollection.dissociateTaskFromTags(mutableTask, tagNames); + tagsFromTask.removeAll(deletedTags); + mutableTask.setTags(tagsFromTask); + }); + } + + /** + * Helper method that validates and handles tag deletion operation + */ + private void deleteTagsHelper(String... tagNames) throws ValidationException { + UniqueTagCollectionValidator.validate("delete tags", + validator -> validator.validateDeleteTags(uniqueTagCollection, tagNames)); + + Collection deletedTags = uniqueTagCollection.deleteTags(tagNames); + todoList.updateAll(mutableTask -> { + Set tagsFromTask = new HashSet<>(mutableTask.getTags()); + tagsFromTask.removeAll(deletedTags); + mutableTask.setTags(tagsFromTask); + }); + } + + /** + * Helper method that validates and handles global tag renaming operation + */ + private void renameTagHelper(String oldName, String newName) throws ValidationException { + UniqueTagCollectionValidator.validate("rename tag", + validator -> validator.validateGlobalRenameCommand(uniqueTagCollection, oldName, newName)); + + Set tasksWithTag = uniqueTagCollection.getTasksLinkedToTag(oldName); + deleteTagsHelper(oldName); + + todoList.updateAll(mutableTask -> { + if (tasksWithTag.contains(mutableTask)) { + addTagsToTaskHelper(mutableTask, newName); + } + }); + } + + /** + * Helper method that validates and handles tag renaming operation + */ + private void renameTagFromTaskHelper(int index, String oldName, String newName) throws ValidationException { + ImmutableTask task = getTask(index); + + UniqueTagCollectionValidator.validate("rename tag", + validator -> validator.validateRenameCommand(task, oldName, newName)); + + deleteTagsFromTaskHelper(index, oldName); + addTagsToTaskHelper(index, newName); + } +} +``` +###### \java\seedu\todo\ui\controller\CommandController.java +``` java +/** + * Processes the input command from {@link CommandInputView}, pass it to {@link seedu.todo.logic.Logic} + * and hands the {@link seedu.todo.logic.commands.CommandResult} to {@link CommandFeedbackView} and + * {@link CommandErrorView}. + * Also, {@link CommandPreviewView} is also linked here to listen to user input. + */ +public class CommandController { + /* Variables */ + private Logic logic; + private CommandInputView inputView; + private CommandPreviewView previewView; + private CommandFeedbackView feedbackView; + private CommandErrorView errorView; + + /* Private Constructor */ + private CommandController() {} + + /** + * Constructs a link between the classes defined in the parameters. + */ + public static CommandController constructLink(Logic logic, CommandInputView inputView, + CommandPreviewView previewView, CommandFeedbackView feedbackView, CommandErrorView errorView) { + + CommandController controller = new CommandController(); + controller.logic = logic; + controller.inputView = inputView; + controller.previewView = previewView; + controller.feedbackView = feedbackView; + controller.errorView = errorView; + controller.start(); + return controller; + } + + /** + * Asks {@link #inputView} to start listening for a new key strokes. + * Once the callback returns a command, {@link #handleInput(KeyCode, String)} will process the input. + */ + private void start() { + inputView.listenToInput(this::handleInput); + } + +``` +###### \java\seedu\todo\ui\controller\CommandController.java +``` java + /** + * Displays error in the respective UI elements + * @param errorBag group of errors to display + */ + private void viewDisplayError(ErrorBag errorBag) { + inputView.flagError(); + feedbackView.flagError(); + errorView.displayErrors(errorBag); + } + + /** + * Displays success behaviour in the respective UI elements + */ + private void viewDisplaySuccess() { + inputView.resetViewState(); + feedbackView.unFlagError(); + errorView.hideCommandErrorView(); + } + + /** + * Displays a message with regards to the user's input + * @param message to be displayed to user + */ + private void displayMessage(String message) { + feedbackView.displayMessage(message); + } +} +``` +###### \java\seedu\todo\ui\UiManager.java +``` java + @Subscribe + private void handleExpandCollapseTaskEvent(ExpandCollapseTaskEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getTodoListView().toggleExpandCollapsed(event.task, event.toCollapse); + } + + @Subscribe + private void handleShowHelpEvent(ShowHelpEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getHelpView().displayCommandSummaries(event.getCommandSummaries()); + } + +``` +###### \java\seedu\todo\ui\UiManager.java +``` java + @Subscribe + private void handleCommandInputEnterEvent(CommandInputEnterEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getHelpView().hideHelpPanel(); + mainWindow.getGlobalTagView().hideGlobalTagViewPanel(); + mainWindow.getCommandFeedbackView().clearMessage(); + } + + @Subscribe + private void handleHighlightTaskEvent(HighlightTaskEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getTodoListView().scrollAndSelect(event.getTask()); + } + +``` +###### \java\seedu\todo\ui\util\FxViewUtil.java +``` java +/** + * Contains generic utility methods for JavaFX views + */ +public class FxViewUtil { + +``` +###### \java\seedu\todo\ui\util\FxViewUtil.java +``` java + /** + * Applies an anchor to the left, right and bottom boundary of {@code node}. + */ + public static void applyAnchorBoundaryParameters(Node node, double left, double right, double bottom) { + AnchorPane.setLeftAnchor(node, left); + AnchorPane.setRightAnchor(node, right); + AnchorPane.setBottomAnchor(node, bottom); + } + + /** + * Hides a specified UI element, and ensures that it does not occupy any space. + */ + public static void setCollapsed(Node node, boolean isCollapsed) { + node.setVisible(!isCollapsed); + node.setManaged(!isCollapsed); + } + + /** + * Set the text to UI element when available, collapse the UI element when not. + */ + public static void displayTextWhenAvailable(Label labelToDisplay, Node nodeToHide, + Optional optionalString) { + if (optionalString.isPresent()) { + labelToDisplay.setText(optionalString.get()); + } else { + labelToDisplay.setText(""); + setCollapsed(nodeToHide, true); + } + } + + /** + * Sets a recurring task on the UI specified in handler to repeat every specified seconds. + * Does not start until the user executes .play() + * @param seconds duration between each repeats + * @param handler method to run is specified here + * @return {@link Timeline} object to run. + */ + public static Timeline setRecurringUiTask(int seconds, EventHandler handler) { + Timeline recurringTask = new Timeline(new KeyFrame(Duration.seconds(seconds), handler)); + recurringTask.setCycleCount(Timeline.INDEFINITE); + return recurringTask; + } + + /** + * Converts an index from a list to the index that is displayed to the user via the Ui + */ + public static int convertToListIndex(int uiIndex) { + return uiIndex - 1; + } + + /** + * Converts an index displayed on the Ui to the user, to the index used on the list + */ + public static int convertToUiIndex(int listIndex) { + return listIndex + 1; + } +} +``` +###### \java\seedu\todo\ui\util\UiPartLoaderUtil.java +``` java + /** + * Attaches only one children view element to a specified placeholder. + * However, if either one of the params is null, it will result in no-op. + * Also, if there are any other children in the placeholder, they will be cleared first. + * + * @param placeholder to add the childrenView to, no-op if null + * @param childrenView to be attached to the placeholder, no-op if null + */ + private static void attachToPlaceholder(AnchorPane placeholder, Node childrenView) { + if (placeholder != null && childrenView != null) { + ObservableList placeholderChildren = placeholder.getChildren(); + placeholderChildren.clear(); + placeholderChildren.add(childrenView); + } + } +} +``` +###### \java\seedu\todo\ui\util\ViewGeneratorUtil.java +``` java +/** + * A utility class that generates commonly used UI elements, such as Labels. + */ +public class ViewGeneratorUtil { + + /** + * Generates a {@link Text} object with the class style applied onto the object. + * @param string to be wrapped in the {@link Text} object + * @param classStyle css style to be applied to the label + * @return a {@link Text} object + */ + public static Text constructText(String string, String classStyle) { + Text text = new Text(string); + ViewStyleUtil.addClassStyles(text, classStyle); + return text; + } + + /** + * Constructs a label view with a dark grey rounded background. + */ + public static Label constructRoundedText(String string) { + Label label = constructLabel(string, "roundLabel"); + label.setPadding(new Insets(0, 8, 0, 8)); + return label; + } + + /** + * Generates a {@link Label} object with the class style applied onto the object. + * @param string to be wrapped in the {@link Label} object + * @param classStyle css style to be applied to the label + * @return a {@link Label} object + */ + public static Label constructLabel(String string, String classStyle) { + Label label = new Label(string); + ViewStyleUtil.addClassStyles(label, classStyle); + return label; + } + + /** + * Place all the specified texts into a {@link TextFlow} object. + */ + public static TextFlow placeIntoTextFlow(Text... texts) { + return new TextFlow(texts); + } +} +``` +###### \java\seedu\todo\ui\util\ViewStyleUtil.java +``` java +/** + * Deals with the CSS styling of View elements + */ +public class ViewStyleUtil { + + /* Style Classes Constants */ + public static final String STYLE_COLLAPSED = "collapsed"; + public static final String STYLE_COLLAPSIBLE = "collapsible"; + public static final String STYLE_COMPLETED = "completed"; + public static final String STYLE_OVERDUE = "overdue"; + public static final String STYLE_SELECTED = "selected"; + public static final String STYLE_TEXT_4 = "text4"; + public static final String STYLE_ERROR = "error"; + public static final String STYLE_CODE = "code"; + public static final String STYLE_CODE_BOLDER = "codeBolder"; + public static final String STYLE_UNDERLINE = "underline"; + public static final String STYLE_ONGOING = "ongoing"; + + /*Static Helper Methods*/ + /** + * Adds only one instance of all the class styles to the node object + * @param node view object to add the class styles to + * @param classStyles all the class styles that is to be added to the node + */ + public static void addClassStyles(Node node, String... classStyles) { + for (String classStyle : classStyles) { + addClassStyle(node, classStyle); + } + } + + /** + * Remove all instance of all the class styles to the node object + * @param node view object to add the class styles to + * @param classStyles all the class styles that is to be removed from the node + */ + public static void removeClassStyles(Node node, String... classStyles) { + for (String classStyle : classStyles) { + removeClassStyle(node, classStyle); + } + } + + /** + * Adds or removes class style based on a boolean parameter + * @param isAdding true to add, false to remove + * @param node view object to add the class styles to + * @param classStyles all the class styles that is to be added to/removed from the node + */ + public static void addRemoveClassStyles(boolean isAdding, Node node, String... classStyles) { + for (String classStyle : classStyles) { + if (isAdding) { + addClassStyles(node, classStyle); + } else { + removeClassStyles(node, classStyle); + } + } + } + + /** + * Toggles one style class to the node: + * If supplied style class is available, remove it. Else, add one instance of it. + * + * @return true if toggled from OFF -> ON + */ + public static boolean toggleClassStyle(Node node, String classStyle) { + boolean wasPreviouslyOff = !node.getStyleClass().contains(classStyle); + if (wasPreviouslyOff) { + addClassStyles(node, classStyle); + } else { + removeClassStyles(node, classStyle); + } + return wasPreviouslyOff; + } + + /* Private Helper Methods */ + /** + * Adds only one instance of a single class style to the node object + */ + private static void addClassStyle(Node node, String classStyle) { + if (!node.getStyleClass().contains(classStyle)) { + node.getStyleClass().add(classStyle); + } + } + + /** + * Removes all instances of a single class style from the node object + */ + private static void removeClassStyle(Node node, String classStyle) { + while (node.getStyleClass().contains(classStyle)) { + node.getStyleClass().remove(classStyle); + } + } +} +``` +###### \java\seedu\todo\ui\view\CommandErrorView.java +``` java +/** + * A view class that displays specific command errors in greater detail. + */ +public class CommandErrorView extends UiPart { + /* Constants */ + private static final String FXML = "CommandErrorView.fxml"; + + /* Layouts */ + private AnchorPane placeholder; + private VBox errorViewBox; + @FXML private VBox nonFieldErrorBox; + @FXML private VBox fieldErrorBox; + @FXML private GridPane nonFieldErrorGrid; + @FXML private GridPane fieldErrorGrid; + +``` +###### \java\seedu\todo\ui\view\CommandErrorView.java +``` java + /** + * Configure the UI layout of {@link CommandErrorView} + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(errorViewBox, 0.0, 0.0, 0.0); + } + + /** + * Displays both field and non-field errors to the user + * @param errorBag that contains both field and non-field errors + */ + public void displayErrors(ErrorBag errorBag) { + showCommandErrorView(); + clearOldErrorsFromViews(); + displayFieldErrors(errorBag.getFieldErrors()); + displayNonFieldErrors(errorBag.getNonFieldErrors()); + } + + /** + * Feeds non field errors to the {@link #nonFieldErrorGrid}. + * If there are no non-field errors, then {@link #nonFieldErrorBox} will be hidden. + * @param nonFieldErrors that stores a list of non-field errors + */ + private void displayNonFieldErrors(List nonFieldErrors) { + if (nonFieldErrors.isEmpty()) { + hideErrorBox(nonFieldErrorBox); + } else { + int rowCounter = 0; + for (String error : nonFieldErrors) { + addRowToGrid(nonFieldErrorGrid, rowCounter++, rowCounter + ".", error); + } + } + } + + /** + * Feeds field errors to the {@link #fieldErrorGrid}. + * If there are no field errors, then {@link #fieldErrorBox} will be hidden. + * @param fieldErrors that stores the field errors + */ + private void displayFieldErrors(Map fieldErrors) { + if (fieldErrors.isEmpty()) { + hideErrorBox(fieldErrorBox); + } else { + int rowCounter = 0; + for (Map.Entry fieldError : fieldErrors.entrySet()) { + addRowToGrid(fieldErrorGrid, rowCounter++, fieldError.getKey(), fieldError.getValue()); + } + } + } + + /* Override Methods */ + @Override + public void setPlaceholder(AnchorPane placeholder) { + this.placeholder = placeholder; + } + + @Override + public void setNode(Node node) { + this.errorViewBox = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + /*Helper Methods*/ + /** + * Adds a row of text to the targetGrid + * @param targetGrid to add a row of text on + * @param rowIndex which row to add this row of text + * @param leftText text for the first column + * @param rightText text for the second column + */ + private void addRowToGrid(GridPane targetGrid, int rowIndex, String leftText, String rightText) { + Label leftLabel = ViewGeneratorUtil.constructLabel(leftText, ViewStyleUtil.STYLE_TEXT_4); + Label rightLabel = ViewGeneratorUtil.constructLabel(rightText, ViewStyleUtil.STYLE_TEXT_4); + rightLabel.setWrapText(true); + targetGrid.addRow(rowIndex, leftLabel, rightLabel); + } + + /** + * Clears all elements in the given grid. + */ + private void clearGrid(GridPane gridPane) { + gridPane.getChildren().clear(); + } + + /** + * Hides a field or non-field error box. + * @param vBox can be either {@link #fieldErrorBox} or {@link #nonFieldErrorBox} + */ + private void hideErrorBox(VBox vBox) { + FxViewUtil.setCollapsed(vBox, true); + } + + /** + * Shows a field or non-field error box. + * @param vBox can be either {@link #fieldErrorBox} or {@link #nonFieldErrorBox} + */ + private void showErrorBox(VBox vBox) { + FxViewUtil.setCollapsed(vBox, false); + } + + /** + * Hides the entire {@link CommandErrorView} + */ + public void hideCommandErrorView() { + FxViewUtil.setCollapsed(placeholder, true); + } + + /** + * Displays the entire {@link CommandErrorView} + */ + private void showCommandErrorView() { + FxViewUtil.setCollapsed(placeholder, false); + } + + /** + * Clears previous errors from the grid, and then unhide all the error boxes. + */ + private void clearOldErrorsFromViews() { + clearGrid(nonFieldErrorGrid); + clearGrid(fieldErrorGrid); + showErrorBox(nonFieldErrorBox); + showErrorBox(fieldErrorBox); + } +} +``` +###### \java\seedu\todo\ui\view\CommandFeedbackView.java +``` java +/** + * Display textual feedback to command input via this view with {@link #displayMessage(String)}. + */ +public class CommandFeedbackView extends UiPart { + /* Constants */ + private static final String FXML = "CommandFeedbackView.fxml"; + + /* Variables */ + private final Logger logger = LogsCenter.getLogger(CommandFeedbackView.class); + + /* Layout Elements */ + @FXML private Label commandFeedbackLabel; + private AnchorPane textContainer; + +``` +###### \java\seedu\todo\ui\view\CommandFeedbackView.java +``` java + /* Interfacing Methods */ + /** + * Displays a message onto the {@link #commandFeedbackLabel}. + * @param message The feedback message to be shown to the user. + */ + public void displayMessage(String message) { + commandFeedbackLabel.setText(message); + } + + /** + * Clears any message in {@link #commandFeedbackLabel}. + */ + public void clearMessage() { + commandFeedbackLabel.setText(""); + } + + /** + * Indicate an error visually on the {@link #commandFeedbackLabel}. + */ + public void flagError() { + ViewStyleUtil.addClassStyles(commandFeedbackLabel, ViewStyleUtil.STYLE_ERROR); + } + + /** + * Remove the error flag visually on the {@link #commandFeedbackLabel}. + */ + public void unFlagError() { + ViewStyleUtil.removeClassStyles(commandFeedbackLabel, ViewStyleUtil.STYLE_ERROR); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + this.textContainer = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java +/** + * A view class that handles the Input text box directly. + */ +public class CommandInputView extends UiPart { + /* Constants */ + private static final String FXML = "CommandInputView.fxml"; + + /* Layout Elements */ + private AnchorPane commandInputPane; + @FXML private TextArea commandTextField; + +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java + /** + * Configure the UI properties of {@link CommandInputView} + */ + private void configureProperties() { + setCommandInputHeightAutoResizeable(); + unflagErrorWhileTyping(); + listenAndRaiseEnterEvent(); + } + +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java + /** + * Listens for Enter keystrokes, and raises an event when it happens. + */ + public void listenAndRaiseEnterEvent() { + this.commandTextField.addEventHandler(KeyEvent.KEY_PRESSED, event -> { + if (event.getCode() == KeyCode.ENTER) { + EventsCenter.getInstance().post(new CommandInputEnterEvent()); + event.consume(); //To prevent commandTextField from printing a new line. + } + }); + } + + /* UI Methods */ + /** + * Resets the text box by {@link #unflagError()} after a key is pressed + */ + private void unflagErrorWhileTyping() { + this.commandTextField.addEventFilter(KeyEvent.KEY_PRESSED, event -> unflagError()); + } + + /** + * Allow {@link #commandTextField} to adjust automatically with the height of the content of the + * text area itself. + */ + private void setCommandInputHeightAutoResizeable() { + new TextAreaResizer(commandTextField); + } + + /** + * Resets the state of the text box, by clearing the text box and clear the errors. + */ + public void resetViewState() { + unflagError(); + clear(); + } + + /** + * Indicate an error visually on the {@link #commandTextField} + */ + public void flagError() { + ViewStyleUtil.addClassStyles(commandTextField, ViewStyleUtil.STYLE_ERROR); + } + + /** + * Remove the error flag visually on the {@link #commandTextField} + */ + private void unflagError() { + ViewStyleUtil.removeClassStyles(commandTextField, ViewStyleUtil.STYLE_ERROR); + } + + /** + * Clears the texts inside the text box + */ + private void clear() { + commandTextField.clear(); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + commandInputPane = (AnchorPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + +``` +###### \java\seedu\todo\ui\view\EmptyListView.java +``` java +/** + * Displays the message when {@link TodoListView} is empty. + */ +public class EmptyListView extends UiPart { + + /* Constants */ + private static final String FXML = "EmptyListView.fxml"; + + private static final String EMPTY_MESSAGE_DEFAULT + = "Hello. Type 'add' to add a new task or event."; + private static final String EMPTY_MESSAGE_COMPLETED + = "You have no completed tasks and past events."; + private static final String EMPTY_MESSAGE_INCOMPLETE + = "You have no incomplete tasks and upcoming events."; + private static final String EMPTY_MESSAGE_DUE_SOON + = "You have no tasks due soon."; + private static final String EMPTY_MESSAGE_EVENTS + = "You have no upcoming events."; + private static final String EMPTY_MESSAGE_TODAY + = "You have no tasks and events for today."; + + private static final String EMOJI_DEFAULT = "/images/emoji-default.png"; + private static final String EMOJI_COMPLETED = "/images/emoji-completed.png"; + private static final String EMOJI_INCOMPLETE = "/images/emoji-incomplete.png"; + private static final String EMOJI_EVENTS = "/images/emoji-events.png"; + private static final String EMOJI_DUESOON = "/images/emoji-duesoon.png"; + private static final String EMOJI_TODAY = "/images/emoji-today.png"; + + /*Layouts*/ + private AnchorPane emptyListPlaceholder; + @FXML private VBox emptyListView; + @FXML private ImageView emptyListImage; + @FXML private Label emptyListLabel; + + /** + * Loads and initialise the {@link #emptyListView} to the {@code placeholder}. + * @param primaryStage of the application + * @param placeholder where the view element {@link #emptyListView} should be placed + * @param todoList for checking whether the list is empty. + * @param viewProperty the view that the user is at. + * @return an instance of this class + */ + public static EmptyListView load(Stage primaryStage, AnchorPane placeholder, + ObservableList todoList, ObjectProperty viewProperty) { + + EmptyListView emptyListView = UiPartLoaderUtil + .loadUiPart(primaryStage, placeholder, new EmptyListView()); + + emptyListView.configureLayout(); + emptyListView.setEmptyListConnections(todoList, viewProperty); + return emptyListView; + } + + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(emptyListView, 0.0, 0.0, 0.0, 0.0); + } + + + /* Empty List View Methods */ + /** + * Configures the {@link EmptyListView} with the {@code todoList} property and {@code viewProperty} + * property. + */ + private void setEmptyListConnections(ObservableList todoList, + ObjectProperty viewProperty) { + + ListChangeListener visibilityUpdate + = c -> setEmptyListViewVisibility(todoList.isEmpty()); + + ChangeListener viewUpdate + = (observable, oldValue, newValue) -> setEmptyListContent(newValue); + + todoList.addListener(visibilityUpdate); + visibilityUpdate.onChanged(null); + + viewProperty.addListener(viewUpdate); + viewUpdate.changed(null, null, viewProperty.get()); + } + + /** + * Displays the {@link #emptyListView} if {@code isVisible} is true, hides otherwise. + */ + private void setEmptyListViewVisibility(boolean isVisible) { + FxViewUtil.setCollapsed(emptyListPlaceholder, !isVisible); + } + + /** + * Sets the content of empty list based on {@link TaskViewFilter} + */ + private void setEmptyListContent(TaskViewFilter filter) { + if (filter == TaskViewFilter.COMPLETED) { + setEmptyListContent(EMPTY_MESSAGE_COMPLETED, EMOJI_COMPLETED); + + } else if (filter == TaskViewFilter.INCOMPLETE) { + setEmptyListContent(EMPTY_MESSAGE_INCOMPLETE, EMOJI_INCOMPLETE); + + } else if (filter == TaskViewFilter.DUE_SOON) { + setEmptyListContent(EMPTY_MESSAGE_DUE_SOON, EMOJI_DUESOON); + + } else if (filter == TaskViewFilter.EVENTS) { + setEmptyListContent(EMPTY_MESSAGE_EVENTS, EMOJI_EVENTS); + + } else if (filter == TaskViewFilter.TODAY) { + setEmptyListContent(EMPTY_MESSAGE_TODAY, EMOJI_TODAY); + + } else { + setEmptyListContent(EMPTY_MESSAGE_DEFAULT, EMOJI_DEFAULT); + } + } + + /** + * Sets the content of empty list by {@code message} and {@code imageUrl} + */ + private void setEmptyListContent(String message, String imageUrl) { + emptyListLabel.setText(message); + emptyListImage.setImage(new Image(imageUrl)); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + this.emptyListView = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + @Override + public void setPlaceholder(AnchorPane placeholder) { + this.emptyListPlaceholder = placeholder; + } +} +``` +###### \java\seedu\todo\ui\view\FilterBarView.java +``` java +/** + * Shows a row of filter categories via {@link TaskViewFilter} + * to filter the tasks in {@link TodoListView} + */ +public class FilterBarView extends UiPart { + /* Constants */ + private final Logger logger = LogsCenter.getLogger(FilterBarView.class); + private static final String FXML = "FilterBarView.fxml"; + + /* Layout Views */ + private FlowPane filterViewPane; + + /* Variables */ + private Map taskFilterBoxesMap = new HashMap<>(); + + /* Layout Initialisation */ + /** + * Loads and initialise the {@link #filterViewPane} to the {@link seedu.todo.ui.MainWindow} + * @param primaryStage of the application + * @param placeholder where the view element {@link #filterViewPane} should be placed + * @return an instance of this class + */ + public static FilterBarView load(Stage primaryStage, AnchorPane placeholder, + ObservableValue filter) { + + FilterBarView filterView = UiPartLoaderUtil + .loadUiPart(primaryStage, placeholder, new FilterBarView()); + filterView.configureLayout(); + filterView.configureProperties(); + filterView.bindListener(filter); + return filterView; + } + + /** + * Configure the UI layout of {@link FilterBarView} + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(filterViewPane, 0.0, 0.0, 0.0, 0.0); + } + + /** + * Initialise and configure the UI properties of {@link FilterBarView} + */ + private void configureProperties() { + initialiseAllViewFilters(); + selectOneViewFilter(TaskViewFilter.DEFAULT); + } + + /** + * Display all the {@link TaskViewFilter} on the {@link #filterViewPane} + */ + private void initialiseAllViewFilters() { + for (TaskViewFilter filter : TaskViewFilter.all()) { + appendEachViewFilter(filter); + } + } + + /** + * Add one {@link TaskViewFilter} on the {@link #filterViewPane} + * and save an instance to the {@link #taskFilterBoxesMap} + * @param filter to add onto the pane + */ + private void appendEachViewFilter(TaskViewFilter filter) { + HBox textContainer = constructViewFilterBox(filter); + taskFilterBoxesMap.put(filter, textContainer); + filterViewPane.getChildren().add(textContainer); + } + + /** + * Given a filter, construct a view element to be displayed on the {@link #filterViewPane} + * @param filter to be displayed + * @return a view element + */ + private HBox constructViewFilterBox(TaskViewFilter filter) { + String filterName = WordUtils.capitalize(filter.name); + String[] partitionedText = StringUtil.partitionStringAtPosition(filterName, filter.shortcutCharPosition); + + Label leftLabel = new Label(partitionedText[0]); + Label centreLabel = new Label(partitionedText[1]); + Label rightLabel = new Label(partitionedText[2]); + ViewStyleUtil.addClassStyles(centreLabel, ViewStyleUtil.STYLE_UNDERLINE); + + HBox textContainer = new HBox(leftLabel, centreLabel, rightLabel); + textContainer.getStyleClass().add("viewFilterItem"); + return textContainer; + } + + /** + * Binds this component with the {@link TaskViewFilter} property it listens to + */ + private void bindListener(ObservableValue filter) { + filter.addListener((observable, oldValue, newValue) -> selectOneViewFilter(newValue)); + } + + /** + * Select exactly one filter from {@link #filterViewPane} + */ + public void selectOneViewFilter(TaskViewFilter filter) { + clearAllViewFiltersSelection(); + selectViewFilter(filter); + } + + /* Helper Methods */ + /** + * Clears all selection from the {@link #filterViewPane} + */ + private void clearAllViewFiltersSelection() { + for (HBox filterBox : taskFilterBoxesMap.values()) { + ViewStyleUtil.removeClassStyles(filterBox, ViewStyleUtil.STYLE_SELECTED); + } + } + + /** + * Mark the filter as selected on {@link #filterViewPane} + * However, if filter is null, nothing is done. + */ + private void selectViewFilter(TaskViewFilter filter) { + if (filter != null) { + HBox filterBox = taskFilterBoxesMap.get(filter); + ViewStyleUtil.addClassStyles(filterBox, ViewStyleUtil.STYLE_SELECTED); + } + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + this.filterViewPane = (FlowPane) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\seedu\todo\ui\view\HelpView.java +``` java +/** + * A view that displays all the help commands in a single view. + */ +public class HelpView extends UiPart { + + /* Constants */ + private static final String FXML = "HelpView.fxml"; + private static final int PREF_WIDTH = 340; + + /* Variables */ + private final Logger logger = LogsCenter.getLogger(HelpView.class); + + /*Layouts*/ + private VBox helpPanelView; + @FXML private GridPane helpGrid; + + /** + * Loads and initialise the feedback view element to the placeHolder + * @param primaryStage of the application + * @param placeholder where the view element {@link #helpPanelView} should be placed + * @return an instance of this class + */ + public static HelpView load(Stage primaryStage, AnchorPane placeholder) { + HelpView helpView = UiPartLoaderUtil.loadUiPart(primaryStage, placeholder, new HelpView()); + helpView.configureLayout(); + helpView.hideHelpPanel(); + return helpView; + } + + /** + * Configure the UI layout of {@link CommandErrorView} + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(helpPanelView, 0.0, 0.0, 0.0); + } + + /** + * Displays a list of commands into the helpPanelView + */ + public void displayCommandSummaries(List commandSummaries) { + this.showHelpPanel(); + helpGrid.getChildren().clear(); + int rowIndex = 0; + for (CommandSummary commandSummary : commandSummaries) { + appendCommandSummary(rowIndex++, commandSummary); + } + } + + /** + * Add a command summary to each row of the helpGrid + * @param rowIndex the row number to which the command summary should append to + * @param commandSummary to be displayed + */ + private void appendCommandSummary(int rowIndex, CommandSummary commandSummary) { + Text commandScenario = ViewGeneratorUtil + .constructText(commandSummary.scenario, ViewStyleUtil.STYLE_TEXT_4); + Text commandName = ViewGeneratorUtil + .constructText(commandSummary.command, ViewStyleUtil.STYLE_TEXT_4); + Text commandArgument = ViewGeneratorUtil + .constructText(" " + commandSummary.arguments, ViewStyleUtil.STYLE_TEXT_4); + + ViewStyleUtil.addClassStyles(commandArgument, ViewStyleUtil.STYLE_CODE); + ViewStyleUtil.addClassStyles(commandName, ViewStyleUtil.STYLE_CODE_BOLDER); + TextFlow combinedCommand = ViewGeneratorUtil.placeIntoTextFlow(commandName, commandArgument); + combinedCommand.setPrefWidth(PREF_WIDTH); + + helpGrid.addRow(rowIndex, commandScenario, combinedCommand); + } + + /* Ui Methods */ + public void hideHelpPanel() { + FxViewUtil.setCollapsed(helpPanelView, true); + } + + private void showHelpPanel() { + FxViewUtil.setCollapsed(helpPanelView, false); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + this.helpPanelView = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\seedu\todo\ui\view\TaskCardView.java +``` java +/** + * This class links up with TaskCardView.fxml layout to display details of a given + * ReadOnlyTask to users via the TaskListPanel.fxml. + */ +public class TaskCardView extends UiPart { + /*Constants*/ + public static final String TAG_LABEL_ID = "tagLabel"; + + private static final String FXML = "TaskCardView.fxml"; + private static final String TASK_TYPE = "Task"; + private static final String EVENT_TYPE = "Event"; + private static final int INSERT_TAG_INDEX = 2; + + /*Static Field*/ + /* + Provides a global reference between an ImmutableTask to the wrapper TaskCardView class, + since we have no direct access of TaskCardView from the ListView object. + */ + private static final Map taskCardMap = new HashMap<>(); + +``` +###### \java\seedu\todo\ui\view\TaskCardView.java +``` java + /** + * Displays all other view elements, including title, type label, pin image, description and location texts. + */ + private void displayEverythingElse() { + titleLabel.setText(String.valueOf(displayedIndex) + ". " + task.getTitle()); + pinImage.setVisible(task.isPinned()); + typeLabel.setText(task.isEvent() ? EVENT_TYPE : TASK_TYPE); + FxViewUtil.displayTextWhenAvailable(descriptionLabel, descriptionBox, task.getDescription()); + FxViewUtil.displayTextWhenAvailable(locationLabel, locationBox, task.getLocation()); + } + + /** + * Displays the tags in lexicographical order, ignoring case. + */ + private void displayTags(){ + task.getTags() + .stream() + .map(Tag::getTagName) + .sorted(Comparator.reverseOrder()) + .forEach(tagName -> { + titleFlowPane.getChildren().add(INSERT_TAG_INDEX, constructTagLabel(tagName)); + }); + } + + /** + * Constructs an appropriately styled tag label. + */ + private Label constructTagLabel(String tagName) { + Label tagLabel = ViewGeneratorUtil.constructRoundedText(tagName); + tagLabel.setId(TAG_LABEL_ID); + return tagLabel; + } + +``` +###### \java\seedu\todo\ui\view\TaskCardView.java +``` java + /** + * Initialise the view to show collapsed state if it can be collapsed, + * else hide the {@link #moreInfoLabel} otherwise. + */ + private void initialiseCollapsibleView() { + ViewStyleUtil.addRemoveClassStyles(true, taskCard, ViewStyleUtil.STYLE_COLLAPSED); + FxViewUtil.setCollapsed(moreInfoLabel, !isTaskCollapsible()); + } + + /** + * Displays formatted task or event timings in the time field. + */ + private void displayTimings() { + String displayTimingOutput; + Optional startTime = task.getStartTime(); + Optional endTime = task.getEndTime(); + boolean isEventWithTime = task.isEvent() && startTime.isPresent() && endTime.isPresent(); + boolean isTaskWithTime = !task.isEvent() && endTime.isPresent(); + + if (isEventWithTime) { + displayTimingOutput = timeUtil.getEventTimeText(startTime.get(), endTime.get()); + } else if (isTaskWithTime) { + displayTimingOutput = timeUtil.getTaskDeadlineText(endTime.get()); + } else { + FxViewUtil.setCollapsed(dateBox, true); + return; + } + dateLabel.setText(displayTimingOutput); + } + + /** + * Allows timing, and deadline highlight style to be updated automatically. + */ + private void setTimingAutoUpdate() { + Timeline timeline = FxViewUtil.setRecurringUiTask(30, event -> { + displayTimings(); + setStyle(); + }); + timeline.play(); + } + + /* Methods interfacing with UiManager*/ + /** + * Toggles the task card's collapsed or expanded state, only if this card is collapsible. + */ + public void toggleCardCollapsing() { + if (isTaskCollapsible()) { + //Sets both the collapsed style of the card, and mark the visibility of the "more" label. + boolean isCollapsing = ViewStyleUtil.toggleClassStyle(taskCard, ViewStyleUtil.STYLE_COLLAPSED); + FxViewUtil.setCollapsed(moreInfoLabel, !isCollapsing); + } + } + + public void setCardCollapsing(boolean isCollapsing) { + if (isTaskCollapsible()) { + ViewStyleUtil.addRemoveClassStyles(isCollapsing, taskCard, ViewStyleUtil.STYLE_COLLAPSED); + FxViewUtil.setCollapsed(moreInfoLabel, !isCollapsing); + } + } + + + /** + * Displays in the Ui whether this card is selected + * @param isSelected true when the card is selected + */ + public void markAsSelected(boolean isSelected) { + ViewStyleUtil.addRemoveClassStyles(isSelected, taskCard, ViewStyleUtil.STYLE_SELECTED); + } + + /* Helper Methods */ + /** + * Returns true if this task card can be collapsed, based on the information given from the + * {@link ImmutableTask} + */ + private boolean isTaskCollapsible() { + return task.getDescription().isPresent(); + } + + /* Getters */ + /** + * Gets the mapped {@link TaskCardView} object from an {@link ImmutableTask} object + * @param task that is being wrapped by the {@link TaskCardView} object + * @return a {@link TaskCardView} object that contains this task (can be null if not available) + */ + public static TaskCardView getTaskCard(ImmutableTask task) { + return taskCardMap.get(task); + } + + public int getDisplayedIndex() { + return displayedIndex; + } + + public VBox getLayout() { + return taskCard; + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + taskCard = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } +} +``` +###### \java\seedu\todo\ui\view\TodoListView.java +``` java +/** + * A panel that holds all the tasks inflated from TaskCardView. + */ +public class TodoListView extends UiPart { + /*Constants*/ + private static final String FXML = "TodoListView.fxml"; + + /*Variables*/ + private final Logger logger = LogsCenter.getLogger(TodoListView.class); + + /*Layout Declarations*/ + private VBox panel; + @FXML private ListView todoListView; + +``` +###### \java\seedu\todo\ui\view\TodoListView.java +``` java + /** + * Configures the {@link TodoListView} + * + * @param todoList A list of {@link ImmutableTask} to be displayed on this {@link #todoListView}. + */ + private void configure(ObservableList todoList) { + setTodoListConnections(todoList); + } + + /* To-do List Ui Methods */ + /** + * Links the list of {@link ImmutableTask} to the {@link #todoListView}. + * + * @param todoList A list of {@link ImmutableTask} to be displayed on this {@link #todoListView}. + */ + private void setTodoListConnections(ObservableList todoList) { + todoListView.setItems(todoList); + todoListView.setCellFactory(param -> new TodoListViewCell()); + } + + /** + * Toggles the expanded/collapsed view of a task card. + * + * @param task The specific to be expanded or collapsed from view. + */ + public void toggleExpandCollapsed(ImmutableTask task, Boolean toCollapse) { + TaskCardView taskCardView = TaskCardView.getTaskCard(task); + if (taskCardView != null && toCollapse == null) { + taskCardView.toggleCardCollapsing(); + } + else if (taskCardView != null && toCollapse != null) { + taskCardView.setCardCollapsing(toCollapse); + } + } + + /** + * Scrolls the {@link #todoListView} to the particular task card. + * + * @param task for the list to scroll to. + */ + public void scrollAndSelect(ImmutableTask task) { + todoListView.getSelectionModel().clearSelection(); + if (task!=null) { + todoListView.getSelectionModel().select(task); + } + + } + + + /* Override Methods */ + @Override + public void setNode(Node node) { + panel = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + +``` +###### \java\seedu\todo\ui\view\TodoListView.java +``` java + /** + * Sets the style properties of a cell on the to-do list, that cannot be done in any other places. + */ + private void setTaskCardStyleProperties(TaskCardView taskCardView) { + this.setPadding(Insets.EMPTY); + this.selectedProperty().addListener((observable, oldValue, newValue) + -> taskCardView.markAsSelected(newValue)); + } + } +} +``` +###### \resources\style\DefaultStyle.css +``` css +.main { + -fx-background-color: #2D2D2D; + -fx-border-color: #00A4FF; + -fx-border-width: 1px; + -fx-padding: 12; + -fx-spacing: 4; + -fx-font-family: "Roboto"; +} + +.text1 { + -fx-text-fill: #f3f3f3; + -fx-fill: #f3f3f3; + -fx-font-family: "Roboto Bold"; + -fx-font-size: 17pt; +} + +.text2 { + -fx-text-fill: #f3f3f3; + -fx-fill: #f3f3f3; + -fx-font-family: "Roboto Medium"; + -fx-font-size: 15pt; +} + +.text3 { + -fx-text-fill: #f3f3f3; + -fx-fill: #f3f3f3; + -fx-font-family: "Roboto Medium"; + -fx-font-size: 13pt; +} + +.text4 { + -fx-text-fill: #f3f3f3; + -fx-fill: #f3f3f3; + -fx-font-size: 12pt; +} + +.code { + -fx-font-family: "Roboto Mono"; +} + +.codeBolder { + -fx-font-family: "Roboto Mono Medium"; +} + +.bolder { + -fx-font-weight: bolder; +} + +.underline { + -fx-underline: true; +} + +.grey { + -fx-background-color: #2D2D2D; +} + +.gridPanel { + -fx-hgap: 16pt; + -fx-vgap: 2pt; + -fx-background-color: #3D3D3D; + -fx-padding: 8 8 8 8; +} + +.spacingBig { + -fx-spacing: 16px; + -fx-hgap: 16px; + -fx-vgap: 16px; +} + +.spacing { + -fx-spacing: 8px; + -fx-hgap: 8px; + -fx-vgap: 8px; +} + +.spacingSmall { + -fx-spacing: 2px; + -fx-hgap: 2px; + -fx-vgap: 2px; +} + +.subheadingPadding { + -fx-padding: 0 0 0 2; +} + + +.commandFeedback{ + -fx-text-fill: #f3f3f3; + -fx-font-family: "Roboto Medium"; + -fx-font-size: 12pt; +} + +.commandFeedback.error { + -fx-text-fill: #FF6464; +} + +.commandInput { + -fx-font-family: "Roboto Mono Medium"; + -fx-font-size: 20px; +} + +.commandInput.error { + -fx-border-color: #FF6464; + -fx-border-width: 2px; + -fx-text-fill: #FF6464; +} + +.commandError { + -fx-text-fill: #f3f3f3; + -fx-font-size: 12pt; + -fx-wrap-text: true; +} + +.roundLabel { + -fx-background-radius: 100; + -fx-text-fill: #f3f3f3; + -fx-background-color: #2D2D2D; + -fx-font-size: 10pt; +} + +.roundLabel.white { + -fx-text-fill: #2D2D2D; + -fx-background-color: #f3f3f3; + -fx-font-size: 12pt; +} + +/***View Filter Styles Start***/ +.viewFilter { + -fx-padding: -12 0 0 0; + -fx-hgap: -3; +} + +.viewFilter .label { + -fx-font-family: "Roboto Bold"; + -fx-font-size: 14pt; + -fx-font-smoothing-type: gray; + -fx-text-fill: #ddd; +} + +.viewFilter .viewFilterItem { + -fx-padding: 0 12 2; +} + +.viewFilter .selected { + -fx-background-color: #f3f3f3; + -fx-background-radius: 100; +} + +.viewFilter .selected .label { + -fx-text-fill: #2D2D2D; +} + +``` +###### \resources\style\DefaultStyle.css +``` css +/***TaskCardView Styles Start***/ +/*Default and Base*/ +.taskCard { + primary-color: transparent; + font-color: #444; + selected-color: #00A4FF; + + -fx-border-width: 0 12 0 0; + -fx-border-color: primary-color; +} + +.taskCard #dateBox { + -fx-background-color: primary-color; +} + +.taskCard #dateBox .label { + -fx-text-fill: font-color; +} + +.taskCard .label { + -fx-text-fill: #444; +} + +.taskCard .titleLabel { + -fx-font-size: 16pt; + -fx-font-family: "Roboto Medium"; +} + +.taskCard .descriptionLabel { + -fx-font-size: 12pt; +} + +.taskCard .footnoteLabel { + -fx-font-size: 10pt; + -fx-font-style: italic; + -fx-padding: 0 0 2 2; +} + +.taskCard .highlightedBackground { + -fx-background-color: #00A4FF; +} + +.taskCard.event .highlightedBackground { + -fx-background-color: #ff9800; +} + +.taskCard .lightBackground { + -fx-background-color: #777777; +} + +.taskCard .roundLabel { + -fx-text-fill: rgba(255, 255, 255, .9); + -fx-padding: 1 8 2; +} + +.taskCard .pinImage { + -fx-image: url("../images/star_gold.png"); + -fx-fit-to-width: 30px; + -fx-fit-to-height: 30px; +} + +.taskCard .dateImage { + -fx-image: url("../images/clock_black.png"); + -fx-fit-to-width: 20px; + -fx-fit-to-height: 20px; +} + +.taskCard .locationImage { + -fx-image: url("../images/location_black.png"); + -fx-fit-to-width: 20px; + -fx-fit-to-height: 20px; +} + +/*Completed*/ +.completed, +.event.overdue { + -fx-opacity: 0.4; +} + +.completed.selected, +.event.overdue.selected { + -fx-opacity: 0.85; +} + +.completed .label, +.event.overdue .label { + -fx-text-fill: font-color; +} + +.completed .roundLabel, +.event.overdue .roundLabel { + -fx-background-color: font-color; + -fx-text-fill: #fff; +} + +.completed .pinImage, +.event.overdue .pinImage { + -fx-image: url("../images/star_grey.png"); +} + +.completed .dateImage, +.event.overdue .dateImage { + -fx-image: url("../images/clock_grey.png"); +} + +.completed .locationImage, +.event.overdue .locationImage { + -fx-image: url("../images/location_grey.png"); +} + +.completed .titleLabel .text { + -fx-strikethrough: true; +} + +/* Overdue and ongoing inverts their highlights their dateBox, so + we add some padding to allow the */ +.overdue.task #dateBox, +.ongoing #dateBox { + -fx-padding: 3 8; + -fx-background-radius: 4; +} + +.overdue.task .dateImage, +.ongoing .dateImage, +.taskCard.selected .dateImage { + -fx-image: url("../images/clock_white.png"); +} + +/*Overdue*/ +.overdue.task { + primary-color: #e53935; + selected-color: #b71c1c; + font-color: rgba(255, 255, 255, .92); +} + +``` +###### \resources\style\DefaultStyle.css +``` css +/*Selected*/ +.taskCard.selected { + font-color: rgba(255, 255, 255, .9); + -fx-background-color: selected-color; +} + +.taskCard.selected .label { + -fx-text-fill: font-color; +} + +.taskCard.selected .roundLabel { + -fx-background-color: #fff; + -fx-text-fill: selected-color; +} + +.taskCard.selected .pinImage { + -fx-image: url("../images/star_white.png"); +} + +.taskCard.selected .locationImage { + -fx-image: url("../images/location_white.png"); +} + +/*Collapse*/ +.collapsed .collapsible { + visibility: collapse; + -fx-pref-height: 0; + -fx-min-height: 0; +} + +/***TaskCardView Styles End***/ +``` diff --git a/collated/main/A0135805Hreused.md b/collated/main/A0135805Hreused.md new file mode 100644 index 000000000000..1840e51f273c --- /dev/null +++ b/collated/main/A0135805Hreused.md @@ -0,0 +1,514 @@ +# A0135805Hreused +###### \java\seedu\todo\commons\events\ui\HighlightTaskEvent.java +``` java +/** + * Request to highlight in the user interface a particular task + * displayed in the {@link TodoListView} + */ +public class HighlightTaskEvent extends BaseEvent { + + private final ImmutableTask task; + + public HighlightTaskEvent(ImmutableTask task) { + this.task = task; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public ImmutableTask getTask() { + return task; + } +} +``` +###### \java\seedu\todo\model\tag\Tag.java +``` java + /* Override Methods */ + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + // if is tag + || (other instanceof Tag && this.tagName.toLowerCase().equals(((Tag) other).tagName.toLowerCase())); + } + + @Override + public int hashCode() { + return tagName.toLowerCase().hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + tagName + ']'; + } + + /* Getters */ + public String getTagName() { + return tagName; + } + + /* Public Helper Methods */ + /** + * Converts tags to a collection of tag names + */ + public static Set getLowerCaseNames(Collection tags) { + return tags.stream() + .map(Tag::getTagName) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } +} +``` +###### \java\seedu\todo\model\tag\UniqueTagCollection.java +``` java + /* Other Override Methods */ + @Override + public Iterator iterator() { + return uniqueTagsToTasksMap.keySet().iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTagCollection // instanceof handles nulls + && this.uniqueTagsToTasksMap.equals(((UniqueTagCollection) other).uniqueTagsToTasksMap)); + } + + @Override + public int hashCode() { + return uniqueTagsToTasksMap.hashCode(); + } +} +``` +###### \java\seedu\todo\ui\MainWindow.java +``` java +/** + * The Main Window. Provides the basic application layout containing placeholders + * where other JavaFX view elements can be placed. + */ +public class MainWindow extends UiPart { + + /* Constants */ + private static final String ICON = "/images/app_icon.png"; + private static final String FXML = "MainWindow.fxml"; + private static final int MIN_HEIGHT = 780; + private static final int MIN_WIDTH = 680; + + /* Variables */ + private Logic logic; + private Config config; + private UserPrefs userPrefs; + private Model model; + + /* Independent Ui parts residing in this Ui container */ + private CommandInputView commandInputView; + private CommandPreviewView commandPreviewView; + private CommandFeedbackView commandFeedbackView; + private CommandErrorView commandErrorView; + private TodoListView todoListView; + private HelpView helpView; + private GlobalTagView globalTagView; + + /* Layout objects for MainWindow: Handles elements of this Ui container */ + private VBox rootLayout; + private Scene scene; + + @FXML private AnchorPane commandInputViewPlaceholder; + @FXML private AnchorPane commandErrorViewPlaceholder; + @FXML private AnchorPane commandPreviewViewPlaceholder; + @FXML private AnchorPane commandFeedbackViewPlaceholder; + @FXML private AnchorPane globalTagViewPlaceholder; + @FXML private AnchorPane todoListViewPlaceholder; + @FXML private AnchorPane helpViewPlaceholder; + @FXML private AnchorPane filterBarViewPlaceholder; + @FXML private AnchorPane searchStatusViewPlaceholder; + @FXML private AnchorPane emptyListPlaceholder; + + /** + * Loads an instance of the {@link MainWindow} together with the associated view elements. + * + * @param primaryStage For the MainWindow to be loaded into. + * @param config App configuration class file for some properties to be loaded. + * @param prefs User preference for some properties to be loaded. + * @param logic The main logic engine for commands to be executed. + * @return An instance of the {@link MainWindow} element. + */ + public static MainWindow load(Stage primaryStage, Config config, UserPrefs prefs, Logic logic, Model model) { + MainWindow mainWindow = UiPartLoaderUtil.loadUiPart(primaryStage, new MainWindow()); + mainWindow.configure(config.getAppTitle(), config, prefs, logic, model); + return mainWindow; + } + + private void configure(String appTitle, Config config, UserPrefs prefs, Logic logic, Model model) { + //Set dependencies + this.logic = logic; + this.config = config; + this.userPrefs = prefs; + this.model = model; + + //Configure the UI + setTitle(appTitle); + setIcon(ICON); + setWindowMinSize(); + setWindowDefaultSize(prefs); + scene = new Scene(rootLayout); + primaryStage.setScene(scene); + } + + void fillInnerParts() { + //Initialise the view elements to each placeholders. + helpView = HelpView.load(primaryStage, helpViewPlaceholder); + commandPreviewView = CommandPreviewView.load(primaryStage, commandPreviewViewPlaceholder); + commandFeedbackView = CommandFeedbackView.load(primaryStage, commandFeedbackViewPlaceholder); + commandInputView = CommandInputView.load(primaryStage, commandInputViewPlaceholder); + commandErrorView = CommandErrorView.load(primaryStage, commandErrorViewPlaceholder); + globalTagView = GlobalTagView.load(primaryStage, globalTagViewPlaceholder); + todoListView = TodoListView.load(primaryStage, todoListViewPlaceholder, model.getObservableList()); + + EmptyListView.load(primaryStage, emptyListPlaceholder, model.getObservableList(), model.getViewFilter()); + FilterBarView.load(primaryStage, filterBarViewPlaceholder, model.getViewFilter()); + SearchStatusView.load(primaryStage, searchStatusViewPlaceholder, model.getSearchStatus()); + + //Constructs a command communication link between the commandXViews and logic. + CommandController.constructLink(logic, + commandInputView, commandPreviewView, commandFeedbackView, commandErrorView); + } + + 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()); + } + + public void hide() { + primaryStage.hide(); + } + + public void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + raise(new ExitAppRequestEvent()); + } + + /* Override Methods */ + @Override + public void setNode(Node node) { + rootLayout = (VBox) node; + } + + @Override + public String getFxmlPath() { + return FXML; + } + + /* Getters */ + public TodoListView getTodoListView() { + return this.todoListView; + } + + public HelpView getHelpView() { + return this.helpView; + } + + public CommandFeedbackView getCommandFeedbackView() { + return commandFeedbackView; + } + + public CommandPreviewView getCommandPreviewView() { + return commandPreviewView; + } + + public GlobalTagView getGlobalTagView() { + return globalTagView; + } +} +``` +###### \java\seedu\todo\ui\Ui.java +``` java +/** + * API of UI component + */ +public interface Ui { + + /** Starts the UI (and the App). */ + void start(Stage primaryStage); + + /** Stops the UI. */ + void stop(); + +} +``` +###### \java\seedu\todo\ui\UiManager.java +``` java +/** + * 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/app_icon.png"; + + private Logic logic; + private Config config; + private UserPrefs prefs; + private MainWindow mainWindow; + private Model model; + + public UiManager(Logic logic, Config config, UserPrefs prefs, Model model) { + super(); + this.logic = logic; + this.config = config; + this.prefs = prefs; + this.model = model; + } + + @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, model); + 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(); + } + + 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("style/DefaultStyle.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 ================================================================= +``` +###### \java\seedu\todo\ui\view\CommandErrorView.java +``` java + /** + * Loads and initialise the feedback view element to the placeHolder + * @param primaryStage of the application + * @param placeHolder where the view element {@link #errorViewBox} should be placed + * @return an instance of this class + */ + public static CommandErrorView load(Stage primaryStage, AnchorPane placeHolder) { + CommandErrorView errorView = UiPartLoaderUtil + .loadUiPart(primaryStage, placeHolder, new CommandErrorView()); + errorView.configureLayout(); + errorView.hideCommandErrorView(); + return errorView; + } + +``` +###### \java\seedu\todo\ui\view\CommandFeedbackView.java +``` java + /** + * Loads and initialise the feedback view element to the placeholder. + * + * @param primaryStage The main stage of the application. + * @param placeholder The place where the view element {@link #textContainer} should be placed. + * @return An instance of this class. + */ + public static CommandFeedbackView load(Stage primaryStage, AnchorPane placeholder) { + CommandFeedbackView feedbackView = UiPartLoaderUtil + .loadUiPart(primaryStage, placeholder, new CommandFeedbackView()); + feedbackView.configureLayout(); + return feedbackView; + } + + /** + * Configures the UI layout of {@link CommandFeedbackView}. + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(textContainer, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(commandFeedbackLabel, 0.0, 0.0, 0.0, 0.0); + } + +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java + /** + * Loads and initialise the input view element to the placeHolder + * @param primaryStage of the application + * @param placeHolder where the view element {@link #commandInputPane} should be placed + * @return an instance of this class + */ + public static CommandInputView load(Stage primaryStage, AnchorPane placeHolder) { + CommandInputView commandInputView = UiPartLoaderUtil + .loadUiPart(primaryStage, placeHolder, new CommandInputView()); + commandInputView.configureLayout(); + commandInputView.configureProperties(); + return commandInputView; + } + + /** + * Configure the UI layout of {@link CommandInputView} + */ + private void configureLayout() { + FxViewUtil.applyAnchorBoundaryParameters(commandInputPane, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(commandTextField, 0.0, 0.0, 0.0, 0.0); + } + +``` +###### \java\seedu\todo\ui\view\TaskCardView.java +``` java + /*Layout Declarations*/ + @FXML private VBox taskCard; + @FXML private ImageView pinImage; + @FXML private Label titleLabel; + @FXML private Label typeLabel, moreInfoLabel; + @FXML private Label descriptionLabel, dateLabel, locationLabel; + @FXML private HBox descriptionBox, dateBox, locationBox; + @FXML private FlowPane titleFlowPane; + + /* Variables */ + private ImmutableTask task; + private int displayedIndex; + private TimeUtil timeUtil = new TimeUtil(); + + /* Default Constructor */ + private TaskCardView(){ + } + + /* Initialisation Methods */ + /** + * Loads and initialise one cell of the task in the to-do list ListView. + * @param task to be displayed on the cell + * @param displayedIndex index to be displayed on the card itself to the user + * @return an instance of this class + */ + public static TaskCardView load(ImmutableTask task, int displayedIndex){ + TaskCardView taskListCard = new TaskCardView(); + taskListCard.task = task; + taskListCard.displayedIndex = displayedIndex; + taskCardMap.put(task, taskListCard); + return UiPartLoaderUtil.loadUiPart(taskListCard); + } + + /** + * Initialise all the view elements in a task card. + */ + @FXML + public void initialize() { + displayEverythingElse(); + displayTags(); + displayTimings(); + setStyle(); + setTimingAutoUpdate(); + initialiseCollapsibleView(); + } + +``` +###### \java\seedu\todo\ui\view\TodoListView.java +``` java + /** + * Default Constructor for {@link TodoListView} + */ + public TodoListView() { + super(); + } + + /** + * Loads and initialise the {@link TodoListView} to the placeHolder. + * + * @param primaryStage of the application + * @param placeHolder where the view element {@link #todoListView} should be placed + * @param todoList the list of tasks to be displayed + * @return an instance of this class + */ + public static TodoListView load(Stage primaryStage, AnchorPane placeHolder, + ObservableList todoList) { + + TodoListView todoListView = UiPartLoaderUtil.loadUiPart(primaryStage, placeHolder, new TodoListView()); + todoListView.configure(todoList); + return todoListView; + } + +``` +###### \java\seedu\todo\ui\view\TodoListView.java +``` java + /** + * Models a Task Card as a single ListCell of the ListView + */ + private class TodoListViewCell extends ListCell { + + /* Override Methods */ + @Override + protected void updateItem(ImmutableTask task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + TaskCardView taskCardView = TaskCardView.load(task, FxViewUtil.convertToUiIndex(getIndex())); + setGraphic(taskCardView.getLayout()); + setTaskCardStyleProperties(taskCardView); + } + } + +``` diff --git a/collated/main/A0135817B.md b/collated/main/A0135817B.md new file mode 100644 index 000000000000..ed2445472725 --- /dev/null +++ b/collated/main/A0135817B.md @@ -0,0 +1,2107 @@ +# A0135817B +###### \java\seedu\todo\commons\exceptions\ValidationException.java +``` java +public class ValidationException extends Exception { + + private ErrorBag errors; + + public ValidationException(String message) { + super(message); + this.errors = new ErrorBag(); + } + + public ValidationException(String message, ErrorBag errors) { + super(message); + this.errors = errors; + } + + public ErrorBag getErrors() { + return errors; + } +} +``` +###### \java\seedu\todo\commons\util\StringUtil.java +``` java + /** + * Returns true if the string is null, of length zero, or contains only whitespace + */ + public static boolean isEmpty(String s) { + return s == null || s.length() == 0 || CharMatcher.whitespace().matchesAllOf(s); + } + +``` +###### \java\seedu\todo\commons\util\TimeUtil.java +``` java + /** + * Translates input string from International date format (DD/MM/YYYY) to American + * date format (MM/DD/YYYY), because Natty only recognizes the later + */ + public static String toAmericanDateFormat(String input) { + return DATE_REGEX.matcher(input).replaceAll("$3$2$1"); + } + +``` +###### \java\seedu\todo\logic\arguments\Argument.java +``` java +abstract public class Argument implements Parameter { + private static final String REQUIRED_ERROR_FORMAT = "The %s parameter is required"; + private static final String TYPE_ERROR_FORMAT = "The %s should be a %s. You gave '%s'."; + + protected static final String OPTIONAL_ARGUMENT_FORMAT = "[%s]"; + protected static final String FLAG_ARGUMENT_FORMAT = "%s%s %s"; + + private String name; + private String description; + private String flag; + private boolean optional = true; + private boolean boundValue = false; + + protected T value; + + private String requiredErrorMessage; + + private static final Logger logger = LogsCenter.getLogger(Argument.class); + + public Argument(String name) { + this.name = name; + } + + public Argument(String name, T defaultValue) { + this.name = name; + this.value = defaultValue; + } + + /** + * Binds a value to this parameter. Implementing classes MUST override AND + * call the parent class function so that the dirty bit is set for required + * parameter validation to work + */ + @Override + public void setValue(String input) throws IllegalValueException { + boundValue = true; + } + + public T getValue() { + return value; + } + + @Override + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Argument description(String description) { + this.description = description; + return this; + } + + public String getFlag() { + return flag; + } + + public Argument flag(String flag) { + this.flag = flag.trim().toLowerCase(); + + if (!this.flag.equals(flag)) { + logger.warning("Flag argument has uppercase or whitespace characters. These have been ignored."); + } + + return this; + } + + public boolean isOptional() { + return optional; + } + + public boolean hasBoundValue() { + return boundValue; + } + + /** + * Sets the field as required + */ + public Argument required() { + this.optional = false; + return this; + } + + /** + * Sets the field as required and specify an error message to show if it is not provided + * @param errorMessage shown to the user when the parameter is not provided + */ + public Argument required(String errorMessage) { + requiredErrorMessage = errorMessage; + this.optional = false; + return this; + } + + @Override + public boolean isPositional() { + return flag == null; + } + + @Override + public void checkRequired() throws IllegalValueException { + if (!isOptional() && !hasBoundValue()) { + String error = requiredErrorMessage == null ? + String.format(Argument.REQUIRED_ERROR_FORMAT, name) : requiredErrorMessage; + throw new IllegalValueException(error); + } + } + + /** + * Throws an IllegalValueException for a type mismatch between user input and what + * the argument expect + * @param field name of the argument + * @param expected the expected type for the argument + * @param actual what the user actually gave + */ + protected void typeError(String field, String expected, String actual) throws IllegalValueException { + throw new IllegalValueException(String.format(Argument.TYPE_ERROR_FORMAT, field, expected, actual)); + } + + @Override + public String toString() { + return toString(name); + } + + public String toString(String name) { + if (!isPositional()) { + name = String.format(FLAG_ARGUMENT_FORMAT, TodoParser.FLAG_TOKEN, flag, name); + } + + if (isOptional()) { + name = String.format(OPTIONAL_ARGUMENT_FORMAT, name); + } + + return name; + } +} +``` +###### \java\seedu\todo\logic\arguments\DateRange.java +``` java +/** + * Utility container class for the output from DateRangeArgument + */ +public class DateRange { + private final LocalDateTime endTime; + private LocalDateTime startTime; + + public DateRange(LocalDateTime endTime) { + this.endTime = endTime; + } + + public DateRange(LocalDateTime startTime, LocalDateTime endTime) { + this(endTime); + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public boolean isRange() { + return startTime != null; + } +} +``` +###### \java\seedu\todo\logic\arguments\DateRangeArgument.java +``` java +public class DateRangeArgument extends Argument { + private static final TimeUtil timeUtil = new TimeUtil(); + + // TODO: Review all error messages to check for user friendliness + private static final String TOO_MANY_DATES_FORMAT = "You specified too many time - we found: %s"; + private static final String NO_DATE_FOUND_FORMAT = "%s does not seem to contain a date"; + + public DateRangeArgument(String name) { + // Makes sure that there is a default value, so that callers won't get null when they getValue() + super(name, new DateRange(null)); + } + + @Override + public void setValue(String input) throws IllegalValueException { + super.setValue(input); + + if (StringUtil.isEmpty(input)) { + return; + } + + input = TimeUtil.toAmericanDateFormat(input); + List dateGroups = TodoParser.dateTimeParser.parse(input); + + List dates = dateGroups.stream() + .map(TimeUtil::asLocalDateTime) + .sorted() + .collect(Collectors.toList()); + + if (dates.size() > 2) { + tooManyDatesError(dates); + } else if (dates.isEmpty()) { + throw new IllegalValueException(String.format(DateRangeArgument.NO_DATE_FOUND_FORMAT, input)); + } + + if (dates.size() == 1) { + value = new DateRange(dates.get(0)); + } else { + value = new DateRange(dates.get(0), dates.get(1)); + } + } + + private void tooManyDatesError(List dates) throws IllegalValueException { + StringJoiner sj = new StringJoiner(", "); + for (LocalDateTime d : dates) { + sj.add(timeUtil.getTaskDeadlineText(d)); + } + String message = String.format(DateRangeArgument.TOO_MANY_DATES_FORMAT, sj.toString()); + throw new IllegalValueException(message); + } + +} +``` +###### \java\seedu\todo\logic\arguments\FlagArgument.java +``` java +public class FlagArgument extends Argument { + + public FlagArgument(String name) { + super(name); + flag(name.substring(0, 1).toLowerCase()); + this.value = false; + } + + @Override + public void setValue(String input) throws IllegalValueException { + this.value = true; + super.setValue(input); + } + + @Override + public String toString(String name) { + String flag = TodoParser.FLAG_TOKEN + getFlag(); + return isOptional() ? String.format(Argument.OPTIONAL_ARGUMENT_FORMAT, flag) : flag; + } +} +``` +###### \java\seedu\todo\logic\arguments\IntArgument.java +``` java +public class IntArgument extends Argument { + + public IntArgument(String name) { + super(name); + } + + @Override + public void setValue(String input) throws IllegalValueException { + try { + value = Integer.parseInt(input); + super.setValue(input); + } catch (NumberFormatException e) { + typeError(this.getName(), "integer", input); + } + } + +} +``` +###### \java\seedu\todo\logic\arguments\Parameter.java +``` java +/** + * Represents a single command parameter that the parser will try to feed the user + * input into. The Parameter interface is needed because the Argument base class is + * typed, so this interface contains all of the non-typed methods that are common to + * all argument subclasses + */ +public interface Parameter { + void setValue(String input) throws IllegalValueException; + + boolean isPositional(); + + boolean hasBoundValue(); + + boolean isOptional(); + + String getFlag(); + + String getName(); + + String getDescription(); + + void checkRequired() throws IllegalValueException; +} +``` +###### \java\seedu\todo\logic\arguments\StringArgument.java +``` java +public class StringArgument extends Argument { + + public StringArgument(String name) { + super(name); + } + + @Override + public void setValue(String input) throws IllegalValueException { + input = input.trim(); + + // Ignore empty strings + if (input.length() > 0) { + this.value = input; + } + + super.setValue(input); + } + +} +``` +###### \java\seedu\todo\logic\commands\AddCommand.java +``` java +public class AddCommand extends BaseCommand { + + private Argument title = new StringArgument("title").required(); + + private Argument description = new StringArgument("description") + .flag("m"); + + private Argument pin = new FlagArgument("pin") + .flag("p"); + + private Argument location = new StringArgument("location") + .flag("l"); + + private Argument date = new DateRangeArgument("deadline") + .flag("d"); + + private Argument tags = new StringArgument("tag") + .flag("t"); + + @Override + public Parameter[] getArguments() { + return new Parameter[] { + title, date, description, location, pin, tags + }; + } + + @Override + public String getCommandName() { + return "add"; + } + + @Override + public List getCommandSummary() { + String eventArguments = Joiner.on(" ").join(title, "/d start and end time", description, location, pin, "/t tag1 [, tag2, ...]"); + + return ImmutableList.of( + new CommandSummary("Add task", getCommandName(), getArgumentSummary()), + new CommandSummary("Add event", getCommandName(), eventArguments)); + } + + @Override + public CommandResult execute() throws ValidationException { + ImmutableTask addedTask = this.model.add(title.getValue(), task -> { + task.setDescription(description.getValue()); + task.setPinned(pin.getValue()); + task.setLocation(location.getValue()); + task.setStartTime(date.getValue().getStartTime()); + task.setEndTime(date.getValue().getEndTime()); + model.addTagsToTask(task, StringUtil.split(tags.getValue())); + }); + if(!model.getObservableList().contains(addedTask)) { + model.view(TaskViewFilter.DEFAULT); + } + + eventBus.post(new HighlightTaskEvent(addedTask)); + eventBus.post(new ExpandCollapseTaskEvent(addedTask)); + return new CommandResult(); + } + +``` +###### \java\seedu\todo\logic\commands\BaseCommand.java +``` java +/** + * The base class for commands. All commands need to implement an execute function + * and a getArguments function that collects the command arguments for the use of + * the help command. + * + * To perform additional validation on incoming arguments, override the validateArguments + * function. + */ +public abstract class BaseCommand { + /** + * The default message that accompanies argument errors + */ + private static final String DEFAULT_ARGUMENT_ERROR_MESSAGE = ""; + + private static final String TASK_MODIFIED_SUCCESS_MESSAGE = "'%s' successfully %s!"; + + protected static final EventsCenter eventBus = EventsCenter.getInstance(); + + protected Model model; + + protected ErrorBag errors = new ErrorBag(); + + abstract protected Parameter[] getArguments(); + + /** + * Return the name of the command, which is used to call it + */ + abstract public String getCommandName(); + + /** + * Returns a list of command summaries for the command. This function returns a + * list because commands may (rarely) be responsible for more than one thing, + * like the add command. + */ + abstract public List getCommandSummary(); + + abstract public CommandResult execute() throws ValidationException; + + /** + * Binds the data model to the command object + */ + public void setModel(Model model) { + this.model = model; + } + + /** + * Binds the both positional and named command arguments from the parse results + * to the command object itself + * + * @throws ValidationException if the arguments are invalid + */ + public void setArguments(ParseResult arguments) throws ValidationException { + if (arguments.getPositionalArgument().isPresent()) { + setPositionalArgument(arguments.getPositionalArgument().get()); + } + + for (Entry e : arguments.getNamedArguments().entrySet()) { + setNameArgument(e.getKey(), e.getValue()); + } + + checkRequiredArguments(); + validateArguments(); + + errors.validate(getArgumentErrorMessage()); + } + + /** + * Hook allowing subclasses to implement their own validation logic for arguments + * Subclasses should add additional errors to the errors ErrorBag + */ + protected void validateArguments() { + // Does no additional validation by default + } + + protected void setPositionalArgument(String argument) { + for (Parameter p : getArguments()) { + if (p.isPositional()) { + try { + p.setValue(argument); + } catch (IllegalValueException e) { + errors.put(e.getMessage()); + } + } + } + } + + protected void setNameArgument(String flag, String argument) { + for (Parameter p : getArguments()) { + if (flag.equals(p.getFlag())) { + try { + p.setValue(argument); + } catch (IllegalValueException e) { + errors.put(p.getName(), e.getMessage()); + } + + return; + } + } + } + + private void checkRequiredArguments() { + for (Parameter p : getArguments()) { + try { + p.checkRequired(); + } catch (IllegalValueException e) { + errors.put(p.getName(), e.getMessage()); + } + } + } + + /** + * Override this function if the command should return some other error + * message on argument validation error + */ + protected String getArgumentErrorMessage() { + return BaseCommand.DEFAULT_ARGUMENT_ERROR_MESSAGE; + } + + /** + * Returns a generic CommandResult with a "{task} successfully {verbed}" success message. + * + * @param title the title of the task that was verbed on + * @param verb the action that was performed on the task, in past tense + */ + protected CommandResult taskSuccessfulResult(String title, String verb) { + return new CommandResult(String.format(BaseCommand.TASK_MODIFIED_SUCCESS_MESSAGE, title, verb)); + } + + /** + * Turns the arguments into a string summary using their toString function + */ + protected String getArgumentSummary() { + StringJoiner sj = new StringJoiner(" "); + for (Parameter p : getArguments()) { + sj.add(p.toString()); + } + return sj.toString(); + } + + /** + * Checks whether a string argument is empty + */ + protected boolean isEmpty(Argument argument) { + return !argument.hasBoundValue() || StringUtil.isEmpty(argument.getValue()); + } +} +``` +###### \java\seedu\todo\logic\commands\CommandResult.java +``` java +/** + * Represents the result of a command execution. + */ +public class CommandResult { + private final String feedback; + private final ErrorBag errors; + + public CommandResult() { + this.feedback = ""; + this.errors = null; + } + + public CommandResult(String feedback) { + this.feedback = feedback; + this.errors = null; + } + + public CommandResult(String feedback, ErrorBag errors) { + this.feedback = feedback; + this.errors = errors; + } + + public String getFeedback() { + return feedback; + } + + public ErrorBag getErrors() { + return errors; + } + + public boolean isSuccessful() { + return errors == null; + } +} +``` +###### \java\seedu\todo\logic\commands\CommandSummary.java +``` java +public class CommandSummary { + /** + * The scenario the summary is aiming to describe, eg. add event, delete task, etc. + * Keep it short but descriptive. + */ + public final String scenario; + + /** + * The command to accomplish the scenario, eg. add, delete + */ + public final String command; + + /** + * The parameters for the command + */ + public final String arguments; + + public CommandSummary(String scenario, String command) { + this(scenario, command, ""); + } + + public CommandSummary(String scenario, String command, String arguments) { + this.scenario = scenario.trim(); + this.command = command.toLowerCase().trim(); + this.arguments = arguments.trim(); + } + +``` +###### \java\seedu\todo\logic\commands\DeleteCommand.java +``` java +public class DeleteCommand extends BaseCommand { + private static final String VERB = "deleted"; + + private Argument index = new IntArgument("index").required(); + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{ index }; + } + + @Override + public String getCommandName() { + return "delete"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Delete task", getCommandName(), + getArgumentSummary())); + } + + @Override + public CommandResult execute() throws ValidationException { + //clear selections + eventBus.post(new HighlightTaskEvent(null)); + + ImmutableTask deletedTask = this.model.delete(index.getValue()); + return taskSuccessfulResult(deletedTask.getTitle(), DeleteCommand.VERB); + } +} +``` +###### \java\seedu\todo\logic\commands\ExitCommand.java +``` java +/** + * Terminates the program. + */ +public class ExitCommand extends BaseCommand { + private final static String EXIT_MESSAGE = "Goodbye!"; + + @Override + public CommandResult execute() throws ValidationException { + EventsCenter.getInstance().post(new ExitAppRequestEvent()); + return new CommandResult(ExitCommand.EXIT_MESSAGE); + } + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{}; + } + + @Override + public String getCommandName() { + return "exit"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Close this app :(", getCommandName())); + } + +} +``` +###### \java\seedu\todo\logic\commands\FindCommand.java +``` java +public class FindCommand extends BaseCommand { + private static final String FEEDBACK = "Type 'find' or switch to another view to dismiss"; + + private Argument keywords = new StringArgument("keywords"); + + private Argument tags = new StringArgument("tags") + .flag("t"); + + @Override + protected Parameter[] getArguments() { + return new Parameter[] { keywords, tags }; + } + + @Override + public String getCommandName() { + return "find"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of( + new CommandSummary("Find tasks", getCommandName(), getArgumentSummary())); + } + + @Override + public CommandResult execute() throws ValidationException { + // Dismissing search results if there are no keywords + if (isEmpty(keywords) && isEmpty(tags)) { + model.find(t -> true); + return new CommandResult(); + } + + // Construct the predicate and list of search terms + Predicate filter = t -> false; + List terms = new ArrayList<>(); + + if (!isEmpty(keywords)) { + List keywordList = getSearchTerms(keywords); + terms.addAll(keywordList); + filter = filter.or(task -> { + String title = task.getTitle().toLowerCase(); + return keywordList.stream().anyMatch(title::contains); + }); + } + + if (!isEmpty(tags)) { + List tagList = getSearchTerms(tags); + terms.addAll(tagList); + filter = filter.or(task -> { + Set taskTagNames = Tag.getLowerCaseNames(task.getTags()); + return !Collections.disjoint(taskTagNames, tagList); + }); + } + + model.find(filter, terms); + return new CommandResult(FEEDBACK); + } + + /** + * Splits the argument's value into lowercase space or comma delimited terms + */ + private List getSearchTerms(Argument argument) { + return Arrays.asList(StringUtil.split(argument.getValue().toLowerCase())); + } +} +``` +###### \java\seedu\todo\logic\commands\HelpCommand.java +``` java +/** + * Shows the help panel + */ +public class HelpCommand extends BaseCommand { + private final static String HELP_MESSAGE = "Start typing in the command to dismiss."; + + @Override + public CommandResult execute() throws ValidationException { + List commandSummaries = CommandMap.getAllCommandSummary(); + + EventsCenter.getInstance().post(new ShowHelpEvent(commandSummaries)); + return new CommandResult(HelpCommand.HELP_MESSAGE); + } + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{}; + } + + @Override + public String getCommandName() { + return "help"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Show help", getCommandName())); + } +} +``` +###### \java\seedu\todo\logic\commands\LoadCommand.java +``` java +public class LoadCommand extends BaseCommand { + private Argument location = new StringArgument("location").required(); + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{ location }; + } + + @Override + public String getCommandName() { + return "load"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Load todo list from file", getCommandName(), + getArgumentSummary())); + } + + @Override + public CommandResult execute() throws ValidationException { + String path = location.getValue(); + model.load(path); + return new CommandResult(String.format("File loaded from %s", path)); + } +} +``` +###### \java\seedu\todo\logic\commands\RedoCommand.java +``` java +public class RedoCommand extends BaseCommand { + @Override + protected Parameter[] getArguments() { + return new Parameter[0]; + } + + @Override + public String getCommandName() { + return "redo"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Redo", getCommandName())); + } + + @Override + public CommandResult execute() throws ValidationException { + model.redo(); + return new CommandResult("Redid last action"); + } +} +``` +###### \java\seedu\todo\logic\commands\SaveCommand.java +``` java +/** + * Saves the save file to a different location + */ +public class SaveCommand extends BaseCommand { + private Argument location = new StringArgument("location"); + + @Override + protected Parameter[] getArguments() { + return new Parameter[]{ location }; + } + + @Override + public String getCommandName() { + return "save"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of( + new CommandSummary("Save to another file", getCommandName(), "location"), + new CommandSummary("Show save location", getCommandName())); + } + + @Override + public CommandResult execute() throws ValidationException { + if (location.hasBoundValue()) { + model.save(location.getValue()); + + return new CommandResult(String.format("Todo list saved successfully to %s", location)); + } else { + return new CommandResult(String.format("Save location: %s", model.getStorageLocation())); + } + } +} +``` +###### \java\seedu\todo\logic\commands\UndoCommand.java +``` java +public class UndoCommand extends BaseCommand { + @Override + protected Parameter[] getArguments() { + return new Parameter[0]; + } + + @Override + public String getCommandName() { + return "undo"; + } + + @Override + public List getCommandSummary() { + return ImmutableList.of(new CommandSummary("Undo last edit", getCommandName())); + } + + @Override + public CommandResult execute() throws ValidationException { + model.undo(); + return new CommandResult("Undid last action"); + } +} +``` +###### \java\seedu\todo\logic\Dispatcher.java +``` java +public interface Dispatcher { + BaseCommand dispatch(String command) throws IllegalValueException; +} +``` +###### \java\seedu\todo\logic\Logic.java +``` java +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param input The command as entered by the user. + */ + CommandResult execute(String input); + +``` +###### \java\seedu\todo\logic\parser\Parser.java +``` java +public interface Parser { + ParseResult parse(String input); +} +``` +###### \java\seedu\todo\logic\parser\ParseResult.java +``` java +public interface ParseResult { + + String getCommand(); + + Optional getPositionalArgument(); + + Map getNamedArguments(); + +} +``` +###### \java\seedu\todo\logic\parser\TodoParser.java +``` java +/** + * Parses a input string into the command, positional argument and named arguments. + * The format looks like: + * + *
command positional [-f or --flag named]...
+ * + * There will only be one command, optionally one positional argument, and + * optionally many named arguments. + */ +public class TodoParser implements Parser { + // Best practice would dictate this property be inside DateRangeArgument instead + // but PrettyTimeParser takes several seconds to warm up, so instead we do it in the + // TodoLogic constructor, which is initialized on app startup. + public static final PrettyTimeParser dateTimeParser = new PrettyTimeParser(); + + public static final String FLAG_TOKEN = "/"; + + public TodoParser() { + // Try to warm up the parser a little on a background thread + new Thread(() -> dateTimeParser.parse("next Wednesday 2pm to 6pm")).start(); + } + + private List tokenize(String input) { + input = input.trim(); + + return Lists.newArrayList(Splitter + .on(" ") + .trimResults() + .omitEmptyStrings() + .split(input)); + } + + private String parseFlag(String token) { + return token.substring(1, token.length()); + } + + private boolean isFlag(String token) { + return token.startsWith(TodoParser.FLAG_TOKEN) && token.length() > 1; + } + + @Override + public ParseResult parse(String input) { + List tokens = tokenize(input); + Map named = new HashMap<>(); + + // Pull out the command, exit if there's no more things to parse + String command = tokens.remove(0).toLowerCase(); + + // Parse out positional argument + StringJoiner sj = new StringJoiner(" "); + while (!tokens.isEmpty() && !isFlag(tokens.get(0))) { + sj.add(tokens.remove(0)); + } + String positional = sj.toString(); + + // If there are no more tokens return immediately + if (tokens.isEmpty()) { + return new TodoResult(command, positional, named); + } + + // Parse out named arguments + String flag = tokens.remove(0); + sj = new StringJoiner(" "); + while (!tokens.isEmpty()) { + if (isFlag(tokens.get(0))) { + named.put(parseFlag(flag), sj.toString()); + flag = tokens.remove(0); + sj = new StringJoiner(" "); + } else { + sj.add(tokens.remove(0)); + } + } + named.put(parseFlag(flag), sj.toString()); + + return new TodoResult(command, positional, named); + } + + private class TodoResult implements ParseResult { + private final String command; + private final String positional; + private final Map named; + + public TodoResult(String command, String positional, Map named) { + this.command = command; + this.positional = positional.length() > 0 ? positional : null; + this.named = named; + } + + @Override + public String getCommand() { + return command; + } + + @Override + public Optional getPositionalArgument() { + return Optional.ofNullable(positional); + } + + @Override + public Map getNamedArguments() { + return named; + } + + @Override + public String toString() { + StringJoiner sj = new StringJoiner(" "); + sj.add(command).add(getPositionalArgument().orElse("")); + for (Entry e : getNamedArguments().entrySet()) { + sj.add(TodoParser.FLAG_TOKEN + e.getKey()).add(e.getValue()); + } + return sj.toString(); + } + } +} +``` +###### \java\seedu\todo\logic\TodoDispatcher.java +``` java +/** + * Selects the correct command based on the parser results + */ +public class TodoDispatcher implements Dispatcher { + private final static String COMMAND_NOT_FOUND_FORMAT = "'%s' doesn't look like any command we know."; + private final static String AMBIGUOUS_COMMAND_FORMAT = "Do you mean %s?"; + + public BaseCommand dispatch(String input) throws IllegalValueException { + + List commands = CommandMap.filterCommandKeys(input); + + // Return immediately when there's one match left. This allow the user to + // type as little as possible + if (commands.size() == 1) { + String key = commands.iterator().next(); + return CommandMap.getCommand(key); + } else if (commands.isEmpty()) { + throw new IllegalValueException(String.format(TodoDispatcher.COMMAND_NOT_FOUND_FORMAT, input)); + } + + String ambiguousCommands = Joiner.on(" or ").join(commands); + throw new IllegalValueException(String.format(TodoDispatcher.AMBIGUOUS_COMMAND_FORMAT, ambiguousCommands)); + } +} +``` +###### \java\seedu\todo\logic\TodoLogic.java +``` java +/** + * Central controller for the application, abstracting application logic from the UI + */ +public class TodoLogic implements Logic { + private final Parser parser; + private final Model model; + private final Dispatcher dispatcher; + + private static final Logger logger = LogsCenter.getLogger(TodoLogic.class); + + public TodoLogic(Parser parser, Model model, Dispatcher dispatcher) { + assert parser != null; + assert model != null; + assert dispatcher != null; + + this.parser = parser; + this.model = model; + this.dispatcher = dispatcher; + } + + public CommandResult execute(String input) { + // Sanity check - input should not be empty + if (StringUtil.isEmpty(input)) { + return new CommandResult(); + } + + ParseResult parseResult = parser.parse(input); + BaseCommand command; + logger.fine("Parsed command: " + parseResult.toString()); + + try { + command = dispatcher.dispatch(parseResult.getCommand()); + } catch (IllegalValueException e) { + return new CommandResult(e.getMessage(), new ErrorBag()); + } + + try { + command.setArguments(parseResult); + command.setModel(model); + return command.execute(); + } catch (ValidationException e) { + logger.info(e.getMessage()); + return new CommandResult(e.getMessage(), e.getErrors()); + } + } + +``` +###### \java\seedu\todo\model\ErrorBag.java +``` java +public class ErrorBag { + private List nonFieldErrors = new ArrayList<>(); + private Map fieldErrors = new HashMap<>(); + + /** + * Add an error that is not related to any specific field + * + * @param nonFieldError the error message + */ + public void put(String nonFieldError) { + nonFieldErrors.add(nonFieldError); + } + + /** + * Add an error that is related to a specific field + * + * @param field the error is for + * @param error the error message + */ + public void put(String field, String error) { + fieldErrors.put(field, error); + } + + public List getNonFieldErrors() { + return nonFieldErrors; + } + + public Map getFieldErrors() { + return fieldErrors; + } + + public int size() { + return nonFieldErrors.size() + fieldErrors.size(); + } + + /** + * Throws a validation exception if the bag contains errors + * + * @param message a short message about why the validation failed + * @throws ValidationException if the ErrorBag is not empty + */ + public void validate(String message) throws ValidationException { + if (size() > 0) { + throw new ValidationException(message, this); + } + } + +``` +###### \java\seedu\todo\model\ImmutableTodoList.java +``` java +public interface ImmutableTodoList { + /** + * Get an immutable list of tasks + */ + List getTasks(); +} +``` +###### \java\seedu\todo\model\Model.java +``` java +public interface Model { + /** + * Adds a new task or event with title only to the todo list. + * + * @param title the title of the task + * @return the task that was just created + * @throws IllegalValueException if the values set in the update predicate is invalid + */ + ImmutableTask add(String title) throws IllegalValueException; + + /** + * Adds a new task or event with title and other fields to the todo list. + * + * @param title the title of the task + * @param update a {@link MutableTask} is passed into this lambda. All other fields + * should be set from inside this lambda. + * @return the task that was just created + * @throws ValidationException if the fields in the task to be updated are not valid + */ + ImmutableTask add(String title, Consumer update) throws ValidationException; + + /** + * Deletes the given task from the todo list. This change is also propagated to the + * underlying persistence layer. + * + * @param index the 1-indexed position of the task that needs to be deleted + * @return the task that was just deleted + * @throws ValidationException if the task does not exist + */ + ImmutableTask delete(int index) throws ValidationException; + + /** + * Deletes all currently visible tasks from the todo list. This change is also propagated to the + * underlying persistence layer. + * + * @return the list of tasks that were just deleted + * @throws ValidationException if any of the tasks do not exist + */ + List deleteAll() throws ValidationException; + + /** + * Replaces certain fields in the task. Mutation of the {@link Task} object should + * only be done in the {@code update} lambda. The lambda takes in one parameter, + * a {@link MutableTask}, and does not expect any return value. For example: + * + *
todo.update(task, t -> {
+     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline back by 2h
+     *     t.setPin(true); // Pin this task
+     * });
+ * + * @return the task that was just updated + * + * @throws ValidationException if the task does not exist or if the fields in the + * task to be updated are not valid + */ + ImmutableTask update(int index, Consumer update) throws ValidationException; + + /** + * Carries out the specified update in the fields of all visible tasks. Mutation of all {@link Task} + * objects should only be done in the {@code update} lambda. The lambda takes in a single parameter, + * a {@link MutableTask}, and does not expect any return value, as per the {@link #update} command. Since + * this represents the observable layer, the changes required to be done to the underlying layer TodoList + * is set via getting a list of their indices in the underlying layer using a UUID map. + * + * Note that no change is carried out if any of the tasks fail, the entire operation will + * fail, and none of the tasks will be updated. + * + *
todo.updateAll (t -> {
+     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline of all Observable tasks back by 2h
+     *     t.setPin(true); // Pin all tasks in Observable view
+     * });
+ * @return the list of tasks which were just updated + * + * @throws ValidationException if any updates on any of the task objects are considered invalid + */ + List updateAll(Consumer update) throws ValidationException; + + /** + * Sets the model to the provided TaskViewFilter object. TaskViewFilters represents the + * filter and sorting needed by each intelligent view + */ + void view(TaskViewFilter view); + + /** + * Filters the list of tasks by this predicate. This is filtering is ran + * after the view predicate. No information about the search is shown to the user. + * Setting predicate to null will reset the search. + */ + void find(Predicate predicate); + + /** + * Filters the list of tasks by this predicate. This is filtering is ran + * after the view predicate. A list of search terms is also shown to the user. + */ + void find(Predicate predicate, List terms); + + /** + * Undoes the last operation that modifies the todolist + * @throws ValidationException if there are no more changes to undo + */ + void undo() throws ValidationException; + + /** + * Redoes the last operation that was undone + * @throws ValidationException if there are no more changes to redo + */ + void redo() throws ValidationException; + + /** + * Changes the save path of the TodoList storage + * @throws ValidationException if the path is not valid + */ + void save(String location) throws ValidationException; + + /** + * Loads a TodoList from the path. + * @throws ValidationException if the path or file is invalid + */ + void load(String location) throws ValidationException; + + /** + * Obtains the current storage methods + */ + String getStorageLocation(); + + /** + * Get an observable list of tasks. Used mainly by the JavaFX UI. + */ + UnmodifiableObservableList getObservableList(); + + /** + * Get the current view filter used on the model. Used mainly by the JavaFx UI. + */ + ObjectProperty getViewFilter(); + + /** + * Get the current status of the search used on the model. + */ + ObjectProperty getSearchStatus(); + +``` +###### \java\seedu\todo\model\task\BaseTask.java +``` java +public abstract class BaseTask implements ImmutableTask { + protected UUID uuid = UUID.randomUUID(); + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ImmutableTask)) { + return false; + } + + return getUUID().equals(((ImmutableTask) o).getUUID()); + } + + @Override + public int hashCode() { + return getUUID().hashCode(); + } + + @Override + public String toString() { + return getTitle(); + } +} +``` +###### \java\seedu\todo\model\task\Task.java +``` java +/** + * Represents a single task + */ +public class Task extends BaseTask implements MutableTask { + private StringProperty title = new SimpleStringProperty(); + private StringProperty description = new SimpleStringProperty(); + private StringProperty location = new SimpleStringProperty(); + + private BooleanProperty pinned = new SimpleBooleanProperty(); + private BooleanProperty completed = new SimpleBooleanProperty(); + + private ObjectProperty startTime = new SimpleObjectProperty<>(); + private ObjectProperty endTime = new SimpleObjectProperty<>(); + + private ObjectProperty> tags = new SimpleObjectProperty<>(new HashSet()); + private LocalDateTime createdAt = LocalDateTime.now(); + + /** + * Creates a new task + */ + public Task(String title) { + this.setTitle(title); + } + + /** + * Constructs a Task from a ReadOnlyTask + */ + public Task(ImmutableTask task) { + this.setTitle(task.getTitle()); + this.setDescription(task.getDescription().orElse(null)); + this.setLocation(task.getLocation().orElse(null)); + this.setStartTime(task.getStartTime().orElse(null)); + this.setEndTime(task.getEndTime().orElse(null)); + this.setCompleted(task.isCompleted()); + this.setPinned(task.isPinned()); + this.setCreatedAt(task.getCreatedAt()); + this.uuid = task.getUUID(); + this.setTags(task.getTags()); + } + + @Override + public String getTitle() { + return title.get(); + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description.get()); + } + + @Override + public Optional getLocation() { + return Optional.ofNullable(location.get()); + } + + @Override + public Optional getStartTime() { + return Optional.ofNullable(startTime.get()); + } + + @Override + public Optional getEndTime() { + return Optional.ofNullable(endTime.get()); + } + + @Override + public boolean isPinned() { + return pinned.get(); + } + + @Override + public boolean isCompleted() { + return completed.get(); + } + + @Override + public Set getTags() { + return Collections.unmodifiableSet(tags.get()); + } + + @Override + public LocalDateTime getCreatedAt() { return createdAt; } + + @Override + public void setTitle(String title) { + this.title.set(title); + } + + @Override + public void setPinned(boolean pinned) { + this.pinned.set(pinned); + } + + @Override + public void setCompleted(boolean completed) { + this.completed.set(completed); + } + + @Override + public void setDescription(String description) { + this.description.set(description); + } + + @Override + public void setLocation(String location) { + this.location.set(location); + } + + @Override + public void setStartTime(LocalDateTime startTime) { + this.startTime.set(startTime); + } + + @Override + public void setEndTime(LocalDateTime endTime) { + this.endTime.set(endTime); + } + + @Override + public void setTags(Set tags) { + this.tags.set(tags); + } + + public void setCreatedAt(LocalDateTime createdAt) { + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + + this.createdAt = createdAt; + } + + public Observable[] getObservableProperties() { + return new Observable[] { + title, description, location, startTime, endTime, tags, completed, pinned, + }; + } +} +``` +###### \java\seedu\todo\model\TodoList.java +``` java +/** + * Represents the todolist inside memory. While Model works as the external + * interface for handling data and application state, this class is internal + * to Model and represents only CRUD operations to the todolist. + */ +public class TodoList implements TodoListModel { + private static final String INCORRECT_FILE_FORMAT_FORMAT = "%s doesn't seem to be in the correct format."; + private static final String FILE_NOT_FOUND_FORMAT = "%s does not seem to exist."; + private static final String FILE_SAVE_ERROR_FORMAT = "Couldn't save file: %s"; + private static final String FILE_LOAD_ERROR_FORMAT = "The data file %s does not appear to be in the correct format"; + + private ObservableList tasks = FXCollections.observableArrayList(Task::getObservableProperties); + + private MovableStorage storage; + + private static final Logger logger = LogsCenter.getLogger(TodoList.class); + private static final EventsCenter events = EventsCenter.getInstance(); + + public TodoList(MovableStorage storage) { + this.storage = storage; + + try { + setTasks(storage.read().getTasks(), false); + } catch (FileNotFoundException e) { + logger.info("Data file not found. Will be starting with an empty TodoList"); + } catch (DataConversionException e) { + String message = String.format(FILE_LOAD_ERROR_FORMAT, storage.getLocation()); + raiseStorageEvent(message, e); + } + } + + private void raiseStorageEvent(String message, Exception e) { + logger.severe("Data IO error - " + e.getClass().getSimpleName() + " | " + e.getMessage()); + events.post(new DataSavingExceptionEvent(message, e)); + } + + private void saveTodoList() { + try { + storage.save(this); + } catch (IOException e) { + String message = String.format(TodoList.FILE_SAVE_ERROR_FORMAT, e.getMessage()); + raiseStorageEvent(message, e); + } + } + + @Override + public ImmutableTask add(String title) { + Task task = new Task(title); + tasks.add(task); + saveTodoList(); + return task; + } + + @Override + public ImmutableTask add(String title, Consumer update) throws ValidationException { + ValidationTask validationTask = new ValidationTask(title); + update.accept(validationTask); + Task task = validationTask.convertToTask(); + tasks.add(task); + saveTodoList(); + return task; + } + + @Override + public List delete(List indexes) throws ValidationException { + List tasksRemoved = new ArrayList<>(); + for (Integer index : indexes) { + ImmutableTask task = tasks.get(index); + tasksRemoved.add(task); + } + tasks.removeAll(tasksRemoved); + saveTodoList(); + return tasksRemoved; + } + + public ImmutableTask delete(int index) throws ValidationException { + List indexes = Lists.newArrayList(index); + //get(0) because there should be only one element returned in the list of deleted tasks + return delete(indexes).get(0); + } + +``` +###### \java\seedu\todo\model\TodoList.java +``` java + @Override + public void save(String location) throws ValidationException { + try { + storage.save(this, location); + } catch (IOException e) { + String message = String.format(TodoList.FILE_SAVE_ERROR_FORMAT, e.getMessage()); + throw new ValidationException(message); + } + } + + @Override + public void load(String location) throws ValidationException { + try { + setTasks(storage.read(location).getTasks()); + } catch (DataConversionException e) { + throw new ValidationException(TodoList.INCORRECT_FILE_FORMAT_FORMAT); + } catch (FileNotFoundException e) { + String message = String.format(TodoList.FILE_NOT_FOUND_FORMAT, location); + throw new ValidationException(message); + } + } + + @Override + public void setTasks(List todoList) { + setTasks(todoList, true); + } + + /** + * We have a private version of setTasks because we also need to setTask during initialization, + * but we don't want the list to be save during init (where we presumably got the data from) + */ + private void setTasks(List todoList, boolean persistToStorage) { + this.tasks.clear(); + this.tasks.addAll(todoList.stream().map(Task::new).collect(Collectors.toList())); + + if (persistToStorage) { + saveTodoList(); + } + } + + @Override + public ObservableList getObservableList() { + return new UnmodifiableObservableList<>(tasks); + } + + @Override + public List getTasks() { + return Collections.unmodifiableList(tasks); + } +} +``` +###### \java\seedu\todo\model\TodoListModel.java +``` java +public interface TodoListModel extends ImmutableTodoList { + /** + * Adds a new task or event with title only to the todo list. + * + * @param title the title of the task + * @return the task that was just created + * @throws IllegalValueException if the values set in the update predicate is invalid + */ + ImmutableTask add(String title) throws IllegalValueException; + + /** + * Adds a new task or event with title and other fields to the todo list. + * + * @param title the title of the task + * @param update a {@link MutableTask} is passed into this lambda. All other fields + * should be set from inside this lambda. + * @return the task that was just created + * @throws ValidationException if the fields in the task to be updated are not valid + */ + ImmutableTask add(String title, Consumer update) throws ValidationException; + + /** + * Deletes the list of given task from the todo list. The changes are also propagated to the + * underlying persistence layer. + * + * @param index the 1-indexed position of the tasks that need to be deleted + * @return the list of tasks that was just deleted + * @throws ValidationException if the task does not exist + */ + List delete(List indexes) throws ValidationException; + + /** + * Deletes the given task from the todo list. The change is also propagated to the + * underlying persistence layer. + * + * @param index the 1-indexed position of the tasks that need to be deleted + * @return the list of tasks that was just deleted + * @throws ValidationException if the task does not exist + */ + ImmutableTask delete(int index) throws ValidationException; + + /** + * Replaces certain fields in the tasks specified. Mutation of the {@link Task} objects should + * only be done in the update lambda. The lambda takes in one parameter, + * a {@link MutableTask}, and does not expect any return value. For example: + * + *
todo.update(task, t -> {
+     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline back by 2h
+     *     t.setPin(true); // Pin this task
+     * });
+ * + * @return the list of tasks that were just updated + * + * @throws ValidationException if the task does not exist or if the fields in the + * task to be updated are not valid + */ + List update(List indexes, Consumer update) throws ValidationException; + + /** + * Replaces certain fields in the task. Mutation of the {@link Task} object should + * only be done in the update lambda. The lambda takes in one parameter, + * a {@link MutableTask}, and does not expect any return value. For example: + * + *
todo.update(task, t -> {
+     *     t.setEndTime(t.getEndTime.get().plusHours(2)); // Push deadline back by 2h
+     *     t.setPin(true); // Pin this task
+     * });
+ * + * @return the task that was just updated + * + * @throws ValidationException if the task does not exist or if the fields in the + * task to be updated are not valid + */ + + ImmutableTask update(int index, Consumer update) throws ValidationException; + +``` +###### \java\seedu\todo\model\TodoListModel.java +``` java + /** + * Changes the save path of the TodoList storage + * @throws ValidationException if the path is not valid + */ + void save(String location) throws ValidationException; + + /** + * Loads a TodoList from the path. + * @throws ValidationException if the path or file is invalid + */ + void load(String location) throws ValidationException; + + /** + * Replaces the tasks in list with the one in the + */ + void setTasks(List todoList); + + /** + * Get an observable list of tasks. Used mainly by the JavaFX UI. + */ + ObservableList getObservableList(); + +} + +``` +###### \java\seedu\todo\model\TodoModel.java +``` java +/** + * Represents the data layer of the application. The TodoModel handles any + * interaction with the application state that are not persisted, such as the + * view (sort and filtering), undo and redo. Since this layer handles + * sorting and filtering, task ID must be passed through {@link #getTaskIndex} + * to transform them into the index {@link TodoList} methods can use. + */ +public class TodoModel implements Model { + // Constants + private static final int UNDO_LIMIT = 10; + private static final String INDEX_OUT_OF_BOUND_FORMAT = "There is no task no. %d"; + private static final String NO_MORE_UNDO_REDO_FORMAT = "There are no more steps to %s"; + + // Dependencies + private TodoListModel todoList; + private UniqueTagCollectionModel uniqueTagCollection; + private MovableStorage storage; + + // Stack of transformation that the tasks go through before being displayed to the user + private ObservableList tasks; + private FilteredList viewFilteredTasks; + private FilteredList findFilteredTasks; + private SortedList sortedTasks; + + // State stacks for managing un/redo + private Deque> undoStack = new ArrayDeque<>(); + private Deque> redoStack = new ArrayDeque<>(); + + /** + * Contains the current view tab the user has selected. + * {@link #getViewFilter()} is the getter and {@link #view(TaskViewFilter)} is the setter + */ + private ObjectProperty view = new SimpleObjectProperty<>(); + + private ObjectProperty search = new SimpleObjectProperty<>(); + + public TodoModel(Config config) { + this(new TodoListStorage(config.getTodoListFilePath())); + } + + public TodoModel(MovableStorage storage) { + this(new TodoList(storage), storage); + } + + public TodoModel(TodoListModel todoList, MovableStorage storage) { + this.storage = storage; + this.todoList = todoList; + + tasks = todoList.getObservableList(); + viewFilteredTasks = new FilteredList<>(tasks); + findFilteredTasks = new FilteredList<>(viewFilteredTasks); + sortedTasks = new SortedList<>(findFilteredTasks); + + uniqueTagCollection = new UniqueTagCollection(todoList.getTasks()); + + // Sets the default view + view(TaskViewFilter.DEFAULT); + } + + /** + * Because the model does filtering and sorting on the tasks, the incoming index needs to be + * translated into it's index in the underlying todoList. The code below is not particularly + * clean, but it works well enough. + * + * @throws ValidationException if the index is invalid + */ + private int getTaskIndex(int index) throws ValidationException { + int taskIndex; + try { + ImmutableTask task = getObservableList().get(index - 1); + taskIndex = tasks.indexOf(task); + } catch (IndexOutOfBoundsException e) { + taskIndex = -1; + } + if (taskIndex == -1) { + String message = String.format(TodoModel.INDEX_OUT_OF_BOUND_FORMAT, index); + throw new ValidationException(message); + } + + return taskIndex; + } + + private void saveState(Deque> stack) { + List tasks = todoList.getTasks().stream() + .map(Task::new).collect(Collectors.toList()); + + stack.addFirst(tasks); + while (stack.size() > TodoModel.UNDO_LIMIT) { + stack.removeLast(); + } + } + + private void saveUndoState() { + saveState(undoStack); + redoStack.clear(); + } + + @Override + public ImmutableTask add(String title) throws IllegalValueException { + saveUndoState(); + return todoList.add(title); + } + + @Override + public ImmutableTask add(String title, Consumer update) throws ValidationException { + saveUndoState(); + return todoList.add(title, update); + } +``` +###### \java\seedu\todo\model\TodoModel.java +``` java + @Override + public void view(TaskViewFilter view) { + viewFilteredTasks.setPredicate(view.filter); + sortedTasks.setComparator((a, b) -> { + int pin = Boolean.compare(b.isPinned(), a.isPinned()); + return pin != 0 || view.sort == null ? pin : view.sort.compare(a, b); + }); + + this.view.setValue(view); + } + + @Override + public void find(Predicate predicate) { + findFilteredTasks.setPredicate(predicate); + search.setValue(null); + } + + @Override + public void find(Predicate predicate, List terms) { + findFilteredTasks.setPredicate(predicate); + search.setValue(new SearchStatus(terms, findFilteredTasks.size(), tasks.size())); + } + + @Override + public void undo() throws ValidationException { + if (undoStack.isEmpty()) { + String message = String.format(TodoModel.NO_MORE_UNDO_REDO_FORMAT, "undo"); + throw new ValidationException(message); + } + + List tasks = undoStack.removeFirst(); + uniqueTagCollection.update(tasks); + saveState(redoStack); + todoList.setTasks(tasks); + } + + @Override + public void redo() throws ValidationException { + if (redoStack.isEmpty()) { + String message = String.format(TodoModel.NO_MORE_UNDO_REDO_FORMAT, "redo"); + throw new ValidationException(message); + } + + List tasks = redoStack.removeFirst(); + uniqueTagCollection.update(tasks); + saveState(undoStack); + todoList.setTasks(tasks); + } + + @Override + public void save(String location) throws ValidationException { + todoList.save(location); + } + + @Override + public void load(String location) throws ValidationException { + todoList.load(location); + uniqueTagCollection.update(tasks); + } + + @Override + public String getStorageLocation() { + return storage.getLocation(); + } + + @Override + public UnmodifiableObservableList getObservableList() { + return new UnmodifiableObservableList<>(sortedTasks); + } + + @Override + public ObjectProperty getViewFilter() { + return view; + } + + @Override + public ObjectProperty getSearchStatus() { + return search; + } + +``` +###### \java\seedu\todo\storage\FixedStorage.java +``` java +/** + * Represents a storage mechanism to save an object to a fixed location + * @param + */ +public interface FixedStorage { + /** + * Reads the object from storage + * @return the object read from storage + * @throws DataConversionException if the data read was not of the expected type + * @throws FileNotFoundException if there was no file + */ + T read() throws DataConversionException, FileNotFoundException; + + /** + * Persists an object + * @param object the object that needs to be persisted + * @throws IOException if there was any problem saving the object to storage + */ + void save(T object) throws IOException; +} +``` +###### \java\seedu\todo\storage\MovableStorage.java +``` java +/** + * Represents an storage mechanism that allows an object to be saved to a + * specific location + */ +public interface MovableStorage extends FixedStorage { + + String getLocation(); + + T read(String location) throws DataConversionException, FileNotFoundException; + + void save(T object, String newLocation) throws IOException; +} +``` +###### \java\seedu\todo\ui\controller\TextAreaResizer.java +``` java +/** + * Automatically resize a text area object such that the height fits the content of the text area + */ +public class TextAreaResizer { + // Constants + private static final int CHAR_AHEAD = 3; + private static final int PADDING_OFFSET = 5; + + // FXML elements + private final TextArea textArea; + private final Text pseudoText = new Text(); + + // Internal variables + private double lineHeight; + private double charWidth; + private double textAreaWidth; + private int textAreaChars; + + public TextAreaResizer(TextArea textArea) { + this.textArea = textArea; + textAreaChars = textArea.getText().length(); + textAreaWidth = textArea.getWidth(); + + pseudoText.fontProperty().addListener((observable, oldValue, newValue) -> updateCharacterWidth()); + pseudoText.fontProperty().bind(textArea.fontProperty()); + + textArea.widthProperty().addListener((observable, oldValue, newValue) -> { + textAreaWidth = (double) newValue - PADDING_OFFSET; + updateTextArea(); + }); + + textArea.textProperty().addListener((observable, oldValue, newValue) -> { + textAreaChars = newValue.length(); + updateTextArea(); + }); + } + + private void updateCharacterWidth() { + final int TEST_CHAR_LEN = 60; + + pseudoText.setText(""); + double initialWidth = pseudoText.getLayoutBounds().getWidth(); + pseudoText.setText(Strings.repeat("w", TEST_CHAR_LEN)); + double newWidth = pseudoText.getLayoutBounds().getWidth(); + charWidth = (newWidth - initialWidth) / TEST_CHAR_LEN; + } + + private void updateTextArea() { + // Silly hack to make sure lineHeight is available before continuing + if (lineHeight <= 0) { + if ((lineHeight = textArea.getLayoutBounds().getHeight() - PADDING_OFFSET) <= 0) { + return; + } + } + + int lines = (int) (((textAreaChars + CHAR_AHEAD) * charWidth) / textAreaWidth) + 1; + double height = lines * lineHeight + PADDING_OFFSET; + + Platform.runLater(() -> { + textArea.setMaxHeight(height); + textArea.setPrefHeight(height); + }); + } +} +``` +###### \java\seedu\todo\ui\view\TaskCardView.java +``` java + /** + * Sets style according to the status (e.g. completed, overdue, etc) of the task. + */ + private void setStyle() { + ViewStyleUtil.addClassStyles(taskCard, task.isEvent() ? "event" : "task"); + + if (task.isCompleted()) { + ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_COMPLETED); + } else if (task.getEndTime().isPresent() && timeUtil.isOverdue(task.getEndTime().get())) { + ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_OVERDUE); + } else if (task.isEvent() && timeUtil.isOngoing(task.getStartTime().get(), task.getEndTime().get())) { + ViewStyleUtil.addClassStyles(taskCard, ViewStyleUtil.STYLE_ONGOING); + } + } + +``` +###### \resources\style\DefaultStyle.css +``` css +.searchStatus { + -fx-padding: 4px; +} + +.searchStatus Text { + -fx-font-size: 16px; + -fx-fill: #f3f3f3; +} + +.searchStatus .searchLabel { +} + +.searchStatus .searchTerm { + -fx-font-weight: bold; +} + +.searchStatus .searchCount { + -fx-text-alignment: right; +} + +``` diff --git a/collated/A0135817Breuse.md b/collated/main/A0135817Breused.md similarity index 56% rename from collated/A0135817Breuse.md rename to collated/main/A0135817Breused.md index a84e8a57536d..8f51fe1cbf1a 100644 --- a/collated/A0135817Breuse.md +++ b/collated/main/A0135817Breused.md @@ -1,5 +1,44 @@ -# A0135817Breuse -###### \src\main\java\seedu\todo\commons\util\TimeUtil.java +# A0135817Breused +###### \java\seedu\todo\commons\events\ui\ExitAppRequestEvent.java +``` java +/** + * Indicates a request for App termination + */ +public class ExitAppRequestEvent extends BaseEvent { + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\todo\commons\exceptions\DataConversionException.java +``` java +/** + * Represents an error during conversion of data from one format to another + */ +public class DataConversionException extends Exception { + public DataConversionException(Exception cause) { + super(cause); + } + +} +``` +###### \java\seedu\todo\commons\exceptions\IllegalValueException.java +``` java +/** + * Signals that some given data does not fulfill some constraints. + */ +public class IllegalValueException extends Exception { + /** + * @param message should contain relevant information on the failed constraint(s) + */ + public IllegalValueException(String message) { + super(message); + } +} +``` +###### \java\seedu\todo\commons\util\TimeUtil.java ``` java // From http://stackoverflow.com/a/27378709/313758 /** diff --git a/collated/main/A0139021U.md b/collated/main/A0139021U.md new file mode 100644 index 000000000000..c1a0945afb84 --- /dev/null +++ b/collated/main/A0139021U.md @@ -0,0 +1,532 @@ +# A0139021U +###### \java\seedu\todo\commons\events\ui\ShowPreviewEvent.java +``` java +/** + * An event requesting to view the help page. + */ +public class ShowPreviewEvent extends BaseEvent { + private List commandSummaries; + + public ShowPreviewEvent(List commandSummaries) { + this.commandSummaries = commandSummaries; + } + + public List getPreviewInfo() { + return commandSummaries; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} +``` +###### \java\seedu\todo\commons\util\StringUtil.java +``` java + /** + * Calculates the levenstein distance between the two strings and returns + * their closeness in a percentage score. + * @param s1 The first string + * @param s2 The second string + * @return The percentage score of their closeness + */ + public static double calculateClosenessScore(String s1, String s2) { + // empty string, not close at all + if (isEmpty(s1) || isEmpty(s2)) { + return 0d; + } + + s1 = s1.toLowerCase(); + s2 = s2.toLowerCase(); + int distance = StringUtils.getLevenshteinDistance(s1, s2); + double ratio = ((double) distance) / (Math.max(s1.length(), s2.length())); + return 100 - ratio * 100; + } +} +``` +###### \java\seedu\todo\logic\commands\CommandMap.java +``` java +public class CommandMap { + // Threshold for accuracy of the fuzzy match + private static final double CLOSENESS_THRESHOLD = 50d; + private static final int COMMAND_INDEX = 0; + + // List of command classes. Remember to register new commands here so that the + // dispatcher can recognize them + private static List> commandClasses = ImmutableList.of( + AddCommand.class, + ClearCommand.class, + CompleteCommand.class, + DeleteCommand.class, + EditCommand.class, + ExitCommand.class, + HelpCommand.class, + PinCommand.class, + UndoCommand.class, + RedoCommand.class, + SaveCommand.class, + LoadCommand.class, + ShowCommand.class, + FindCommand.class, + ViewCommand.class, + TagCommand.class + ); + + private static Map> commandMap; + private static Map> commandSummaryMap; + + private static void buildCommandMap() { + // Use immutable map so commands are presented to user in the same order every time + ImmutableMap.Builder> commandMapBuilder = ImmutableMap.builder(); + + for (Class command : commandClasses) { + String commandName = getCommand(command).getCommandName().toLowerCase(); + commandMapBuilder.put(commandName, command); + } + + commandMap = commandMapBuilder.build(); + } + + public static BaseCommand getCommand(String key) { + return getCommand(getCommandMap().get(key)); + } + + public static BaseCommand getCommand(Class command) { + try { + return command.newInstance(); + } catch (InstantiationException|IllegalAccessException e) { + e.printStackTrace(); + return null; // This shouldn't happen + } + } + + public static Map> getCommandMap() { + if (commandMap == null) { + buildCommandMap(); + } + + return commandMap; + } + + private static void buildCommandSummariesMap() { + // Use immutable map so summaries are presented to user in the same order every time + ImmutableMap.Builder> commandSummaryBuilder = ImmutableMap.builder(); + + for (String key : getCommandMap().keySet()) { + commandSummaryBuilder.put(key, CommandMap.getCommand(key).getCommandSummary()); + } + commandSummaryMap = commandSummaryBuilder.build(); + } + + public static Map> getCommandSummaryMap() { + if (commandSummaryMap == null) { + buildCommandSummariesMap(); + } + + return commandSummaryMap; + } + + public static List getAllCommandSummary() { + // convert map to list + List commandSummariesList = new ArrayList<>(); + getCommandSummaryMap().values().forEach(commandSummariesList::addAll); + return commandSummariesList; + } + + public static List filterCommandKeys(String input) { + List commands = new ArrayList<>(); + + // No input + if (StringUtil.isEmpty(input)) { + return commands; + } + Set allCommands = getCommandMap().keySet(); + String command = getUserCommand(input); + + if (allCommands.contains(command)) { + // If one-to-one correspondence, return only the command + commands.add(command); + } else { + // Else add relevant commands if they fit the criteria + commands = allCommands.stream().filter(key -> key.startsWith(command) || + StringUtil.calculateClosenessScore(key, command) > CLOSENESS_THRESHOLD + ).collect(Collectors.toList()); + } + return commands; + } + + private static String getUserCommand(String userInput) { + List inputList = Lists.newArrayList(Splitter.on(" ") + .trimResults() + .omitEmptyStrings() + .split(userInput.toLowerCase())); + return inputList.get(COMMAND_INDEX); + } +} +``` +###### \java\seedu\todo\logic\commands\CommandPreview.java +``` java +/** + * Represents all relevant commands that will be used to show to the user. + */ +public class CommandPreview { + private List commandSummaries; + + public CommandPreview(String userInput) { + commandSummaries = filterCommandSummaries(userInput); + } + + public List getPreview() { + return commandSummaries; + } + + private List filterCommandSummaries(String input) { + List summaries = new ArrayList<>(); + List keys = CommandMap.filterCommandKeys(input); + CommandMap.getCommandSummaryMap().forEach((key, value) -> { + if (keys.contains(key)) { + summaries.addAll(value); + } + }); + return summaries; + } +} +``` +###### \java\seedu\todo\logic\Logic.java +``` java + /** + * Receives the intermediate product of the command and sends a ShowPreviewEvent. + * @param input The intermediate input as entered by the user. + */ + void preview(String input); +} +``` +###### \java\seedu\todo\logic\TodoLogic.java +``` java + @Override + public void preview(String input) { + List listOfCommands = new CommandPreview(input).getPreview(); + EventsCenter.getInstance().post(new ShowPreviewEvent(listOfCommands)); + } +} +``` +###### \java\seedu\todo\model\task\ValidationTask.java +``` java +public class ValidationTask extends BaseTask implements MutableTask { + private static final String END_TIME = "endTime"; + private static final String TITLE = "title"; + private static final String ONLY_START_TIME_ERROR_MESSAGE = "You must define an ending time."; + private static final String TITLE_EMPTY_ERROR_MESSAGE = "Your title should not be empty."; + private static final String VALIDATION_ERROR_MESSAGE = "Your task is not in the correct format."; + private static final String START_AFTER_END_ERROR_MESSAGE = "No time travelling allowed! You've finished before you even start."; + + private ErrorBag errors = new ErrorBag(); + + private String title; + private String description; + private String location; + + private boolean pinned; + private boolean completed; + + private LocalDateTime startTime; + private LocalDateTime endTime; + + private Set tags = new HashSet<>(); + private LocalDateTime createdAt; + + public ValidationTask(String title) { + this.setTitle(title); + this.setCreatedAt(); + } + + /** + * Constructs a ValidationTask from an ImmutableTask + */ + public ValidationTask(ImmutableTask task) { + this.setTitle(task.getTitle()); + this.setDescription(task.getDescription().orElse(null)); + this.setLocation(task.getLocation().orElse(null)); + this.setStartTime(task.getStartTime().orElse(null)); + this.setEndTime(task.getEndTime().orElse(null)); + this.setCompleted(task.isCompleted()); + this.setPinned(task.isPinned()); + + this.createdAt = task.getCreatedAt(); + this.uuid = task.getUUID(); + } + + /** + * Validates the task by checking the individual fields are valid. + */ + public void validate() throws ValidationException { + isValidTime(); + isValidTitle(); + errors.validate(VALIDATION_ERROR_MESSAGE); + } + + private void isValidTitle() { + if (StringUtil.isEmpty(title)) { + errors.put(TITLE, TITLE_EMPTY_ERROR_MESSAGE); + } + } + + /** + * Validates time. Only valid when + * 1) both time fields are not declared + * 2) end time is present + * 3) start time is before end time + */ + private void isValidTime() { + if (startTime == null && endTime == null) { + return; + } else if (endTime == null) { + errors.put(END_TIME, ONLY_START_TIME_ERROR_MESSAGE); + } else if (startTime != null && startTime.isAfter(endTime)) { + errors.put(END_TIME, START_AFTER_END_ERROR_MESSAGE); + } + } + + /** + * Converts the validation task into an actual task for consumption. + * + * @return A task with observable properties + */ + public Task convertToTask() throws ValidationException { + validate(); + return new Task(this); + } + + @Override + public String getTitle() { + return title; + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public Optional getLocation() { + return Optional.ofNullable(location); + } + + @Override + public Optional getStartTime() { + return Optional.ofNullable(startTime); + } + + @Override + public Optional getEndTime() { + return Optional.ofNullable(endTime); + } + + @Override + public boolean isPinned() { + return pinned; + } + + @Override + public boolean isCompleted() { + return completed; + } + + @Override + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + public LocalDateTime getCreatedAt() { return createdAt; } + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public void setTitle(String title) { + this.title = title; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public void setLocation(String location) { + this.location = location; + } + + @Override + public void setPinned(boolean pinned) { + this.pinned = pinned; + } + + @Override + public void setCompleted(boolean completed) { + this.completed = completed; + } + + @Override + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + @Override + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + @Override + public void setTags(Set tags) { + this.tags = tags; + } + + public void setCreatedAt() { this.createdAt = LocalDateTime.now(); } +} +``` +###### \java\seedu\todo\storage\LocalDateTimeAdapter.java +``` java +public class LocalDateTimeAdapter extends XmlAdapter { + + @Override + public LocalDateTime unmarshal(String v) throws Exception { + return LocalDateTime.parse(v); + } + + @Override + public String marshal(LocalDateTime v) throws Exception { + return v.toString(); + } +} +``` +###### \java\seedu\todo\ui\controller\CommandController.java +``` java + /** + * Handles a key stroke from input and sends it to logic. Once logic sends back a preview, it will be + * processed by {@link #handleCommandResult(CommandResult)} + * @param keyCode key pressed by user + * @param userInput text as shown in input view + */ + private void handleInput(KeyCode keyCode, String userInput) { + switch (keyCode) { + case ENTER : // Submitting command + if (!StringUtil.isEmpty(userInput)) { + CommandResult result = logic.execute(userInput); + handleCommandResult(result); + } + break; + default : // Typing command, show preview + logic.preview(userInput); + errorView.hideCommandErrorView(); // Don't show error when previewing + break; + } + } + + /** + * Handles a CommandResult object, and updates the user interface to reflect the result. + * @param result produced by {@link Logic} + */ + private void handleCommandResult(CommandResult result) { + previewView.hidePreviewPanel(); + displayMessage(result.getFeedback()); + if (result.isSuccessful()) { + viewDisplaySuccess(); + } else { + viewDisplayError(result.getErrors()); + } + } + +``` +###### \java\seedu\todo\ui\UiManager.java +``` java + @Subscribe + private void handleShowPreviewEvent(ShowPreviewEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.getHelpView().hideHelpPanel(); + mainWindow.getCommandPreviewView().displayCommandSummaries(event.getPreviewInfo()); + mainWindow.getCommandFeedbackView().clearMessage(); + } + +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java + /** + * Sets {@link #commandTextField} to listen out for keystrokes. + * Once a keystroke is received, calls {@link KeyStrokeCallback} interface to process this command. + */ + public void listenToInput(KeyStrokeCallback listener) { + this.commandTextField.addEventHandler(KeyEvent.KEY_RELEASED, event -> { + KeyCode keyCode = event.getCode(); + String textInput = commandTextField.getText(); + + boolean isNonEssential = keyCode.isNavigationKey() || + keyCode.isFunctionKey() || + keyCode.isMediaKey() || + keyCode.isModifierKey(); + + if (!isNonEssential) { + listener.onKeyStroke(keyCode, textInput); + } + }); + } + +``` +###### \java\seedu\todo\ui\view\CommandInputView.java +``` java + /*Interface Declarations*/ + /** + * Defines an interface for controller class to receive a key stroke from this view class, and process it. + */ + public interface KeyStrokeCallback { + void onKeyStroke(KeyCode keyCode, String text); + } +} +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/collated/A0139021Ureused.md b/collated/main/A0139021Ureused.md similarity index 53% rename from collated/A0139021Ureused.md rename to collated/main/A0139021Ureused.md index 483a89748de7..b5d5e31916d0 100644 --- a/collated/A0139021Ureused.md +++ b/collated/main/A0139021Ureused.md @@ -1,5 +1,89 @@ # A0139021Ureused -###### \src\main\java\seedu\todo\storage\XmlAdaptedTask.java +###### \java\seedu\todo\model\UserPrefs.java +``` java +/** + * Represents User's preferences. + */ +public class UserPrefs { + + private GuiSettings guiSettings; + private static final Logger logger = LogsCenter.getLogger(UserPrefs.class); + private UserPrefsStorage storage; + + public GuiSettings getGuiSettings() { + return guiSettings == null ? new GuiSettings() : guiSettings; + } + + public void updateLastUsedGuiSetting(GuiSettings guiSettings) { + this.guiSettings = guiSettings; + } + + public UserPrefs(){ + this.setDefaultGuiSettings(); + } + + public UserPrefs(Config config) { + assert config != null; + + String prefsFilePath = config.getUserPrefsFilePath(); + logger.info("Using prefs file : " + prefsFilePath); + + this.storage = new UserPrefsStorage(prefsFilePath); + try { + this.updateLastUsedGuiSetting(storage.read().getGuiSettings()); + } catch (DataConversionException e) { + logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. " + + "Using default user prefs"); + this.setDefaultGuiSettings(); + } + + //Update prefs file in case it was missing to begin with or there are new/unused fields + this.save(); + } + + private void setDefaultGuiSettings() { + this.setGuiSettings(680, 780, 0, 0); + } + + public void setGuiSettings(double width, double height, int x, int y) { + guiSettings = new GuiSettings(width, height, x, y); + } + + public void save() { + try { + storage.save(this); + } catch (IOException e) { + logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); + } + } + + @Override + public boolean equals(Object other) { + if (other == this){ + return true; + } + if (!(other instanceof UserPrefs)){ //this handles null as well. + return false; + } + + UserPrefs o = (UserPrefs)other; + + return Objects.equals(guiSettings, o.guiSettings); + } + + @Override + public int hashCode() { + return Objects.hash(guiSettings); + } + + @Override + public String toString(){ + return guiSettings.toString(); + } + +} +``` +###### \java\seedu\todo\storage\XmlAdaptedTask.java ``` java /** * JAXB-friendly version of the task. @@ -41,9 +125,8 @@ public class XmlAdaptedTask { /** * Converts a given Task into this class for JAXB use. * - * @param source - * future changes to this will not affect the created - * XmlAdaptedPerson + * @param source future changes to this will not affect the created + * XmlAdaptedTask */ public XmlAdaptedTask(ImmutableTask source) { title = source.getTitle(); @@ -94,7 +177,7 @@ public class XmlAdaptedTask { } } ``` -###### \src\main\java\seedu\todo\storage\XmlSerializableTodoList.java +###### \java\seedu\todo\storage\XmlSerializableTodoList.java ``` java /** * An Immutable TodoList that is serializable to XML format @@ -128,10 +211,13 @@ public class XmlSerializableTodoList implements ImmutableTodoList { return p.toModelType(); } catch (IllegalValueException e) { e.printStackTrace(); - //TODO: better error handling + // This likely means that the task format changed between versions of + // the app. Unfortunately there's no good way to migrate data yet, + // so unfortunately we will lose some data here return null; } - }).collect(Collectors.toCollection(ArrayList::new)); + }).filter(task -> task != null) + .collect(Collectors.toCollection(ArrayList::new)); } } ``` diff --git a/collated/main/generated.md b/collated/main/generated.md new file mode 100644 index 000000000000..0bab8fb74d8d --- /dev/null +++ b/collated/main/generated.md @@ -0,0 +1,348 @@ +# generated +###### \resources\view\CommandErrorView.fxml +``` fxml + + + + + + + + + + + + + + + + + + +``` +###### \resources\view\CommandFeedbackView.fxml +``` fxml + + + + + + + + + + +``` +###### \resources\view\CommandInputView.fxml +``` fxml + + + + + +