This assignment is to build the "Light Table" window for the Photo Album application. This window will contain shrunken thumbnail components for each photo in your album. You can click on these thumbnails to go to the corresponding photo in the album, and drag them to rearrange them the order of the corresponding photo in the main application.
The learning goals for this assignment are:
Users should be able to do a number of operations on thumbnails in the Light Table:
Hint: Scaling is easy. In fact, you can reuse the existing paint() code from your previous UI class with the addition of only a few lines: just make a copy of the Graphics object that's passed to your paint() method, cast it to a Graphics2D object (the one that has all the fancy operations on it), and then call scale() on it, passing a floating point scaling factor (0.2 to draw at 20% normal size, for example).
Your Light Table component should "notice" any change to the set of thumbnails it contains; soliciting ContainerEvents is the easiest way. When new thumbnails are added or old thumbnails are removed, the Light Table should begin a process that recalculates its preferredSize based on the current set of thumbnails. This size should be the insets around the component, plus the room taken up by any thumbnails, plus whatever spacing you have around the thumbnails. Then, you should iterate through the thumbnails and call setBounds() on each of them to update their position in the new layout. Important: If you change your preferredSize, you have to tell Swing about this fact because the sizes and positions of other components in your frame may need to change. You do this simply by calling revalidate() and then repaint() whenever your size changes; Swing takes care of the rest.
When you know what thumbnail has been selected, you can call back to the application to have it set the current photo.
For the drag, when the mouse is pressed anywhere in the light table 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).
Hint: Your Light Table can use the normal component methods to access the list of child thumbnails contained inside of it. So, for example, it can use getComponents() to get a list of all current children. You may have noticed that there's the possibility for a caller to add any type of child to the Light Table, not just thumbnails. If you want to restrict this, you can define your own add() method on the Light Table that verifies the type of components passed to it and then calls super.add().Hint: You may have noticed that the ordered list of PhotoComponents you're keeping in your main application is much like a Model for the Light Table. If you wanted to get all fancy, you could architect the entire application so far with a model that contains all of the PhotoComponent models in the album, and then do separate Component and UI classes for both the main app and the Light Table. No requirement to do this however.
Hint: 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. For the thumbnails, be sure that you're repaint()'ing the thumbnail whenever the underlying model sends you a ChangeEvent.
Design rationale: In the description above, I have the Light Table doing the input processing, while individual thumbnails are "output only." You could also do this a different way, with thumbnails themselves soliciting the events that make them clickable, draggable, etc.
While each strategy has it's own merits, I think the one I've outlined above is the most sensible. It's the Light Table, after all, that is providing the selection and reordering functionality for the album application, not individual thumbnails. You might, for example, want to reuse the thumbnail class in a window other than a Light Table, and so selecting and dragging might have different semantics there. If the thumbnail class itself implemented this functionality, it would make thumbnails harder to reuse.
Possible gotcha: If you make the thumbnails respond to mouse events, then they'll consume any events that happen on them (remember that Swing dispatches events to the lowest leaf-node component that can accept those events). Be sure to not make your thumbnail component a MouseListener or MouseMotionListener if you don't want this to happen.
Design rationale: If you've done much Swing programming in the past, you know that Swing typically uses Layout Managers to handle the sizing and positioning of child components inside a container. Why are we not implementing this as a custom layout manager? The main reason is that I want to be able to drag and move thumbnails without having to compete with whatever positions that a layout manager would be trying to set for these. If you do want to implement a custom layout manager to do this, see me about getting some extra credit. :-)
A simple bonus is to have the Light Table show the selected thumbnail differently somehow. For example, it may detect which thumbnail is selected and paint a "halo" or "glow" effect under it. The way you'd do this is to override the Light Table's paintComponent() method and simply draw the effect at a size slightly larger than the thumbnail. Since thumbnails are painted after the Light Table gets painted, the glow effect will be visible behind the thumbnail. +1 point for this one.
Another 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. This should be a very cool effect. Remember that you'd have to have the Light Table listen for updates to component sizes in order to correctly redraw the Light Table itself; likewise, 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.
As described above, when a drag ends the Light Table will figure out where it should go in the new photo ordering, then recompute a standard "grid" layout for all of the thumbnails. Visually, this will look like all of the new 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.
This assignment will probably be a bit easier for most people than the last one. My suggestion is to start with the thumbnails. See if you can get a simple thumbnail component going that you can display in a simple, standalone frame next to your main application before starting on the Light Table.
Once you've got this going, implement the Light Table component itself. See if you can get the basic layout mechanics going first, before starting on input. In other words, make it respond to new thumbnails being added, compute a preferred size based on the number of thumbnails, and then set the positions of these thumbnails based on the preferred size.
Finally, I'd save the input stuff for last. It's not hard, but does require that most of the rest of the UI is working correctly before you can start on it. To turn in the assignment, please follow the same process as last time:
0. Make sure your program is runnable from the command line using the command "j ava PhotoAlbum". Use exactly this name (with no package) to make things easier on me and the TAs.
1. Create a new directory using your last name as the name of the directory.
2. Compile your application and place both the sources and classfiles into this directory (they can be at the top-level or in a subdirectory, whatever you want).
3. Put a README.txt file (described below) into the top level of the directory. This file should contain your name and email address, the version of Java you used (1.5.x or 1.6.x, please) as well as any special info I might need in order to run your code (command line arguments, etc.)
4. ZIP this directory and submit it via T-Square (instructions are here).
If you do any extra credit work, be sure to let us know about it in the README file so that we don't miss it!!
Please take care to remove any platform dependencies, such as hardcoded Windows path names or dependence on a particular look-and-feel that may not exist on all platforms. Also, if you use any images in your application, please make sure that you include these in your ZIP file and that your code will refer to them and load them properly when from from inside the directory that's created when I unZIP your code.
Grading for this assignment, and future assignments, will roughly follow this breakdown: