Project: Rolodex

Rolodex is a desktop contact management application for users who prefer working without a mouse to manage their contacts more efficiently. If you are a businessman with a list of clients to remember, a teacher who wants to organize their student information, or anyone with a need for contact management, Rolodex provides you with a way to organize your important contacts in a fast and productive manner.

The user interacts with the application using a CLI (Command Line Interface), and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.

Code contributed: [Functional code] [Test code]

Enhancement Added: Keyboard Shortcuts

Justification

Having keyboard shortcuts allows the user to navigate around the application with ease and speed. This implementation should enable you to access all parts of Rolodex without using the mouse. The table below shows the keyboard shortcuts that have been added, with the exception of shortcuts with the behavior Exit the application and View help window which came with the original state of the application.

External Behavior


Quick Command Shortcuts (Since v1.2)
Shortcut Purpose

Ctrl+Shift+C

Clear contact list

Ctrl+H

View history

Ctrl+L

List all contacts

Ctrl+Z

Undo last command

Ctrl+Y

Redo last command

F1

View help window

Shortcut Purpose

Enter

Select the command box

Esc or Ctrl+

Select the contact list panel

Ctrl+

Select the result display panel

When the command box is not in selection:

and

Scroll the contact list

Ctrl+D

Delete the selected contact (Coming in V2.0)

Alt+F4

Exit the application

Implementation


The keyboard shortcuts mechanism is implemented in the KeyListener class under the UI component of the application. It listens for key presses done by the user and executes actions mapped to these key presses, or key events. The mapping of actions to key events are found in the KeyListenerUtil class.

The following class diagram shows the relationship between classes concerning the keyboard shortcuts mechanism.

KeyListenerDiagram

The KeyListener requires access to the CommandBox, PersonListPanel and ResultDisplay for key event executions, such as changing the focus node.

When the user launches the application, the UiManager calls mainWindow.setKeyListeners() which creates the KeyListener class to handle the key events. In KeyListener, the main method for handling the key events has the following code:

public void handleKeyPress() {
    mainNode.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
        // actions to execute key event
    });
}

The mainNode refers to the node containing the all UI components in the application, and addEventFilter captures key events received by that node. The application will continue to listen to key presses done by the user once this method is called.

Another common method used for capturing key events is onKeyPressed. In this case, addEventFilter is used instead as key events captured by the former will be consumed by the TextField used in the application. addEventFilter captures the key events before they are consumed by the TextField.

When the user presses a key combination which matches one that is assigned, a corresponding action will be executed. An example of such an execution is given as shown:

private void executeKeyEvents(KeyEvent keyEvent) {
        if (KEY_COMBINATION_FOCUS_PERSON_LIST.match(keyEvent)) {
                // action to focus on person list
        }
        // … more cases
}

Where the application focuses on the person list when the user presses a key combination that is assigned to KEY_COMBINATION_FOCUS_PERSON_LIST.

The mapping of KEY_COMBINATION_FOCUS_PERSON_LIST to a key combination can be found in the KeyListenerUtil class.

public class KeyListenerUtil {

    public static final KeyCombination KEY_COMBINATION_FOCUS_PERSON_LIST = KeyCombination.valueOf("Esc");
    // … more mappings
}

In this case, KEY_COMBINATION_FOCUS_PERSON_LIST is mapped to the esc key. As such, a user pressing the esc key will result in the application focusing on the person list.

Design Considerations
Location of KeyCombination constants for the KeyListener class

Alternative 1 (current choice): KeyCombination constants are stored in a separate utility class, KeyListenerUtil
Pros: Better modularity given that the number of constants is fairly large.
Cons: The constants have to be imported to be used in the KeyListener class.

Alternative 2: Store the constants in the KeyListener class itself
Pros: Straightforward implementation and the constants does not have to be imported.
Cons: Violates Separation of Concerns as the KeyListener class acts as a storage for the constants in addition to handling key events.


The way commands words are executed

Alternative 1 (current choice): Pressing the shortcut automatically executes the command
Pros: Execution of command is faster and more convenient for the user.
Cons: User may want to check what the shortcut does before executing the command.

