diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToPersonListRequestEvent.java similarity index 76% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/seedu/address/commons/events/ui/JumpToPersonListRequestEvent.java index 4fc32183f074..282f871adb47 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/JumpToPersonListRequestEvent.java @@ -6,11 +6,11 @@ /** * Indicates a request to jump to the list of persons */ -public class JumpToListRequestEvent extends BaseEvent { +public class JumpToPersonListRequestEvent extends BaseEvent { public final int targetIndex; - public JumpToListRequestEvent(Index targetIndex) { + public JumpToPersonListRequestEvent(Index targetIndex) { this.targetIndex = targetIndex.getZeroBased(); } diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToScheduleListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToScheduleListRequestEvent.java new file mode 100644 index 000000000000..99748e5b8c3f --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/JumpToScheduleListRequestEvent.java @@ -0,0 +1,22 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of persons + */ +public class JumpToScheduleListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToScheduleListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/SchedulePanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/SchedulePanelSelectionChangedEvent.java new file mode 100644 index 000000000000..1e05e7a99a17 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/SchedulePanelSelectionChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.ui.ScheduleCard; + +/** + * Represents a selection change in the Person List Panel + */ +public class SchedulePanelSelectionChangedEvent extends BaseEvent { + + private final ScheduleCard newSelection; + + public SchedulePanelSelectionChangedEvent(ScheduleCard newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public ScheduleCard getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index a63c37af8f49..ecdc4e5814c5 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -6,6 +6,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; +import seedu.address.model.schedule.ReadOnlySchedule; /** * API of the Logic component @@ -26,6 +27,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of groups */ ObservableList getFilteredGroupList(); + /** Returns an unmodifiable view of the filtered list of schedules */ + ObservableList getFilteredScheduleList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 811a6d5da3ad..af0797119ad5 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -13,6 +13,7 @@ import seedu.address.model.Model; import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; +import seedu.address.model.schedule.ReadOnlySchedule; /** * The main LogicManager of the app. @@ -56,6 +57,11 @@ public ObservableList getFilteredGroupList() { return model.getFilteredGroupList(); } + @Override + public ObservableList getFilteredScheduleList() { + return model.getFilteredScheduleList(); + } + @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 5c13c54d35b6..347b480cf89e 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -17,32 +17,52 @@ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_ALT = "d"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the last person listing.\n" + + ": Deletes the person identified by the index number(s) used in the last person listing.\n" + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + "Example: " + COMMAND_WORD + " 1 2 3"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - private final Index targetIndex; + private Index targetIndex; + + private Index[] targetIndexes; public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; } + public DeleteCommand(Index[] targetIndexes) { + this.targetIndexes = targetIndexes; + } @Override public CommandResult executeUndoableCommand() throws CommandException { List lastShownList = model.getFilteredPersonList(); - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + ReadOnlyPerson personToDelete = null; + ReadOnlyPerson[] personsToDelete = null; + + if (targetIndex != null) { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + personToDelete = lastShownList.get(targetIndex.getZeroBased()); } - ReadOnlyPerson personToDelete = lastShownList.get(targetIndex.getZeroBased()); + if (targetIndexes != null) { + personsToDelete = new ReadOnlyPerson[targetIndexes.length]; + for (int i = 0; i < personsToDelete.length; i++) { + personsToDelete[i] = lastShownList.get(targetIndexes[i].getZeroBased()); + } + } try { - model.deletePerson(personToDelete); + if (personsToDelete == null) { + model.deletePerson(personToDelete); + } else { + model.deletePersons(personsToDelete); + } } catch (PersonNotFoundException pnfe) { assert false : "The target person cannot be missing"; } diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index 67b85a7e6f1a..b80252c1538c 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -6,7 +6,7 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.events.ui.JumpToGroupListRequestEvent; -import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.JumpToPersonListRequestEvent; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; @@ -52,7 +52,7 @@ public CommandResult execute() throws CommandException { if (targetIndex.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + EventsCenter.getInstance().post(new JumpToPersonListRequestEvent(targetIndex)); return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index fe9c1653850e..00b35d976a43 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -19,8 +19,17 @@ public class DeleteCommandParser implements Parser { */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + String[] arguments = args.trim().split(" "); + if (arguments.length == 1) { + Index index = ParserUtil.parseIndex(args); + return new DeleteCommand(index); + } else { + Index[] indexes = new Index[arguments.length]; + for (int i = 0; i < indexes.length; i++) { + indexes[i] = ParserUtil.parseIndex(arguments[i]); + } + return new DeleteCommand(indexes); + } } catch (IllegalValueException ive) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 81a785cd8c12..50ddc542e17b 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -23,6 +23,11 @@ import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.NoPersonsException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.schedule.ReadOnlySchedule; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.UniqueScheduleList; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; @@ -36,6 +41,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; private final UniqueTagList tags; private final UniqueGroupList groups; + private final UniqueScheduleList schedules; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -48,6 +54,7 @@ public class AddressBook implements ReadOnlyAddressBook { persons = new UniquePersonList(); tags = new UniqueTagList(); groups = new UniqueGroupList(); + schedules = new UniqueScheduleList(); } public AddressBook() {} @@ -278,6 +285,32 @@ public boolean removeGroup(ReadOnlyGroup key) throws GroupNotFoundException { throw new GroupNotFoundException(); } } + + //// schedule-level operations + + /** + * Adds a schedule to the address book. + * + * @throws DuplicateScheduleException if an equivalent schedule already exists. + */ + + public void addSchedule(ReadOnlySchedule s) throws DuplicateScheduleException { + Schedule newSchedule = new Schedule(s); + schedules.add(newSchedule); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws ScheduleNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeSchedule(ReadOnlySchedule key) throws ScheduleNotFoundException { + if (schedules.remove(key)) { + return true; + } else { + throw new ScheduleNotFoundException(); + } + } + //// util methods @Override @@ -304,6 +337,11 @@ public ObservableList getGroupList() { return groups.asObservableList(); } + @Override + public ObservableList getScheduleList() { + return schedules.asObservableList(); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index db09df19e9fb..89fb16ce9434 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -12,6 +12,9 @@ import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.NoPersonsException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.schedule.ReadOnlySchedule; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; /** * The API of the Model component. @@ -23,6 +26,9 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_GROUPS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_SCHEDULES = unused -> true; + /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -32,6 +38,9 @@ public interface Model { /** Deletes the given person. */ void deletePerson(ReadOnlyPerson target) throws PersonNotFoundException; + /** Deletes the given persons. */ + void deletePersons(ReadOnlyPerson[] targets) throws PersonNotFoundException; + /** Adds the given person */ void addPerson(ReadOnlyPerson person) throws DuplicatePersonException; @@ -44,6 +53,12 @@ public interface Model { /** Deletes the given group */ void deleteGroup(ReadOnlyGroup group) throws GroupNotFoundException; + /** Adds the given schedule */ + void addSchedule(ReadOnlySchedule schedule) throws DuplicateScheduleException; + + /** Deletes the given schedule */ + void deleteSchedule(ReadOnlySchedule schedule) throws ScheduleNotFoundException; + /** Adds given person to given group */ void addPersonToGroup(Index targetGroup, ReadOnlyPerson toAdd) throws GroupNotFoundException, PersonNotFoundException, DuplicatePersonException; @@ -69,6 +84,11 @@ void updatePerson(ReadOnlyPerson target, ReadOnlyPerson editedPerson) /** Returns an unmodifiable view of the filtered group list */ ObservableList getFilteredGroupList(); + /** Returns an unmodifiable view of the filtered schedule list */ + ObservableList getFilteredScheduleList(); + + void showUnfilteredPersonList(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. @@ -81,4 +101,9 @@ void updatePerson(ReadOnlyPerson target, ReadOnlyPerson editedPerson) */ void updateFilteredGroupList(Predicate predicate); + /** + * Updates the filter of the filtered schedule list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredScheduleList(Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index d03043d0fc93..5085692c32ee 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -24,6 +24,9 @@ import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.NoPersonsException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.schedule.ReadOnlySchedule; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; /** * Represents the in-memory model of the address book data. @@ -35,6 +38,7 @@ public class ModelManager extends ComponentManager implements Model { private final AddressBook addressBook; private final FilteredList filteredPersons; private final FilteredList filteredGroups; + private final FilteredList filteredSchedules; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -48,6 +52,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { this.addressBook = new AddressBook(addressBook); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); filteredGroups = new FilteredList<>(this.addressBook.getGroupList()); + filteredSchedules = new FilteredList<>(this.addressBook.getScheduleList()); } public ModelManager() { @@ -76,6 +81,15 @@ public synchronized void deletePerson(ReadOnlyPerson target) throws PersonNotFou indicateAddressBookChanged(); } + @Override + public synchronized void deletePersons(ReadOnlyPerson[] targets) throws PersonNotFoundException { + for (ReadOnlyPerson target : targets) { + addressBook.removePerson(target); + } + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + @Override public synchronized void addPerson(ReadOnlyPerson person) throws DuplicatePersonException { addressBook.addPerson(person); @@ -110,10 +124,23 @@ public void deleteGroup(ReadOnlyGroup target) throws GroupNotFoundException { indicateAddressBookChanged(); } + @Override + public void addSchedule(ReadOnlySchedule schedule) throws DuplicateScheduleException { + addressBook.addSchedule(schedule); + updateFilteredScheduleList(PREDICATE_SHOW_ALL_SCHEDULES); + } + @Override public void addPersonToGroup(Index targetGroup, ReadOnlyPerson toAdd) throws GroupNotFoundException, PersonNotFoundException, DuplicatePersonException { addressBook.addPersonToGroup(targetGroup, toAdd); + + indicateAddressBookChanged(); + } + + @Override + public void deleteSchedule(ReadOnlySchedule target) throws ScheduleNotFoundException { + addressBook.removeSchedule(target); indicateAddressBookChanged(); } @@ -145,6 +172,16 @@ public ObservableList getFilteredGroupList() { return FXCollections.unmodifiableObservableList(filteredGroups); } + @Override + public ObservableList getFilteredScheduleList() { + return FXCollections.unmodifiableObservableList(filteredSchedules); + } + + @Override + public void showUnfilteredPersonList() { + filteredPersons.setPredicate(PREDICATE_SHOW_ALL_PERSONS); + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); @@ -173,6 +210,11 @@ private void handleGroupPanelSelectionChangedEvent(GroupPanelSelectionChangedEve updateFilteredPersonList(getGroupMembersPredicate(personList)); } + @Override + public void updateFilteredScheduleList(Predicate predicate) { + requireNonNull(predicate); + filteredSchedules.setPredicate(predicate); + } @Override public boolean equals(Object obj) { // short circuit if same object diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1bb6f55b8923..30e2ab51e200 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -3,6 +3,7 @@ import javafx.collections.ObservableList; import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; +import seedu.address.model.schedule.ReadOnlySchedule; import seedu.address.model.tag.Tag; /** @@ -28,4 +29,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getGroupList(); + /** + * Returns an unmodifiable view of the schedules list. + * This list will not contain any duplicate schedules. + */ + ObservableList getScheduleList(); + } diff --git a/src/main/java/seedu/address/model/schedule/ReadOnlySchedule.java b/src/main/java/seedu/address/model/schedule/ReadOnlySchedule.java new file mode 100644 index 000000000000..c3d4a1f695a4 --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/ReadOnlySchedule.java @@ -0,0 +1,37 @@ +package seedu.address.model.schedule; + +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; + +/** + * A read-only immutable interface for a Group in the addressbook. + * Implementations should guarantee: details are present and not null, field values are validated. + */ +public interface ReadOnlySchedule { + + ObjectProperty nameProperty(); + ScheduleName getName(); + ObservableList getSchedules(); + + /** + * Returns true if both have the same state. (interfaces cannot override .equals) + */ + default boolean isSameStateAs(ReadOnlySchedule other) { + return other == this // short circuit if same object + || (other != null // this is first to avoid NPE below + && other.getName().equals(this.getName()) // state checks here onwards + && other.getSchedules().equals(this.getSchedules())); + } + + /** + * Formats the Schedule as text, showing schedule name. + */ + default String getAsText() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Schedule Name: ") + .append(getName()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/schedule/Schedule.java b/src/main/java/seedu/address/model/schedule/Schedule.java new file mode 100644 index 000000000000..1fe75ce9b1c4 --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/Schedule.java @@ -0,0 +1,92 @@ +package seedu.address.model.schedule; + +import static java.util.Objects.requireNonNull; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; + +/** + * Represents a Schedule in an address book. + * Guarantees: details are present and not null, field values are validated. + */ +public class Schedule implements ReadOnlySchedule { + + private ObjectProperty scheduleName; + /** + * A Schedule will have an empty schedules list by default + */ + private final UniqueScheduleList schedules = new UniqueScheduleList(); + + /** + * Every field must be present and not null. + */ + public Schedule(ScheduleName name) { + requireNonNull(name); + this.scheduleName = new SimpleObjectProperty<>(name); + } + + /** + * Every field must be present and not null. + */ + public Schedule(String name) throws IllegalValueException { + requireNonNull(name); + this.scheduleName = new SimpleObjectProperty<>(new ScheduleName(name)); + } + /** + * Creates a copy of the given ReadOnlySchedule. + */ + public Schedule(ReadOnlySchedule source) { + this(source.getName()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Schedule // instanceof handles nulls + && this.scheduleName.toString().equals(((Schedule) other).scheduleName.toString())); // state check + } + + @Override + public int hashCode() { + return scheduleName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return getAsText(); + } + + public void addMember(ReadOnlySchedule schedule) throws DuplicateScheduleException { + this.schedules.add(schedule); + } + + public void deleteMember(ReadOnlySchedule schedule) throws ScheduleNotFoundException { + this.schedules.remove(schedule); + } + + @Override + public ObjectProperty nameProperty() { + return scheduleName; + } + + @Override + public ScheduleName getName() { + return scheduleName.get(); + } + + public void setScheduleName(ScheduleName name) { + this.scheduleName.set(requireNonNull(name)); + } + + @Override + public ObservableList getSchedules() { + return schedules.asObservableList(); + } + +} diff --git a/src/main/java/seedu/address/model/schedule/ScheduleName.java b/src/main/java/seedu/address/model/schedule/ScheduleName.java new file mode 100644 index 000000000000..0471891fff0c --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/ScheduleName.java @@ -0,0 +1,63 @@ +package seedu.address.model.schedule; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.exceptions.IllegalValueException; + +/** + * Represents a Schedule's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class ScheduleName { + + public static final String MESSAGE_SCHEDULE_CONSTRAINTS = "Schedule names should contain only " + + "alphanumeric characters, spaces, underscores and dashes"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String SCHEDULE_VALIDATION_REGEX = "^[a-zA-Z0-9]([\\w -]*[a-zA-Z0-9])?$"; + + public final String fullName; + + /** + * Validates given name. + * + * @throws IllegalValueException if given name string is invalid. + */ + public ScheduleName(String name) throws IllegalValueException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!isValidName(trimmedName)) { + throw new IllegalValueException(MESSAGE_SCHEDULE_CONSTRAINTS); + } + this.fullName = trimmedName; + } + + /** + * Returns true if a given string is a valid schedule name. + */ + public static boolean isValidName(String test) { + return test.matches(SCHEDULE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ScheduleName // instanceof handles nulls + && this.fullName.equals(((ScheduleName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/schedule/UniqueScheduleList.java b/src/main/java/seedu/address/model/schedule/UniqueScheduleList.java new file mode 100644 index 000000000000..c439150da6f9 --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/UniqueScheduleList.java @@ -0,0 +1,165 @@ +package seedu.address.model.schedule; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; + + +/** + * A list of schedules that enforces no nulls and uniqueness between its elements. + * + * Supports minimal set of list operations for the app's features. + * + * @see Schedule#equals(Object) + */ + + +public class UniqueScheduleList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + // used by asObservableList() + private final ObservableList mappedList = EasyBind.map(internalList, (schedule) -> schedule); + + /** + * Creates a UniqueScheduleList using given Schedules. + * Enforces no nulls. + */ + public UniqueScheduleList(Set schedules) { + requireAllNonNull(schedules); + internalList.addAll(schedules); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Constructs empty ScheduleList. + */ + public UniqueScheduleList() {} + + /** + * Returns all schedules in this list as a Set. + * This set is mutable and change-insulated against the internal list. + */ + + /** + * Returns true if the list contains an equivalent Schedule as the given argument. + */ + public boolean contains(ReadOnlySchedule toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Returns a set representation of the schedule. + */ + public Set toSet() { + assert CollectionUtil.elementsAreUnique(internalList); + return new HashSet<>(internalList); + } + + /** + * Ensures every schedule in the argument list exists in this object. + */ + public void mergeFrom(UniqueScheduleList from) { + final Set alreadyInside = this.toSet(); + from.internalList.stream() + .filter(schedule -> !alreadyInside.contains(schedule)) + .forEach(internalList::add); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Adds a Schedule to the list. + * + * @throws seedu.address.model.schedule.exceptions.DuplicateScheduleException + * if the Schedule to add is a duplicate of an existing Schedule in the list. + */ + public void add(ReadOnlySchedule toAdd) throws DuplicateScheduleException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateScheduleException(); + } + internalList.add(new Schedule(toAdd)); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Removes the equivalent schedule from the list. + * + * @throws ScheduleNotFoundException if no such schedule could be found in the list. + */ + public boolean remove(ReadOnlySchedule toRemove) throws ScheduleNotFoundException { + requireNonNull(toRemove); + final boolean scheduleFoundAndDeleted = internalList.remove(toRemove); + if (!scheduleFoundAndDeleted) { + throw new ScheduleNotFoundException(); + } + return scheduleFoundAndDeleted; + } + + @Override + public Iterator iterator() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.iterator(); + } + + public void setSchedules(UniqueScheduleList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setSchedules(List schedules) throws DuplicateScheduleException { + final UniqueScheduleList replacement = new UniqueScheduleList(); + for (final ReadOnlySchedule schedule: schedules) { + replacement.add(new Schedule(schedule)); + } + setSchedules(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(mappedList); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof seedu.address.model.schedule.UniqueScheduleList // instanceof handles nulls + && this.internalList.equals(((seedu.address.model.schedule.UniqueScheduleList) other).internalList)); + } + + /** + * Returns true if the element in this list is equal to the elements in {@code other}. + * The elements do not have to be in the same order. + */ + public boolean equalsOrderInsensitive(seedu.address.model.schedule.UniqueScheduleList other) { + assert CollectionUtil.elementsAreUnique(internalList); + assert CollectionUtil.elementsAreUnique(other.internalList); + return this == other || new HashSet<>(this.internalList).equals(new HashSet<>(other.internalList)); + } + + @Override + public int hashCode() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.hashCode(); + } + +} + diff --git a/src/main/java/seedu/address/model/schedule/exceptions/DuplicateScheduleException.java b/src/main/java/seedu/address/model/schedule/exceptions/DuplicateScheduleException.java new file mode 100644 index 000000000000..a2e91a98c637 --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/exceptions/DuplicateScheduleException.java @@ -0,0 +1,12 @@ +package seedu.address.model.schedule.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +/** + * Signals that the operation will result in duplicate Schedule objects. + */ +public class DuplicateScheduleException extends DuplicateDataException { + public DuplicateScheduleException() { + super("Operation would result in duplicate schedules"); + } +} diff --git a/src/main/java/seedu/address/model/schedule/exceptions/NoSchedulesException.java b/src/main/java/seedu/address/model/schedule/exceptions/NoSchedulesException.java new file mode 100644 index 000000000000..5559cf5a1391 --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/exceptions/NoSchedulesException.java @@ -0,0 +1,6 @@ +package seedu.address.model.schedule.exceptions; + +/** + * Signals that the operation is unable to sort due to an empty list. + */ +public class NoSchedulesException extends Exception {} diff --git a/src/main/java/seedu/address/model/schedule/exceptions/ScheduleNotFoundException.java b/src/main/java/seedu/address/model/schedule/exceptions/ScheduleNotFoundException.java new file mode 100644 index 000000000000..726bb0596e5a --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/exceptions/ScheduleNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.schedule.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class ScheduleNotFoundException extends Exception {} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedSchedule.java b/src/main/java/seedu/address/storage/XmlAdaptedSchedule.java new file mode 100644 index 000000000000..7128cbbf8bfb --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedSchedule.java @@ -0,0 +1,51 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.schedule.ReadOnlySchedule; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleName; + + +/** + * JAXB-friendly adapted version of the Schedule. + */ +public class XmlAdaptedSchedule { + + @XmlElement(required = true) + private String scheduleName; + @XmlElement + private List schedules = new ArrayList<>(); + /** + * Constructs an XmlAdapteddSchedule. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedSchedule() {} + + /** + * Converts a given Schedule into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedSchedule(ReadOnlySchedule source) { + scheduleName = source.getName().toString(); + for (ReadOnlySchedule schedule: source.getSchedules()) { + schedules.add(new XmlAdaptedSchedule(schedule)); + } + + } + + /** + * Converts this jaxb-friendly adapted group object into the model's Group object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Schedule toModelType() throws IllegalValueException { + final ScheduleName scheduleName = new ScheduleName(this.scheduleName); + return new Schedule(scheduleName); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index 4ad67ef10c4e..9f31b231a0c7 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -13,6 +13,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; +import seedu.address.model.schedule.ReadOnlySchedule; import seedu.address.model.tag.Tag; /** @@ -27,6 +28,8 @@ public class XmlSerializableAddressBook implements ReadOnlyAddressBook { private List tags; @XmlElement private List groups; + @XmlElement + private List schedules; /** * Creates an empty XmlSerializableAddressBook. @@ -36,6 +39,7 @@ public XmlSerializableAddressBook() { persons = new ArrayList<>(); tags = new ArrayList<>(); groups = new ArrayList<>(); + schedules = new ArrayList<>(); } /** @@ -46,6 +50,7 @@ public XmlSerializableAddressBook(ReadOnlyAddressBook src) { persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); groups.addAll(src.getGroupList().stream().map(XmlAdaptedGroup::new).collect(Collectors.toList())); + schedules.addAll(src.getScheduleList().stream().map(XmlAdaptedSchedule::new).collect(Collectors.toList())); } @Override @@ -90,4 +95,17 @@ public ObservableList getGroupList() { return FXCollections.unmodifiableObservableList(groups); } + @Override + public ObservableList getScheduleList() { + final ObservableList schedules = this.schedules.stream().map(s -> { + try { + return s.toModelType(); + } catch (IllegalValueException e) { + e.printStackTrace(); + //TODO: better error handling + return null; + } + }).collect(Collectors.toCollection(FXCollections::observableArrayList)); + return FXCollections.unmodifiableObservableList(schedules); + } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index acf2d720322a..18c07768bde1 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -19,6 +19,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.paint.ImagePattern; @@ -46,6 +47,9 @@ public class BrowserPanel extends UiPart { @FXML private WebView browser; + @FXML + private Circle contactImageCircle; + @FXML private BorderPane socialIcon1Placeholder; @@ -64,6 +68,11 @@ public class BrowserPanel extends UiPart { @FXML private VBox contactDetailsVBox; + //This has a getter method as MainWindow.java needs to + // access this node to populate it with Logic.getFilteredScheduleList(). + @FXML + private StackPane schedulePlaceholder; + private ParallelTransition pt; public BrowserPanel() { @@ -72,28 +81,33 @@ public BrowserPanel() { // To prevent triggering events for typing inside the loaded Web page. getRoot().setOnKeyPressed(Event::consume); loadDefaultPage(); - - setupContactDetailsBox(); - + //Setup needed JFX nodes which will be updated upon selecting persons + setupContactImageCircle(); + setupContactDetailsVBox(); + setupScheduleListViewPlaceholder(); registerAsAnEventHandler(this); } private void setContactImage() { - Circle cir = new Circle(250, 250, 90); - cir.setStroke(Color.valueOf("#3fc380")); - cir.setStrokeWidth(5); - cir.setStrokeDashOffset(20); Image img = new Image("images/maleIcon.png"); - cir.setFill(new ImagePattern(img)); - cir.radiusProperty().bind(Bindings.min( + contactImageCircle.setVisible(true); + contactImageCircle.setFill(new ImagePattern(img)); + easeIn(contactImageCircle); + } + + private void setupContactImageCircle() { + contactImageCircle = new Circle(250, 250, 90); + contactImageCircle.setStroke(Color.valueOf("#3fc380")); + contactImageCircle.setStrokeWidth(5); + contactImageCircle.radiusProperty().bind(Bindings.min( contactImagePlaceholder.widthProperty().divide(3), contactImagePlaceholder.heightProperty().divide(3)) ); - contactImagePlaceholder.setCenter(cir); - easeIn(cir); + contactImagePlaceholder.setCenter(contactImageCircle); + contactImageCircle.setVisible(false); } - private void setupContactDetailsBox() { + private void setupContactDetailsVBox() { contactDetailsVBox.setSpacing(0); contactDetailsVBox.getChildren().addAll( new Label(""), @@ -122,7 +136,6 @@ private void setIcons() { Circle cir = new Circle(250, 250, 30); cir.setStroke(Color.valueOf("#3fc380")); cir.setStrokeWidth(5); - cir.setStrokeDashOffset(20); cir.radiusProperty().bind(Bindings.min( socialIconPlaceholders[i].widthProperty().divide(3), socialIconPlaceholders[i].heightProperty().divide(3)) @@ -133,6 +146,10 @@ private void setIcons() { } } + private void setupScheduleListViewPlaceholder() { + schedulePlaceholder.setVisible(false); + } + private void loadPersonPage(ReadOnlyPerson person) { loadPage(GOOGLE_SEARCH_URL_PREFIX + person.getName().fullName.replaceAll(" ", "+") + GOOGLE_SEARCH_URL_SUFFIX); @@ -164,10 +181,14 @@ private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedE setContactImage(); setContactDetails(event.getNewSelection().person); setIcons(); + setSchedule(); } private void setContactDetails(ReadOnlyPerson person) { //Set up name label separately as it has no icons + contactDetailsVBox.setSpacing(0); + contactDetailsVBox.getChildren().addAll(); + Label name = (Label) contactDetailsVBox.getChildren().get(0); name.setText("" + person.getName()); name.setStyle("-fx-font-size: 60;"); @@ -197,6 +218,12 @@ private void setContactDetails(ReadOnlyPerson person) { } } + private void setSchedule() { + schedulePlaceholder.setVisible(true); + //scheduleListView.setStyle("-fx-alignment: center-left; -fx-padding: 0 0 0 10;"); + easeIn(schedulePlaceholder); + } + /** * Animates any node passed into this method with an ease-in */ @@ -214,4 +241,8 @@ private void easeIn(Node node) { pt.getChildren().addAll(ft, tt); pt.play(); } + + public StackPane getSchedulePlaceholder() { + return schedulePlaceholder; + } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 910a6797658a..924fee57e484 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -132,18 +132,20 @@ void fillInnerParts() { browserPanel = new BrowserPanel(); browserPlaceholder.getChildren().add(browserPanel.getRoot()); + ScheduleListPanel scheduleListPanel = new ScheduleListPanel(logic.getFilteredScheduleList()); + browserPanel.getSchedulePlaceholder().getChildren().add(scheduleListPanel.getRoot()); + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - GroupListPanel groupListPanel; - groupListPanel = new GroupListPanel(logic.getFilteredGroupList()); //logic needs to get groupList instead + GroupListPanel groupListPanel = new GroupListPanel(logic.getFilteredGroupList()); groupListPanelPlaceholder.getChildren().add(groupListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath(), - logic.getFilteredPersonList().size()); + logic.getFilteredPersonList().size()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index d3a5fbae42bb..926a63dd3a03 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -13,7 +13,7 @@ import javafx.scene.control.ListView; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.JumpToPersonListRequestEvent; import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; import seedu.address.model.person.ReadOnlyPerson; @@ -62,7 +62,7 @@ private void scrollTo(int index) { } @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + private void handleJumpToPersonListRequestEvent(JumpToPersonListRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); scrollTo(event.targetIndex); } diff --git a/src/main/java/seedu/address/ui/ScheduleCard.java b/src/main/java/seedu/address/ui/ScheduleCard.java new file mode 100644 index 000000000000..8ce1b9d1e4b9 --- /dev/null +++ b/src/main/java/seedu/address/ui/ScheduleCard.java @@ -0,0 +1,64 @@ +package seedu.address.ui; + +import javafx.beans.binding.Bindings; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.schedule.ReadOnlySchedule; + + +/** + * An UI component that displays information of a {@code Schedule}. + */ +public class ScheduleCard extends UiPart { + + private static final String FXML = "ScheduleListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final ReadOnlySchedule schedule; + + @FXML + private Label scheduleName; + @FXML + private Label scheduleId; + + public ScheduleCard(ReadOnlySchedule schedule, int displayedIndex) { + super(FXML); + this.schedule = schedule; + scheduleId.setText(displayedIndex + ". "); + bindListeners(schedule); + } + + /** + * Binds the individual UI elements to observe their respective {@code Schedule} properties + * so that they will be notified of any changes. + */ + private void bindListeners(ReadOnlySchedule schedule) { + scheduleName.textProperty().bind(Bindings.convert(schedule.nameProperty())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ScheduleCard)) { + return false; + } + + // state check + ScheduleCard card = (ScheduleCard) other; + return scheduleId.getText().equals(card.scheduleId.getText()) + && schedule.equals(card.schedule); + } +} diff --git a/src/main/java/seedu/address/ui/ScheduleListPanel.java b/src/main/java/seedu/address/ui/ScheduleListPanel.java new file mode 100644 index 000000000000..4431f37714e9 --- /dev/null +++ b/src/main/java/seedu/address/ui/ScheduleListPanel.java @@ -0,0 +1,95 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +//import com.google.common.eventbus.Subscribe; + +//import javafx.application.Platform; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +//import seedu.address.commons.events.ui.JumpToScheduleListRequestEvent; +import seedu.address.commons.events.ui.SchedulePanelSelectionChangedEvent; +import seedu.address.model.schedule.ReadOnlySchedule; + + + +/** + * Panel containing the list of schedules. + */ +public class ScheduleListPanel extends UiPart { + private static final String FXML = "ScheduleListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ScheduleListPanel.class); + + @FXML + private ListView scheduleListView; + + public ScheduleListPanel(ObservableList scheduleList) { + super(FXML); + setConnections(scheduleList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList scheduleList) { + ObservableList mappedList = EasyBind.map( + scheduleList, (schedule) -> new ScheduleCard(schedule, scheduleList.indexOf(schedule) + 1)); + scheduleListView.setItems(mappedList); + scheduleListView.setCellFactory(listView -> new scheduleListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + scheduleListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in schedule list panel changed to : '" + newValue + "'"); + raise(new SchedulePanelSelectionChangedEvent(newValue)); + } + }); + } + + + + /** + * Scrolls to the {@code ScheduleCard} at the {@code index} and selects it. + */ + /* Both methods should be disabled until tests for this schedule list panel is done. + private void scrollTo(int index) { + Platform.runLater(() -> { + scheduleListView.scrollTo(index); + scheduleListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToScheduleListRequestEvent(JumpToScheduleListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + */ + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ScheduleCard}. + */ + class scheduleListViewCell extends ListCell { + + @Override + protected void updateItem(ScheduleCard schedule, boolean empty) { + super.updateItem(schedule, empty); + + if (empty || schedule == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(schedule.getRoot()); + } + } + } + +} diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml index 199aeb12c059..3fff4bd92c8b 100644 --- a/src/main/resources/view/BrowserPanel.fxml +++ b/src/main/resources/view/BrowserPanel.fxml @@ -1,5 +1,7 @@ + + @@ -7,7 +9,7 @@ - + @@ -38,6 +40,7 @@ + diff --git a/src/main/resources/view/ScheduleListCard.fxml b/src/main/resources/view/ScheduleListCard.fxml new file mode 100644 index 000000000000..282e6280eba8 --- /dev/null +++ b/src/main/resources/view/ScheduleListCard.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ScheduleListPanel.fxml b/src/main/resources/view/ScheduleListPanel.fxml new file mode 100644 index 000000000000..f37ecd4eec81 --- /dev/null +++ b/src/main/resources/view/ScheduleListPanel.fxml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/test/java/guitests/AddressBookGuiTest.java b/src/test/java/guitests/AddressBookGuiTest.java index 579ece9d1422..a3f2c502aa30 100644 --- a/src/test/java/guitests/AddressBookGuiTest.java +++ b/src/test/java/guitests/AddressBookGuiTest.java @@ -11,6 +11,7 @@ import guitests.guihandles.BrowserPanelHandle; import guitests.guihandles.CommandBoxHandle; +import guitests.guihandles.GroupListPanelHandle; import guitests.guihandles.MainMenuHandle; import guitests.guihandles.MainWindowHandle; import guitests.guihandles.PersonListPanelHandle; @@ -75,6 +76,10 @@ protected PersonListPanelHandle getPersonListPanel() { return mainWindowHandle.getPersonListPanel(); } + protected GroupListPanelHandle getGroupListPanel() { + return mainWindowHandle.getGroupListPanel(); + } + protected MainMenuHandle getMainMenu() { return mainWindowHandle.getMainMenu(); } diff --git a/src/test/java/guitests/guihandles/GroupCardHandle.java b/src/test/java/guitests/guihandles/GroupCardHandle.java new file mode 100644 index 000000000000..6bc397f792b7 --- /dev/null +++ b/src/test/java/guitests/guihandles/GroupCardHandle.java @@ -0,0 +1,30 @@ +package guitests.guihandles; + +import javafx.scene.Node; +import javafx.scene.control.Label; + +/** + * Provides a handle to a person card in the person list panel. + */ +public class GroupCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#groupId"; + private static final String NAME_FIELD_ID = "#groupName"; + + private final Label idLabel; + private final Label nameLabel; + + public GroupCardHandle(Node cardNode) { + super(cardNode); + this.idLabel = getChildNode(ID_FIELD_ID); + this.nameLabel = getChildNode(NAME_FIELD_ID); + } + + public String getId() { + return idLabel.getText(); + } + + public String getName() { + return nameLabel.getText(); + } + +} diff --git a/src/test/java/guitests/guihandles/GroupListPanelHandle.java b/src/test/java/guitests/guihandles/GroupListPanelHandle.java new file mode 100644 index 000000000000..3bf2bad3c4a6 --- /dev/null +++ b/src/test/java/guitests/guihandles/GroupListPanelHandle.java @@ -0,0 +1,133 @@ +package guitests.guihandles; + +import java.util.List; +import java.util.Optional; + +import javafx.scene.control.ListView; +import seedu.address.model.group.ReadOnlyGroup; +import seedu.address.ui.GroupCard; + +/** + * Provides a handle for {@code GroupListPanel} containing the list of {@code GroupCard}. + */ +public class GroupListPanelHandle extends NodeHandle> { + public static final String GROUP_LIST_VIEW_ID = "#groupListView"; + + private Optional lastRememberedSelectedGroupCard; + + public GroupListPanelHandle(ListView groupListPanelNode) { + super(groupListPanelNode); + } + + /** + * Returns a handle to the selected {@code GroupCardHandle}. + * A maximum of 1 item can be selected at any time. + * @throws AssertionError if no card is selected, or more than 1 card is selected. + */ + public GroupCardHandle getHandleToSelectedCard() { + List groupList = getRootNode().getSelectionModel().getSelectedItems(); + if (groupList.size() != 1) { + throw new AssertionError("Group list size expected 1."); + } + + return new GroupCardHandle(groupList.get(0).getRoot()); + } + + /** + * Returns the index of the selected card. + */ + public int getSelectedCardIndex() { + return getRootNode().getSelectionModel().getSelectedIndex(); + } + + /** + * Returns true if a card is currently selected. + */ + public boolean isAnyCardSelected() { + List selectedCardsList = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedCardsList.size() > 1) { + throw new AssertionError("Card list size expected 0 or 1."); + } + + return !selectedCardsList.isEmpty(); + } + + /** + * Navigates the listview to display and select the group. + */ + public void navigateToCard(ReadOnlyGroup group) { + List cards = getRootNode().getItems(); + Optional matchingCard = cards.stream().filter(card -> card.group.equals(group)).findFirst(); + + if (!matchingCard.isPresent()) { + throw new IllegalArgumentException("Group does not exist."); + } + + guiRobot.interact(() -> { + getRootNode().scrollTo(matchingCard.get()); + getRootNode().getSelectionModel().select(matchingCard.get()); + }); + guiRobot.pauseForHuman(); + } + + /** + * Returns the group card handle of a group associated with the {@code index} in the list. + */ + public GroupCardHandle getGroupCardHandle(int index) { + return getGroupCardHandle(getRootNode().getItems().get(index).group); + } + + /** + * Returns the {@code GroupCardHandle} of the specified {@code group} in the list. + */ + public GroupCardHandle getGroupCardHandle(ReadOnlyGroup group) { + Optional handle = getRootNode().getItems().stream() + .filter(card -> card.group.equals(group)) + .map(card -> new GroupCardHandle(card.getRoot())) + .findFirst(); + return handle.orElseThrow(() -> new IllegalArgumentException("Group does not exist.")); + } + + /** + * Selects the {@code GroupCard} at {@code index} in the list. + */ + public void select(int index) { + getRootNode().getSelectionModel().select(index); + } + + /** + * Remembers the selected {@code GroupCard} in the list. + */ + public void rememberSelectedGroupCard() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + lastRememberedSelectedGroupCard = Optional.empty(); + } else { + lastRememberedSelectedGroupCard = Optional.of(selectedItems.get(0)); + } + } + + /** + * Returns true if the selected {@code GroupCard} is different from the value remembered by the most recent + * {@code rememberSelectedGroupCard()} call. + */ + public boolean isSelectedGroupCardChanged() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + return lastRememberedSelectedGroupCard.isPresent(); + } else { + return !lastRememberedSelectedGroupCard.isPresent() + || !lastRememberedSelectedGroupCard.get().equals(selectedItems.get(0)); + } + } + + /** + * Returns the size of the list. + */ + public int getListSize() { + return getRootNode().getItems().size(); + } +} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..4777389650dd 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -8,6 +8,7 @@ public class MainWindowHandle extends StageHandle { private final PersonListPanelHandle personListPanel; + private final GroupListPanelHandle groupListPanel; private final ResultDisplayHandle resultDisplay; private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; @@ -18,6 +19,7 @@ public MainWindowHandle(Stage stage) { super(stage); personListPanel = new PersonListPanelHandle(getChildNode(PersonListPanelHandle.PERSON_LIST_VIEW_ID)); + groupListPanel = new GroupListPanelHandle(getChildNode(GroupListPanelHandle.GROUP_LIST_VIEW_ID)); resultDisplay = new ResultDisplayHandle(getChildNode(ResultDisplayHandle.RESULT_DISPLAY_ID)); commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); @@ -29,6 +31,10 @@ public PersonListPanelHandle getPersonListPanel() { return personListPanel; } + public GroupListPanelHandle getGroupListPanel() { + return groupListPanel; + } + public ResultDisplayHandle getResultDisplay() { return resultDisplay; } diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 12e3fc25c61e..56da3e2944d5 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -30,6 +30,9 @@ import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.NoPersonsException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.schedule.ReadOnlySchedule; +import seedu.address.model.schedule.exceptions.DuplicateScheduleException; +import seedu.address.model.schedule.exceptions.ScheduleNotFoundException; import seedu.address.testutil.PersonBuilder; public class AddCommandTest { @@ -123,6 +126,16 @@ public void deleteGroup(ReadOnlyGroup group) throws GroupNotFoundException { fail("This method should not be called."); } + @Override + public void addSchedule(ReadOnlySchedule schedule) throws DuplicateScheduleException { + fail("This method should not be called."); + } + + @Override + public void deleteSchedule(ReadOnlySchedule schedule) throws ScheduleNotFoundException { + fail("This method should not be called."); + } + @Override public void addPersonToGroup(Index targetGroup, ReadOnlyPerson toAdd) throws GroupNotFoundException, PersonNotFoundException, DuplicatePersonException { @@ -151,6 +164,11 @@ public void deletePerson(ReadOnlyPerson target) throws PersonNotFoundException { fail("This method should not be called."); } + @Override + public void deletePersons(ReadOnlyPerson[] targets) throws PersonNotFoundException { + fail("This method should not be called."); + } + @Override public void updatePerson(ReadOnlyPerson target, ReadOnlyPerson editedPerson) throws DuplicatePersonException { @@ -168,6 +186,15 @@ public ObservableList getFilteredGroupList() { return null; } + @Override + public ObservableList getFilteredScheduleList() { + return null; + } + + public void showUnfilteredPersonList() { + fail("This method should not be called."); + } + @Override public void updateFilteredPersonList(Predicate predicate) { fail("This method should not be called."); @@ -177,6 +204,11 @@ public void updateFilteredPersonList(Predicate predicate) { public void updateFilteredGroupList(Predicate predicate) { fail("This method should not be called."); } + + @Override + public void updateFilteredScheduleList(Predicate predicate) { + fail("This method should not be called."); + } } /** diff --git a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java index 098cff934f9d..a336edcd63f0 100644 --- a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java @@ -16,7 +16,7 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.JumpToPersonListRequestEvent; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; @@ -96,8 +96,8 @@ public void equals() { } /** - * Executes a {@code SelectCommand} with the given {@code index}, and checks that {@code JumpToListRequestEvent} - * is raised with the correct index. + * Executes a {@code SelectCommand} with the given {@code index}, and checks that + * {@code JumpToPersonListRequestEvent} is raised with the correct index. */ private void assertExecutionSuccess(Index index) { SelectCommand selectCommand = prepareCommand(index); @@ -110,7 +110,8 @@ private void assertExecutionSuccess(Index index) { throw new IllegalArgumentException("Execution of command should not fail.", ce); } - JumpToListRequestEvent lastEvent = (JumpToListRequestEvent) eventsCollectorRule.eventsCollector.getMostRecent(); + JumpToPersonListRequestEvent lastEvent = + (JumpToPersonListRequestEvent) eventsCollectorRule.eventsCollector.getMostRecent(); assertEquals(index, Index.fromZeroBased(lastEvent.targetIndex)); } diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index 17d714595c8e..0a4ad31f6b88 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -19,6 +19,7 @@ import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.Person; import seedu.address.model.person.ReadOnlyPerson; +import seedu.address.model.schedule.ReadOnlySchedule; import seedu.address.model.tag.Tag; public class AddressBookTest { @@ -77,6 +78,7 @@ private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); private final ObservableList tags = FXCollections.observableArrayList(); private final ObservableList groups = FXCollections.observableArrayList(); + private final ObservableList schedules = FXCollections.observableArrayList(); AddressBookStub(Collection persons, Collection tags) { this.persons.setAll(persons); @@ -97,6 +99,11 @@ public ObservableList getTagList() { public ObservableList getGroupList() { return groups; } + + @Override + public ObservableList getScheduleList() { + return schedules; + } } } diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 59ce1b83693a..ffff1f627c29 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -28,6 +28,7 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException @Test public void equals() { + //Adding withGroup(ALICE_GROUP) makes assert fail, unsure why AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); AddressBook differentAddressBook = new AddressBook(); UserPrefs userPrefs = new UserPrefs(); diff --git a/src/test/java/seedu/address/model/UniqueGroupListTest.java b/src/test/java/seedu/address/model/UniqueGroupListTest.java new file mode 100644 index 000000000000..6515bf03c08d --- /dev/null +++ b/src/test/java/seedu/address/model/UniqueGroupListTest.java @@ -0,0 +1,19 @@ +package seedu.address.model; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.group.UniqueGroupList; + +public class UniqueGroupListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableListModifyListThrowsUnsupportedOperationException() { + UniqueGroupList uniqueGroupList = new UniqueGroupList(); + thrown.expect(UnsupportedOperationException.class); + uniqueGroupList.asObservableList().remove(0); + } +} diff --git a/src/test/java/seedu/address/model/group/GroupNameTest.java b/src/test/java/seedu/address/model/group/GroupNameTest.java new file mode 100644 index 000000000000..5ac6d47f839e --- /dev/null +++ b/src/test/java/seedu/address/model/group/GroupNameTest.java @@ -0,0 +1,25 @@ +package seedu.address.model.group; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class GroupNameTest { + + @Test + public void isValidName() { + // invalid name + assertFalse(GroupName.isValidName("")); // empty string + assertFalse(GroupName.isValidName(" ")); // spaces only + assertFalse(GroupName.isValidName("^")); // only non-alphanumeric characters + assertFalse(GroupName.isValidName("peter*")); // contains non-alphanumeric characters + + // valid name + assertTrue(GroupName.isValidName("peter jack")); // alphabets only + assertTrue(GroupName.isValidName("12345")); // numbers only + assertTrue(GroupName.isValidName("peter the 2nd")); // alphanumeric characters + assertTrue(GroupName.isValidName("Capital Tan")); // with capital letters + assertTrue(GroupName.isValidName("David Roger Jackson Ray Jr 2nd")); // long names + } +} diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index bf48d980a236..1793a9635966 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -51,7 +51,7 @@ public AddressBookBuilder withTag(String tagName) { } /** - * Parses {@code groupName} into a {@code Group} and adds it to the {@code AddressBook} that we are building. + * Adds a new {@code Group} to the {@code AddressBook} that we are building. */ public AddressBookBuilder withGroup(ReadOnlyGroup group) { try { diff --git a/src/test/java/seedu/address/testutil/GroupBuilder.java b/src/test/java/seedu/address/testutil/GroupBuilder.java new file mode 100644 index 000000000000..fed5b24c16b3 --- /dev/null +++ b/src/test/java/seedu/address/testutil/GroupBuilder.java @@ -0,0 +1,50 @@ +package seedu.address.testutil; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupName; +import seedu.address.model.group.ReadOnlyGroup; + + +/** + * A utility class to help with building Group objects. + */ +public class GroupBuilder { + + public static final String DEFAULT_NAME = "Alice Pauline Group"; + + private Group group; + + public GroupBuilder() { + try { + GroupName defaultName = new GroupName(DEFAULT_NAME); + this.group = new Group(defaultName); + } catch (IllegalValueException ive) { + throw new AssertionError("Default group's values are invalid."); + } + } + + /** + * Initializes the GroupBuilder with the data of {@code groupToCopy}. + */ + public GroupBuilder(ReadOnlyGroup groupToCopy) { + this.group = new Group(groupToCopy); + } + + /** + * Sets the {@code Name} of the {@code Group} that we are building. + */ + public GroupBuilder withName(String name) { + try { + this.group.setGroupName(new GroupName(name)); + } catch (IllegalValueException ive) { + throw new IllegalArgumentException("name is expected to be unique."); + } + return this; + } + + public Group build() { + return this.group; + } + +} diff --git a/src/test/java/seedu/address/testutil/GroupUtil.java b/src/test/java/seedu/address/testutil/GroupUtil.java new file mode 100644 index 000000000000..72f6007a997d --- /dev/null +++ b/src/test/java/seedu/address/testutil/GroupUtil.java @@ -0,0 +1,28 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.model.group.ReadOnlyGroup; + +/** + * A utility class for Group. + */ +public class GroupUtil { + + /** + * Returns an add command string for adding the {@code group}. + */ + public static String getAddCommand(ReadOnlyGroup group) { + return AddCommand.COMMAND_WORD + " " + getGroupDetails(group); + } + + /** + * Returns the part of command string for the given {@code group}'s details. + */ + public static String getGroupDetails(ReadOnlyGroup group) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_NAME + group.getName().fullName + " "); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalGroups.java b/src/test/java/seedu/address/testutil/TypicalGroups.java new file mode 100644 index 000000000000..974ff43a495a --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalGroups.java @@ -0,0 +1,62 @@ +package seedu.address.testutil; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.AddressBook; +import seedu.address.model.group.ReadOnlyGroup; +import seedu.address.model.group.exceptions.DuplicateGroupException; + +/** + * A utility class containing a list of {@code Person} objects to be used in tests. + */ +public class TypicalGroups { + + public static final ReadOnlyGroup ALICE_GROUP = new GroupBuilder().withName("Alice Pauline Group").build(); + public static final ReadOnlyGroup BENSON_GROUP = new GroupBuilder().withName("Benson Meier Group").build(); + public static final ReadOnlyGroup CARL_GROUP = new GroupBuilder().withName("Carl Kurz Group").build(); + public static final ReadOnlyGroup DANIEL_GROUP = new GroupBuilder().withName("Daniel Meier Group").build(); + public static final ReadOnlyGroup ELLE_GROUP = new GroupBuilder().withName("Elle Meyer Group").build(); + public static final ReadOnlyGroup FIONA_GROUP = new GroupBuilder().withName("Fiona Kunz Group").build(); + public static final ReadOnlyGroup GEORGE_GROUP = new GroupBuilder().withName("George Best Group").build(); + + // Manually added + public static final ReadOnlyGroup HOON_GROUP = new GroupBuilder().withName("Hoon Meier Group").build(); + public static final ReadOnlyGroup IDA_GROUP = new GroupBuilder().withName("Ida Mueller Group").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final ReadOnlyGroup AMY_GROUP = new GroupBuilder().withName(VALID_NAME_AMY).build(); + public static final ReadOnlyGroup BOB_GROUP = new GroupBuilder().withName(VALID_NAME_BOB).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier Group"; // A keyword that matches MEIER + + private TypicalGroups() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical groups. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (ReadOnlyGroup group : getTypicalGroups()) { + try { + ab.addGroup(group); + } catch (DuplicateGroupException e) { + assert false : "not possible"; + } + } + return ab; + } + + public static List getTypicalGroups() { + return new ArrayList<>(Arrays.asList( + ALICE_GROUP, BENSON_GROUP, CARL_GROUP, DANIEL_GROUP, ELLE_GROUP, FIONA_GROUP, GEORGE_GROUP)); + } + + //Methods for getEmptyAddressBook() and getSortedAddressBook() are removed unlike in TypicalPersons + //as there's no equivalent usage in SortCommandTest. + +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e6139376570..4661091eb0d2 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -9,4 +9,8 @@ public class TypicalIndexes { public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + + public static final Index INDEX_FIRST_GROUP = Index.fromOneBased(1); + public static final Index INDEX_SECOND_GROUP = Index.fromOneBased(2); + public static final Index INDEX_THIRD_GROUP = Index.fromOneBased(3); } diff --git a/src/test/java/seedu/address/ui/GroupCardTest.java b/src/test/java/seedu/address/ui/GroupCardTest.java new file mode 100644 index 000000000000..219d0fe61445 --- /dev/null +++ b/src/test/java/seedu/address/ui/GroupCardTest.java @@ -0,0 +1,80 @@ +package seedu.address.ui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.testutil.TypicalGroups.ALICE_GROUP; +import static seedu.address.ui.testutil.GuiTestAssert.assertCardDisplaysGroup; + +import org.junit.Test; + +import guitests.guihandles.GroupCardHandle; + +import seedu.address.model.group.Group; +import seedu.address.model.group.ReadOnlyGroup; +import seedu.address.testutil.GroupBuilder; + + +public class GroupCardTest extends GuiUnitTest { + + @Test + public void display() { + Group group = new GroupBuilder().build(); + GroupCard groupCard = new GroupCard(group, 1); + uiPartRule.setUiPart(groupCard); + assertCardDisplay(groupCard, group, 1); + + Group group2 = new GroupBuilder().build(); + groupCard = new GroupCard(group2, 2); + uiPartRule.setUiPart(groupCard); + assertCardDisplay(groupCard, group2, 2); + + // changes made to Group reflects on card + guiRobot.interact(() -> { + group2.setGroupName(ALICE_GROUP.getName()); + }); + assertCardDisplay(groupCard, group2, 2); + } + + @Test + public void equals() { + Group group = new GroupBuilder().build(); + GroupCard groupCard = new GroupCard(group, 0); + + // same group, same index -> returns true + GroupCard copy = new GroupCard(group, 0); + assertTrue(groupCard.equals(copy)); + + // same object -> returns true + assertTrue(groupCard.equals(groupCard)); + + // null -> returns false + assertFalse(groupCard == null); + + // different types -> returns false + assertFalse(groupCard.equals(0)); + + // different group, same index -> returns false + Group differentGroup = new GroupBuilder().withName("differentName").build(); + assertFalse(groupCard.equals(new GroupCard(differentGroup, 0))); + + // same group, different index -> returns false + assertFalse(groupCard.equals(new GroupCard(group, 1))); + } + + /** + * Asserts that {@code groupCard} displays the details of {@code expectedGroup} correctly and matches + * {@code expectedId}. + */ + private void assertCardDisplay(GroupCard groupCard, ReadOnlyGroup expectedGroup, int expectedId) { + guiRobot.pauseForHuman(); + + GroupCardHandle groupCardHandle = new GroupCardHandle(groupCard.getRoot()); + + // verify id is displayed correctly + assertEquals(Integer.toString(expectedId) + ". ", groupCardHandle.getId()); + + // verify group details are displayed correctly + assertCardDisplaysGroup(expectedGroup, groupCardHandle); + } +} diff --git a/src/test/java/seedu/address/ui/GroupListPanelTest.java b/src/test/java/seedu/address/ui/GroupListPanelTest.java new file mode 100644 index 000000000000..eff9111d90d7 --- /dev/null +++ b/src/test/java/seedu/address/ui/GroupListPanelTest.java @@ -0,0 +1,59 @@ +package seedu.address.ui; + +import static org.junit.Assert.assertEquals; +import static seedu.address.testutil.EventsUtil.postNow; +import static seedu.address.testutil.TypicalGroups.getTypicalGroups; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_GROUP; +import static seedu.address.ui.testutil.GuiTestAssert.assertCardDisplaysGroup; +import static seedu.address.ui.testutil.GuiTestAssert.assertCardEquals; + +import org.junit.Before; +import org.junit.Test; + +import guitests.guihandles.GroupCardHandle; +import guitests.guihandles.GroupListPanelHandle; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.events.ui.JumpToGroupListRequestEvent; +import seedu.address.model.group.ReadOnlyGroup; + +public class GroupListPanelTest extends GuiUnitTest { + private static final ObservableList TYPICAL_GROUPS = + FXCollections.observableList(getTypicalGroups()); + + private static final JumpToGroupListRequestEvent JUMP_TO_SECOND_EVENT = + new JumpToGroupListRequestEvent(INDEX_SECOND_GROUP); + + private GroupListPanelHandle groupListPanelHandle; + + @Before + public void setUp() { + GroupListPanel groupListPanel = new GroupListPanel(TYPICAL_GROUPS); + uiPartRule.setUiPart(groupListPanel); + + groupListPanelHandle = new GroupListPanelHandle(getChildNode(groupListPanel.getRoot(), + GroupListPanelHandle.GROUP_LIST_VIEW_ID)); + } + + @Test + public void display() { + for (int i = 0; i < TYPICAL_GROUPS.size(); i++) { + groupListPanelHandle.navigateToCard(TYPICAL_GROUPS.get(i)); + ReadOnlyGroup expectedGroup = TYPICAL_GROUPS.get(i); + GroupCardHandle actualCard = groupListPanelHandle.getGroupCardHandle(i); + + assertCardDisplaysGroup(expectedGroup, actualCard); + assertEquals(Integer.toString(i + 1) + ". ", actualCard.getId()); + } + } + + @Test + public void handleJumpToGroupListRequestEvent() { + postNow(JUMP_TO_SECOND_EVENT); + guiRobot.pauseForHuman(); + + GroupCardHandle expectedCard = groupListPanelHandle.getGroupCardHandle(INDEX_SECOND_GROUP.getZeroBased()); + GroupCardHandle selectedCard = groupListPanelHandle.getHandleToSelectedCard(); + assertCardEquals(expectedCard, selectedCard); + } +} diff --git a/src/test/java/seedu/address/ui/PersonListPanelTest.java b/src/test/java/seedu/address/ui/PersonListPanelTest.java index b774c2a3320c..9d552bff1519 100644 --- a/src/test/java/seedu/address/ui/PersonListPanelTest.java +++ b/src/test/java/seedu/address/ui/PersonListPanelTest.java @@ -14,14 +14,15 @@ import guitests.guihandles.PersonListPanelHandle; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.JumpToPersonListRequestEvent; import seedu.address.model.person.ReadOnlyPerson; public class PersonListPanelTest extends GuiUnitTest { private static final ObservableList TYPICAL_PERSONS = FXCollections.observableList(getTypicalPersons()); - private static final JumpToListRequestEvent JUMP_TO_SECOND_EVENT = new JumpToListRequestEvent(INDEX_SECOND_PERSON); + private static final JumpToPersonListRequestEvent JUMP_TO_SECOND_EVENT = + new JumpToPersonListRequestEvent(INDEX_SECOND_PERSON); private PersonListPanelHandle personListPanelHandle; @@ -47,7 +48,7 @@ public void display() { } @Test - public void handleJumpToListRequestEvent() { + public void handleJumpToPersonListRequestEvent() { postNow(JUMP_TO_SECOND_EVENT); guiRobot.pauseForHuman(); diff --git a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java index 0a7f7a3c4195..68ee2201279c 100644 --- a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java +++ b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java @@ -5,9 +5,11 @@ import java.util.List; import java.util.stream.Collectors; +import guitests.guihandles.GroupCardHandle; import guitests.guihandles.PersonCardHandle; import guitests.guihandles.PersonListPanelHandle; import guitests.guihandles.ResultDisplayHandle; +import seedu.address.model.group.ReadOnlyGroup; import seedu.address.model.person.ReadOnlyPerson; /** @@ -23,6 +25,14 @@ public static void assertCardEquals(PersonCardHandle expectedCard, PersonCardHan assertEquals(expectedCard.getTags(), actualCard.getTags()); } + /** + * Asserts that {@code actualCard} displays the same values as {@code expectedCard}, for groups. + */ + public static void assertCardEquals(GroupCardHandle expectedCard, GroupCardHandle actualCard) { + assertEquals(expectedCard.getId(), actualCard.getId()); + assertEquals(expectedCard.getName(), actualCard.getName()); + } + /** * Asserts that {@code actualCard} displays the details of {@code expectedPerson}. */ @@ -32,6 +42,13 @@ public static void assertCardDisplaysPerson(ReadOnlyPerson expectedPerson, Perso actualCard.getTags()); } + /** + * Asserts that {@code actualCard} displays the details of {@code expectedGroup}. + */ + public static void assertCardDisplaysGroup(ReadOnlyGroup expectedPerson, GroupCardHandle actualCard) { + assertEquals(expectedPerson.getName().fullName, actualCard.getName()); + } + /** * Asserts that the list in {@code personListPanelHandle} displays the details of {@code persons} correctly and * in the correct order.