HOMEWORK 2: Building Custom Swing Components
This is an INDIVIDUAL assignment.
Objective
In the last assignment you learned how to put together Swing applications
by using existing components. In this assignment, you'll learn how to
create your own Swing component from scratch. We'll be building the
"content area" of the photo album application that displays a single
photo as well as annotations added by the user. This PhotoComponent
you'll write will have to
respond to both keyboard and mouse input (using the mouse to simulate a
pen), and render appropriate output.
Additionally, you'll learn how to use Swing's MVC
(Model-View-Controller)-like design patterns. While these patterns
require you to build a bit of extra boilerplate code, they're the
foundation for how Swing supports pluggable looks and feels.
The learning goals for this assignment are:
- Experience with the Swing drawing pipeline.
- Experience writing a variety of input listeners.
- Experience with using the Swing MVC patterns to architect a custom
component.
Description
Don't be intimidated by the length of the homework description!
Although long, most of this writeup is here just to provide detail about
what I expect from this assignment, as well as some hints about how best
to do the implementation.
In this homework, we'll create a custom Swing component that serves as the
content area of the photo album application. The basic idea is that this
component displays a single photo, and also provides a way to store and
render annotations including text and drawn strokes. You can "flip the photo
over" to annotate on it using either mouse strokes or text.
There are a number of specific requirements for this assignment:
#1: Basic component architecture.
Remember in class how we discussed that components should implement the
paintComponent() method to do their drawing. This strategy works fine,
and is what was used by all Swing components up until Swing acquired a
pluggable look-and-feel (PLAF) mechanism. When PLAF came around, Swing
introduced a new way to structure components, based on a modified
Model-View-Controller (MVC) architecture.
For this assignment, I'd like you to use this
style in the implementation of your component. It's pretty easy to do
(although it requires a bit of boilerplate code), and it gives you some
pretty cool extensibility in terms of just dropping new interfaces on
top of the existing data structures for your component. Using MVC now
will also let us do some cool stuff in future assignments.
There are several things you have to do in order to use the MVC style in
Swing:
- Create a separate model class for your component. The model
is the piece that contains the core data structures that represent the
state of your component. In this assignment, the model will likely
contain a reference to the picture file, the photo's "flipped" state (is
it flipped over for annotation or not), as well as
lists of the strokes and text associated with the photo (see
below).
- Create a separate UI class for your component (in Swing the
UI essentially combines the View and Controller from MVC). This class
will be responsible for responding to user input (mouse and key clicks),
as well as for drawing the component.
- Application programmers do not typically work with the model or the UI classes
directly; rather, these classes are an internal architectural feature of a
component's implementation. Your new PhotoComponent should extend
JComponent and have the necessary boilerplate to set up the model and UI
objects that it'll use internally.
There's a bit of standard boilerplate code that you have to write that
sets things up correctly. For example, the model needs to
communicate with the UI through ChangeEvents whenever the model's state
changes (whenever a new stroke is drawn, for example). This gives a
nice, loose coupling between the model and UI, allowing different UIs to
be combined with the same model. There are also a few standard methods
that you'll have to implement that have to do with telling models about
UIs and vice versa.
Hint: Look at the "MVC Meets Swing" link off the Supplemental
Readings list for an overview of how this all works. Also, the Java
Swing book has a nice section that shows how to do this in detail; look
at pages 1133-1155. If you didn't buy the Java Swing book, you may want
to borrow a friend's copy as it's especially useful for this assignment.
#2: Photo display.
Your component will hold a single photo. Since the component may be
larger or smaller than the photo it displays (because the user may resize
the window, for example), you need to provide a background behind the
photo, and a "frame" or some sorts around it. For example, you could do a
beige paper background, or something with a pattern (blue graph paper
maybe). For the frame around the picture you can have a simple white border
or something resembling a film slide, or "scrapbook corners". The background
should be rendered underneath the photo itself. The frame
can either be rendered above or below the photo.
Hint: For background graphics, you can simply write a little routine that draws the
necessary fills and lines, and call it first thing in your UI class's
paint routine. This will ensure it gets drawn before any stuff that you
render later in that route.
#3: Flipping to annotate.
With physical paper photos, people often
flip them over to write notes and other annotations on the back. Your
PhotoComponent should support this also. Double-clicking on the photo
should cause it to be replaced by a plain white surface for annotation.
All of the rest of your component (the background and frame) should be drawn the same--only the photo should be replaced. When in this mode, you can
annotate the photo's "back" via drawn strokes and typed text (see #4 and
#5 below). Double-click on the photo back again to flip it over and see the
photo again. The current "flip state" of a given photo should be a part of
its model, meaning that if you come back to this PhotoComponent again it should be in
the same flipped or unflipped state that you left it in.
Note that yoou do not have to do
any fancy animation for flipping, although you'll get some extra credit if
you do.
#4: Support for drawn strokes.
When in the flipped state, you
should be able to draw onto the photo back using the mouse (if you're using a pen
tablet, the pen also produces mouse events and so this sort of implementation
will also work nicely on a pen-based computer). What this means is that
the user should be able to draw freehand strokes by dragging the mouse
on the photo back with the button pressed. The component should show the
stroke while it is in the process of being drawn, to give appropriate
feedback to the user.
Hint: Remember that you'll need to redraw all of these same
strokes anytime the Swing repaint pipeline tells you that you need to
paint your component. So, the classic way to do this is to add strokes
to a display list that contains the things to be rendered, and
then in your paint code you simply iterate through the items to be
painted, rendering them to the screen.
Hint: Painted strokes will look much better if you use
Java2D's anti-aliasing mechanism. Look at the setRenderingHints()
method on Graphics2D.
#5: Support for typed text.
When in the flipped state you should also allow typed text to be entered onto the photo back.
The way this should work is that the user clicks on the photo back to set an
insertion point for the text. Then, any typing will begin to fill
the photo back starting at that insertion point. Clicking again will reset the
insertion point to another position. While you don't have to do
any especially fancy text processing (no ligatures or custom fonts or
anything like that), you should make the basics work correctly. The
basics are:
- You should implement word wrap. This means that when your typing
hits the end of the line you should find the rightmost whitespace in the
line, break the line there, and put the remaining word on a
new line. If there is no whitespace in the line then you can just break
the line at the last character that will fit on the line.
- Use reasonable linespacing. Remember: ascent + descent + leading.
- You should reflow the text if the content area is resized. This
means re-word wrapping the lines so that they fill the new size
appropriately.
You do not have to implement more complicated features (although
you're welcome to for extra credit). For example, you do not have
to implement:
- Backspacing over already entered characters.
- The ability to put the insertion point into already-entered text and
edit it.
- The ability to select a span of text.
Hint: While all the word wrapping and reflowing stuff may seem
difficult, it's possible to architect the code in such a way that you
kill lots of birds with one stone. The key is to remember that, like
with stokes above, you'll need to keep a data structure containing the
text that will be rendered by your component.
So one way to architect things is to
simply create a new object to hold a text block whenever the insertion
point is set; this object only needs to remember the insertion point and
the set of characters entered at that point.
Whenever characters are typed they are simply added
to the current text block object. The job of your paint code, then, is
simply to iterate over the list of text blocks and draw them to the
screen, wrapping as you draw based on the current size of the component. This
strategy will take care of word wrap and refilling via one
implementation in the component's painting code.
Hint: Telling the difference between "pen mode" and "draw mode"
should be easy: If you see a mouse down followed by movement, you can
assume you're drawing. If you see a mouse click, you can assume you've
set the text insertion point for keyboard entry.
#6: Integration with the rest of the application
Once you've implemented and debugged your PhotoComponent, it's time
to integrate it into the application you wrote for Homework #1. When
your application starts up, it should contain no photos, and hence no
PhotoComponents.
Selecting Import Photo should prompt the user for a photo file (via a file
dialog), and create a new PhotoComponent containing this photo and display it in the content area.
Selecting Import Photos from Folder should prompt the user for a
directory (via a file dialog) and create a set of PhotoComponents containing
the photos.
The currently displayed PhotoComponent
should be used in the central content area of your application, should
resize properly when the window is resized, etc. (You don't have to
worry about scrolling within a PhotoComponent, although you can implement this if you
want to. You also don't have to worry about doing fancy stuff like scaling
photos.)
Selecting Delete Photo should delete the current PhotoComponent. You'll
probably want to update your application to have a list of your
PhotoComponents so that you can keep track of them.
Selecting Previous and Next should move between the PhotoComponentss.
Extra Credit
There are a lot of ways to augment this assignment, should you choose
to. Some obvious ways are:
- Fancier text processing
- Allow editing of already-entered text. +2 points
- Support backspace of entered text. +1 point
- More options
- Customizable fonts. Either on a photo-wide basis (+1 point), or for
individual spans of text (+3 points).
- Pen sizes and ink color: +2 points.
- Fancy features
- Animated photo flipping. You can imaging a fancy flip effect that
happens when you transition between flip and unflip mode. +1 points
for a basic animation up to +3 for extreme fanciness.
- Saving and loading of data. PhotoComponents should remember what picture
they're displaying as well as the strokes or text on them, between
runs of the application. It'd be cool if this happened
automatically as strokes or characters are entered, or photos are loaded, so you never have to
remember to hit "save." +3 points.
- Other stuff. If you build in some particularly neat feature, make
sure you let us know about it when you turn it in and we'll consider it
for possible extra credit.
Deliverable
This is an INDIVIDUAL assignment; while you may ask others for help on Java
or Swing details, please write the code on your own.
While you can use any development environment you choose, you are not
allowed to use "GUI builder" type tools (like JBuilder or Eclipse GUI
Builder).
While this assignment is significantly more difficult than the previous
one, most of the parts should be fairly straightforward. Start with the
basic PLAF component architecture and get that working in your
application first; one easy way to do this is to implement the UI
class's paint method so that it only draws the component background. This way
you'll be able to see your component show up and know it's working
correctly. After this do the stroke processing (it's the next
easiest), followed by the text processing (slightly more complicated).
Finally, once all of this is working, it should be fairly
straightforward to keep a list of PhotoComponents in your
application and make the New/Delete/Next/Previous controls work.
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:
- 60% functionality
- 40% good architectural design, coding style, commenting
Please let the TA or me know if you have any questions.