Alternative 2: Pressing the shortcut replaces the TextField with the command word
Pros: Allows the user to check the command before executing it.
Cons: An extra step for user to execute the command, i.e. pressing Enter.


Enhancement Added: UI Improvements


UI Improvements

The user interface of the application has been revamped to take on a clean design, which aims to improve the user’s experience with Rolodex.

Ui2

Some improvements made to the UI include the following features:

Randomized Avatars Colors

Avatar colors are randomized and generated over a color range to reduce the chance of two adjacent persons sharing the same avatar color. Some results from using this color generator can be seen from the following screenshots.

avatars avatars2

The generation of avatar colors involves two steps:

  1. Randomly pick a default color from ColorsUtil, which contains a list of selected colors chosen from Google’s material ui color palette.

  2. Generate a randomized offset value to create a new color which values differ from the default color by this offset.

The code snippet below shows the details of how the color values are manipulated in the color generator.

/**
* Generates a new color with a random offset from {@code Color defaultColor}.
*
* @return The new color in hexadecimal {@code String} format
*/
private static String generateNewColor(Color defaultColor) {
    int r = defaultColor.getRed();
    int g = defaultColor.getGreen();
    int b = defaultColor.getBlue();

    int newR = Math.max(COLOR_VALUE_MIN, Math.min(COLOR_VALUE_MAX, (r + generateOffset())));
    int newG = Math.max(COLOR_VALUE_MIN, Math.min(COLOR_VALUE_MAX, (g + generateOffset())));
    int newB = Math.max(COLOR_VALUE_MIN, Math.min(COLOR_VALUE_MAX, (b + generateOffset())));

    return String.format("#%02x%02x%02x", newR, newG, newB);
}

An upper bound is applied to the offset to ensure that the new color does not deviate too far from the default color.

Keyboard Icon

A keyboard icon is added in the command box to provide visual feedback for certain user events:

  • keyboard : User is idle

  • keyboardTyping : User is typing

  • keyboardError : User entered an invalid command

The keyboard icon is updated by setting ImageView of the icon to one of the three variations as shown above, whichever is appropriate for the current state of the user.

In particular, to check that the user is idle, we measure the time elapsed since any changes made to the CommandBox. This is done with the PauseTransition class from JavaFX, which allows us to execute an event after a set duration. For better illustration, below is a simplified code snippet of the implementation.

private void updateKeyboardIcon() {

    // set icon to typing keyboard

    pause.setOnFinished(event -> {
        // set icon to idle keyboard
    });
    pause.playFromStart(); // reset timer
}

pause is a PauseTransition object, and setOnFinished takes in an event handler as its parameter.

Autoselection for Newly Added/Edited Person

A newly added, or edited person will automatically be selected in the PersonListPanel. This makes it easier for the user to take note of changes made to their contacts.

This implementation is done by raising an event whenever the add and edit commands are executed. The PersonListPanel is registered as an event handler and will subsequently scroll to the person that is newly modified.

This feature is integrated with the Person Detail Panel implementation, and more details on the event handling can be found under the section.

Responsive Split Pane

The split pane which separates the contact list from the other UI components is responsive. Its divider is automatically maintained at the optimal position when the window size changes.

This is achieved by setting up listeners to the property of the window. The divider will be repositioned when the listener detects any changes in the window property. The code below illustrates the implementation in greater detail.

private void setSplitPaneDividerPosition() {

    // repositions divider when window is just showed
    primaryStage.showingProperty().addListener((observable, oldValue, newValue) ->
            splitPane.setDividerPositions(SPLIT_PANE_DIVIDER_POSITION));

    // repositions divider when window width changes
    primaryStage.widthProperty().addListener((observable, oldValue, newValue) ->
            splitPane.setDividerPositions(SPLIT_PANE_DIVIDER_POSITION));
}

Enhancement Added: Person Detail Panel

Justification

The person detail panel aims to reduce cluttering of information in the contact list. With this panel, the contact list will only have to store the essential information for identifying a person, whilst additional information about the person will be displayed on this panel.

External Behavior


The person detail panel displays the complete information of a person that is selected.

detailPanel
detailPanelLabeled

Implementation


The behavior of the panel is mostly event-driven, where the information displayed on the panel changes depending on various user actions, such as a person selection, editing of a person, addition of a person, etc.

