HOMEWORK 3: Containers and Layout

This is an INDIVIDUAL assignment.

Objective

In the last assignment you learned how to create a custom PhotoComponent that could render a photograph and any associated annotation. In this assignment, you'll learn how to create container components that contain and manage the layout of multiple children. The container we'll create is commonly called a Light Table, as it's used by photographers to review collections of photos.

The learning goals for this assignment are:

Some Important Notes for This Assignment

First, this is a large assignment with many subparts. Please get an early start on it, and see us early if you run into trouble or get stuck, or if the basic concepts are still confusing to you.

Second, my recommendation is to not use full-size digital images when testing your application. Images take a lot of memory, and without additional configuration settings when you run Java, your Java Virtual Machine may run out of memory. So, to make your life easier, you may want to scale down your photos to 640x480 or some such, and not use too many of them.

(A "real" photo browser application (on any platform, not just Java) would handle this by not loading up all images at once, but only lazily loading them when needed, and unloading them when they're not actually being displayed.)

Finally, a good strategy for an assignment like this is to start small. For example, implement one of the view modes and get it working before worrying about changing between views. For the thumbnails, you might start by creating "dummy" thumbnail components that don't actually render real photos, but simply show a 75x50 block of red or some such. This will let you start working on application integration without having to have everything working completely beforehand.

Description

In this assignment we'll create a custom LightTable container component that can give the user an overview of all the photos current imported, and allow navigation among them. This component will replace the single PhotoComponent from Homework 2 in the main content area of the application. The view mode (described below) controls how the Light Table will display the subcomponents that are its children. The Light Table should stay up-to-date with respect to the photos in your collection, meaning that new photos should show up immediately in the Light Table after they are imported; deleted photos should disappear; and any changes to individual photos should be displayed in that photo's view in the Light Table.

View Modes

The LightTable will display the photo collection differently depending on what View Mode is selected: These modes are similar to the ones in many commercial photo browsing applications. For example, the screen shots below show modes from the Aperture tool that are similar to what we'll be building:
Photo Viewer Mode. The single selected photo is displayed. Browser Mode.Two-dimensional matrix view of all photos in the collection. Split View Mode. The currently selected photo is displayed at the top, with a "film strip" view of the collection at the bottom.

Navigation Behavior

The basic navigation behavior we'll be implementing is what's standard in any commercial photo browsing application: In any viewing mode, clicking on the Forward and Backward buttons should take you to the next and previous photos, respectively. In Photo Viewer mode, this will simply change the single displayed photo. In Split View, this will change the displayed photo and also highlight the currently selected thumbnail image to indicate that it is the selected one (notice in the screen show of Aperture's split view mode that the thumbnail picture on the bottom left is surrounded by a white box to indicate that it's the currently selected picture). In the Browser mode, the currently selected photo should be highlighted just as in Split View.

Additionally, the user should be able to single-click on a thumbnail image in either the Split View or Browser mode to change the currently selected photo (in Split View this will display a new photo at the top of the Light Table and change the highlighted thumbnail; in Browser mode this will just change the highlighted thumbnail).

In either Split View or Browser View, double clicking on a thumbnail should select that photo, and switch your viewing mode to Photo View.

Finally, in any view, selecting Delete Photo from the menu should remove the current photo and thumbnail, and navigate the user to either the next or previous photo in the list. This menu item should be greyed out if there are no photos imported, or no current photo is selected.

Be sure that your View menu works correctly: selecting one of the modes from this menu should change the corresponding view mode, and likewise when you double click to enter Photo View mode, the menu should reflect this change in state. The "flipping" and drawing behavior from the previous assignment should continue to work on the PhotoComponents displayed in Photo or Split view mode.

To summarize, the specific navigation-related functions you need to implement are:

Implementing the Thumbnails

The Thumbnail images in the Split and Browser modes are essentially kind of like little PhotoComponents. In fact, they're similar enough that one good implementation strategy is to leverage the work you've already done in PhotoComponent. Here's a suggested way to build a ThumbnailComponent:

(There are other approaches you could use to create a ThumbnailComponent, but this is the one that seems the most straightforward to me.)

Implementing the Light Table Itself

Once you've got the thumbnails working you're set to do the Light Table component. While there are a lot of ways to do this, here's how I'd suggest proceeding.

Making the Component
First, I'd create a custom component by subclassing JComponent. You will probably want a mode variable (and corresponding getter/setter methods) to keep track of what view mode you're in, and make sure this is hooked up to your View menu. You will probably also want a currentPhoto variable that tracks which picture is currently selected, as well as a data structure to keep track of the ordered list of all photos and thumbnails.

Overall Structure
Your LightTable component will have a number of child components that it manages directly, and that it uses to structure how the photos and thumbnails are layed out. For example, when in Photo View mode you want a single large area that holds a single PhotoComponent. You could implement this by having a child JPanel set as the CENTER component of a BorderLayout when in this mode. When in Split View mode, you want a similar large area for holding a PhotoComponent plus an area at the bottom that holds a set of ThumbnailComponents. You could implement this by having a child JPanel in CENTER to hold the PhotoComponent, plus a child JPanel in SOUTH to hold the Thumbnails. In Browser View you want a large area for holding the grid of thumbnails. You could implement this by having a child JPanel in CENTER to hold these. Then, whenever you switch viewing modes, you install and remove these JPanels in their respective places to create the layout you want.

When your application starts it should "tell" your LightTable about the set of photos that have been imported; the LightTable can then create PhotoComponents and ThumbnailComponents to represent these, store them in some data structure, and install them in the various panels that you use for layout, depending on what your mode is.

Hint: Remember that Swing components can only have one parent at a time. So whenever your viewing mode changes you'll need to take the components out of the panel that currently contains them and add them to the panel you're using for your current layout mode. For example, if you've got a bunch of thumbnails installed in the 1-d filmstrip panel, and you change to Browser mode, you'll want to remove these and re-add them to the panel that implements the 2-d browser layout. You'll probably want to write a little changeMode() helper function to do this work for you.
Layout and Scrolling
For the panels that hold your PhotoComponents and ThumbnailComponents, you'll need to ensure that they properly lay out their children. The JPanel that holds the PhotoComponent that appears in Photo and Split mode is the easiest: just use the existing BorderLayout manager and install the PhotoComponent in the CENTER to ensure that it's centered appropriately. For the panels that hold the 1-d and 2-d sets of thumbnails, you'll need to ensure that these are layed out correctly (doing the single PhotoComponent that appears in Photo or Split views is easy: just put it in a JPanel and use the existing BorderLayout manager with CENTER to ensure that it's centered appropriately).

