Assignment 7: The Music Editor:   Third Movement
1 Implementing a controller
1.1 Handling the keyboard, generally
1.2 Mouse handling
1.3 Organization of dependencies
2 Composite Views
2.1 Assignment
3 Testing
4 Grading standards
5 Submission
6.7

Assignment 7: The Music Editor: Third Movement

Due: Mon 11/21 at 8:59pm; self-evaluation due Tue 11/22 at 8:59pm

This assignment is to be completed with the same partner as Homework 6.

This week you will complete your music editor. You will need to implement one more view, and implement the controller for your editor.

Like views and models, controllers are best described as an interface whose purpose is to mediate the interactions between the view and the model. Multiple implementations of controllers are possible — potentially one for every model/view pairing. This can be tricky, since different views (e.g. console or GUI) can expose different possibilities for user interaction.

1 Implementing a controller

Your controller must allow the user to (at minimum):

  1. Most likely, you have an interface View, which is directly implemented by your ConsoleViewImpl, GuiViewImpl and MidiViewImpl classes. Create a sub-interface GuiView that extends View, and refactor GuiViewImpl to implement it. No existing code should break, if you’ve properly abstracted everything up to this point. Use this GuiView interface to add GUI-specific methods that make no sense for the other view types, such as methods for dealing with keyboard and mouse.

The most important design constraint for your controller will be keeping the dependencies ordered the right way. In particular, the views should not know about the controller, even though you are implementing an asynchronous controller, where users supply inputs whenever they please, and the view must somehow inform the controller of that fact. Instead, the controller must inject its controlling actions as callbacks into the views. Let’s see how this works.

1.1 Handling the keyboard, generally

You may design your keyboard handler however you wish. Use this tutorial on writing a KeyListener to get started. Below we sketch one particular design to get you started.

The general theme of handling the keyboard is quite uniform: every time a key is typed, or pressed, or released, potentially do something depending on the key. Keys are represented in Java as integers (because future keyboards may add new keys, so using an enumeration is not possible). To keep your code streamlined and yet extensible, this suggests building a Map<Integer, Runnable> whose keys are the (character codes for the) keys of interest, and the values are function objects representing what to do when that particular key is pressed. (If no Runnable is present in the Map for a particular key, that indicates nothing should be done when that key is pressed.) You may need three maps, one for keys being typed, one for keys being pressed (i.e. held down), and one for keys being released.

  1. Design your keyboard handler:

    1. Design a class KeyboardHandler that implements the KeyListener interface.

    2. The KeyListener interface is defined in terms of handling KeyEvent objects, which unfortunately are not defined with an extensional equality or hashCode that work for us here. So instead of storing KeyEvents directly, give your class three Map<Integer, Runnable> mappings from key-codes to the handler for that particular keystroke.

    3. Implement the KeyListener methods by looking up the relevant key in the relevant map, and if a Runnable is found, invoking it.

    4. Add methods to allow clients of your class (such as your controller) to install Runnables for the various key events that are of interest.

  2. Enhance your controller to create a KeyboardHandler, and install as many key event handlers on that object as it needs to.

Congratulations: you’ve now implemented a miniature analogue of dynamic dispatch, where the handler code that runs is dynamically determined by inputs that are only available at runtime.

1.2 Mouse handling

Mouse handling involves a MouseListener interface, just as keyboard handling involved a KeyListener.

  1. Design a class that implements MouseListener. It probably does not need to be as sophisticated and indirect as the KeyboardHandler above, since there are only three possible mouse events (left, middle and right clicks), rather than an entire keyboard.

  2. Enhance your controller again to create one of this mouse listener objects, and configure it however you need to.

  3. Enhance your GuiView again with addMouseListener(MouseListener) (and possibly a removeMouseListener too).

1.3 Organization of dependencies

Notice that the controller is telling the GuiView “please use the following objects to react to events”, while the GuiView is advertising that it is capable of reacting to mouse and keyboard events. The views don’t have to know anything about what the controller wants to do with those events, or even have a reference to the controller object directly. This ensures a looser coupling between the controller and view, and allows the controller to freely modify the keys it wants to handle without requiring changes within the view (beyond the initial, generic ability to handle events in the first place).

2 Composite Views

Having visual and audible views completely separate seems unfortunate: it’s time to remedy that.

  1. Design a new view that takes in both a GuiViewImpl and a MidiViewImpl. It should be able to start, pause and resume playback of the sound, and it should draw a red line that sweeps across marking the current beat. Once the current beat reaches the right-hand edge of the window, the whole window should scroll to bring the next measures of the composition into view.

This will assuredly require enhancing your view interfaces with new methods. Consider carefully whether the new methods you design could plausibly be part of the View interface, or only on the GuiView sub-interface.

To synchronize the sound and visual views, you will need some sort of common reference of time. Either both sound and visual synchronize with a common time, or one of the views synchronize with the other.

2.1 Assignment

  1. Implement the controller, keyboard and mouse handlers, and the new view above. Place all the code of your views in the cs3500.music.view package, and the controller and handlers in the cs3500.music.controller package.

  2. Document any further changes made to your models or views from the previous assignments: explain what was added, removed or changed (besides the package declaration), and why.

3 Testing

Testing KeyboardHandler is straightforward — by design, it should be very easy to construct mock Runnables that confirm that they’ve been run.

Testing the mouse handling is harder (unless you designed a MouseHandler to match the keyboard).

Testing the controller should be straightforward — all of its behavior is either in its methods, or in the wiring-up of those methods to key and mouse event handlers. If you’ve already tested that the wiring works properly, now all that remains is to test the methods themselves.

Complete any testing from the last assignment that you didn’t otherwise finish.

Hint: When you are testing each component above, be clear about what you are testing. The objective of testing the keyboard handler is to ensure that the appropriate action is taken on the appropriate key, not whether that action is successfully completed (that is part of testing the controller).

4 Grading standards

For this assignment, you will be graded on

5 Submission

To create a JAR file, do the following:

Please submit your homework to https://cs3500.ccs.neu.edu/ by the above deadline. Then be sure to complete your self evaluation by the second deadline.