For example, the sequence diagram below shows the sequence of events to update the panel display when the user edits a person (the logic aspect of updating the person detail is omitted for brevity).

DetailPanelSequenceDiagram

The information displayed on the panel is extracted from the ReadOnlyPerson that is returned by the EventsCenter. The details of the implementation can be seen from the following code snippets.

/**
* Shows the details of the person on the panel
*/
private void showPersonDetails(ReadOnlyPerson person) {
    setAvatar(person);
    setTextFields(person);
    setTags(person);
}

The showPersonDetails method is called when a PersonEditedEvent is raised, which occurs when the user edits a person.

/**
* Updates the panel when the details of the selected person is changed
*/
@Subscribe
private void handlePersonDetailsChangedEvent(PersonEditedEvent event) {
    logger.info(LogsCenter.getEventHandlingLogMessage(event));
    showPersonDetails(event.editedPerson);
}

The behavior of the panel is summarized in the table below.

User Action Panel Behavior

Select a person

Display selected person

Edit a person

Display newly edited person

Add a person

Display newly added person

Clear the list

Display empty panel

Call undo

Display empty panel

Call redo

Behavior as per command that is redone

Other commands

No change in display

Design Considerations
Synchronization of person details with the panel display

Alternative 1 (current choice): Using the event system to update the panel
Pros: Allows for custom panel behavior for different user actions.
Cons: Increase coupling as updating the panel relies on the logic component for user actions involving commands.

Alternative 2: Bind the fields in the panel to their corresponding object properties
Pros: Less coupling.
Cons: Higher implementation overhead to set up the bindings properly and difficult to enforce custom panel behaviour for specific user actions.


Enhancement Added: Remark Field

Justification

The remark field allows the user to store miscellaneous notes about their contacts. For example, a user might want to note down a hobby of a friend. Such information can be easily added to the contact with the remark field.

External Behavior


remark, rmk, note or comment : Adding a Remark (Since v1.4)

Adds a remark to the specified person in Rolodex.
Format: remark INDEX [r/REMARK]
Alternatives: remark can be replaced by note or rmk or comment
Keyboard shortcut: Ctrl+R

  • Adds a remark to the person at the specified INDEX.

  • The index refers to the index number shown in the most recent listing.

  • The index must be a positive integer 1, 2, 3, …​

  • An existing remark will be replaced.

Examples:

  • remark 1 r/Likes fishing
    Sets the remark of the 1st person to Likes fishing.

  • note 1 r/
    Removes the remark for the 1st person.

  • Remarks can also be added using the add command, and modified with the edit command.

  • Remark commands are undoable with the undo command.


Enhancement Proposed: Add Profile Pictures

External Behavior


Adding a Profile Picture: picture or pic (Coming in v2.0)

Adds a profile picture to the specified person in Rolodex.
Format: picture INDEX FILE_DIRECTORY or pic INDEX FILE_DIRECTORY

  • Adds a profile picture to the person at the specified INDEX.

  • The index refers to the index number shown in the most recent listing.

  • The index must be a positive integer 1, 2, 3, …​

  • The FILE_DIRECTORY indicates the location of the profile picture to be added.

  • An existing profile picture will be replaced.

Examples:

  • picture 1 C:\Users\John\Pictures\my_pic.png
    Sets the profile picture of the 1st person to my_pic.png.

  • pic 6 C:\Users\Alice\Pictures\Contacts\john.png
    Sets the profile picture of the 6th person to john.png.

The second example is illustrated in greater detail here:

  1. Before the addition of profile pictures, the contact list is loaded with the default avatar image as shown below.

    profile pic before
  2. pic 6 C:\Users\Alice\Pictures\Contacts\john.png is entered in the command box with the valid FILE_DIRECTORY.

  3. If the command is entered correctly, you should see the updated profile picture as specified in the FILE_DIRECTORY of the command.

    profile pic after

Other contributions

  • Imported the Segoe UI font pack into the application (Pull request #136)

  • Added a prompt text to the result display panel (Pull request #122)

  • Added the removeTag method (Pull request #6)

  • Updated the General Documentation (Pull requests #2, #66, #112, #124, #134)