For the panels that contain Thumbnails, you'll probably want to use either BoxLayout or FlowLayout for the Split view panel, and GridLayout for the Browser view panel. Check the Java Docs for each of these; you'll probably want to add some padding (called "glue" in the BoxLayout) around your Thumbnails so that they look nice.

Note: By using the existing Swing Layout Managers to arrange the thumbnails and photos, you're ensuring that the panels that contain them will be sized correctly: the layout manager you select will figure out the size necessary to hold the children components (by querying them for their min, max, and preferred size), position them correctly, and then set the size of the panel so that it can hold them all. In order for this to work, you need to make sure that your Thumbnail and Photo Components are returning correct values for min, max, and preferred size.

How do you implement scrolling of the browser or split view panels? Easy! Rather than adding these panels directly to the LightTable, add them to a JScrollView and then add that to the LightTable. The JScrollView will let its child component expand to whatever size it needs, and then add a scrollbar so that you can move around the view to see all of it.

Responding to Events
You'll need to respond to MouseEvents in order to implement the interaction with the user. To handle the clicks and double clicks, you can add a MouseListener to the JPanels that contain the thumbnails. Then, when a click occurs, you simply iterate through your thumbnail child components and test whether the mouse click was within the bounding box of the component (using something like child.getBounds().contains(event.getPoint())).

When you know what thumbnail has been clicked, you can then set it as selected and/or change the currently displayed photo. An easy way to highlight the selected thumbnail would be to call setBorder() on it to give it a visible border.

Overall hint for this assignment: Having repaint problems? Are your changes not getting reflected on screen? Be sure to call revalidate() anytime the set of children changes, or your size changes. Be sure that your components are responding with valid min/max/preferred sizes when queried.

Possible gotcha: Having event problems? Are your components not responding properly to mouse events? Remember that Swing dispatches events to the lowest leaf-node component that can accept those events. So, if you've made your ThumbnailComponent a MouseListener, it'll receive the events rather than the panel that contains it. So be sure to not make your thumbnail component a MouseListener if you don't want this to happen.

Extra Credit

This is a complex assignment, so make sure you get the basics working first. If you have time, however, there are a bunch of ways to make this assignment more useful and visually appealing.

One possibility is to do variable-sized thumbnails. One way you could do this is to have a "zoom slider" at the bottom of your Light Table. Moving the slider changes the scale factor--meaning you'd iterate through all of the thumbnails and update the scale factor that they use to paint and to compute their sizes. Remember that the thumbnails should "notice" when their scale factor is updated and then call repaint() on themselves and generate a component event when their size changes. +4 points for doing this one.

A more complicated feature is to allow the user to drag thumbnails in either the split or the browser view to change their order. If you do this, you will need to change the way the thumbnails are contained in their parents: in particular, you will not be able to use the standard LayoutManagers to lay out the thumbnails, since these layout managers expect to be able to control the positions of their children, and wouldn't allow a drag. Instead, you'd need to set the positions of the thumbnails within their parent explicitly (probably by doing this "manually" although you could create a custom LayoutManager class if you really wanted to get fancy).

For the drag, when the mouse is pressed anywhere in the parent component you should record the thumbnail (if any) on which the press occurs. Then, when a mouse drag begins, you can begin moving the selected component. Do this by calling setBounds() on the thumbnail. Note that to do this "right" you'll have to set the new location of the thumbnail to be the location of the mouse pointer minus the offset of where the mouse press occurred inside the thumbnail.

When the drag ends (and the mouseReleased callback is fired), you should iterate through the list of thumbnails to figure out where the dragged thumbnail was dropped. Then you can (1) reorder the photos in the application to reflect the new ordering, and (2) recompute new standard positions for all of the thumbnails in the LightTable (so that everything appears aligned on a grid for example). Up to +8 points for this feature.

As described above, when a drag ends the thumbnails will all have new positions and will be repainted. Visually, this will look like the thumbnails instantly snapping into their new locations. We know that jarring visual transitions are bad (right??), and so a nicer effect would be to smoothly move the thumbnails to their new positions. This actually isn't as hard as it sounds: look at the current position and the intended final position for each thumbnail. Compute a set of intermediate points (maybe 10 or 20) between these, for each thumbnail, and save these in a list. Now you're all set! Use the Swing Timer class (javax.swing.Timer) to schedule a series of events; for each event, set the location of each thumbnail to the successive item in its intermediate position list. By changing the position of the components, Swing should repaint the window and show each component smoothly moving to its new position on the screen. +4 points for this one.

As usual, if you do something else that's above and beyond the call of duty, let us know in your README file and we may assign some extra credit for it.

Deliverable

See here for instructions on how to submit your homework. These instructions will be the same for each assignment.