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 "user content area" that resides in the right hand side of the Courier application. The component you write will have to respond to both keyboard and mouse input (using the mouse to simulate a pen), and render appropriate output.

The learning goals for this assignment are:

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 user content area for the Courier application. The basic idea is that this journal page component serves as a container for text, drawn strokes, and graphical figures, simulating the appearance of a page in a paper notebook. There are a number of specific requirements for this assignment:

#1: Paper-like background

Your component must have a background that looks like a common notebook page. So, for example, you could do a blue graph paper background, or a yellow legal pad style, or something else. This background will be rendered underneath any content that's on the page.

Hint: 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 that it gets drawn before any stuff that you render late in that routine.

#2: Support for free-form drawn strokes and shapes

Your component should allow the user to draw onto the page 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).

When the user has selected the free-form ink radio button at the bottom of the content area, the user should be able to draw freehand strokes by dragging the mouse on the page 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.

When the user has selected the rectangle or oval radio button at the bottom of the content area, the user should be able to draw those shapes in the content area, much as in a normal drawing program (press to position the initial corner, then drag out to change the size, then release to "finalize" the shape). During the drag, you must show the shape to give feedback to the user; this includes "rubber band" behavior, where previous iterations are "undrawn".

Hint: Remember that you'll need to redraw all of these same strokes and shapes 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 and shapes 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: Confused about how to "undraw" the previous shape during a drag? The trick is to remember that you write your paint code so that it always draws the current state of the world. In other words, when the mouse is moved so that the previously drawn iteration of the shape is out of date, the mouse event handling code needs to simply update the display list to reflect the new size of the shape and then initiate a repaint again. (For cases like this, where you might be in the process of drawing a shape that's not yet finalized, you may want to keep a variable called inProgressShape (or some such) that holds any shape that's currently in the process of being drawn. Your mouse handling code would then just update the coordinates in the shape referred to by that variable and initiate a repaint.)

Hint: Painted strokes will look *much* better if you use Java2D's anti-aliasing mechanism. Look at the setRenderingHints() method on Graphics2D.

#4: Support for typed text

Your component should also allow typed text to be entered onto the page. This is functionality that's not included in the real Courier prototype (since it has no keyboard), but we're going to do this to give you experience with text handling in GUI components.

The way this should work is that the user clicks on the page to set an insertion point for the text. Then, any typing will begin to fill the page starting at that insertion point. Clicking again will reset the insertion point to another position on the page. Note that you do not need an extra "text entry" radio button at the bottom of the content area to do this--no matter what mode you're in, a single-point click should set the text entry point.

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 do not have to implement more complicated features (although you're welcome to for extra credit). For example, you do not have to implement:

Hint: It's probably possible to build the text functionality in such a way that you're using the existing JTextArea component's code to render and process the text. My recommendation is to NOT do this, but instead build your text processing code from scratch. For one thing, it's probably easier to do this than to try to figure out how to make a JTextArea work while playing nicely with the rest of the application. Second, you want your text to look like it's been typed directly onto your notepage background, not floating in a separate component.

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 in that block. 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 in your display list and draw them to the screen, wrapping as you draw based on the current size of the page. This strategy will take care of word wrap and reflowing upon resize via one implementation in the component's painting code.

#4 Integration with the rest of the application

Once you've implemented and debugged your journal page component, it's time to integrate it into the application you wrote for Homework #1. When your application starts up, it should create one initial instance of the journal page component that represents the first (and only) page currently in your journal. This component should be used in the right size user content area of your application, should resize properly when the window is resized, etc.

Hint: A good overall resize strategy to use is to start the content component at a "reasonable" initial size to allow it to fill the right side of the app without scrolling. Then, allow the content component to be arbitrarily stretched as the user resizes the window, and set the actual size of the content component to be the *maximum* size that it ever acquires after a resize. If the currently available space is smaller than the current size of the component, scroll bars should allow the user to view the entire content area. Any content that's occluded when the window is shrunk would be revealed when the window is later enlarged.

Selecting New Page should create a new page and take you to it. Selecting Delete Page should delete the current page (unless there's only one page--your notebook should always have at least one page). You'll probably want to update your application to have a list of your journal page components so that you can keep track of them.

Selecting Previous and Next should move between the pages.

Hint: I'd recommend that you create a separate PageComponent instance for each page in your application, rather than keeping just one PageComponent and swapping out its data structures. This means, of course, that you'll need to make sure you're only displaying one PageComponent at a time, and switch between which one is visible when next/previous/delete are selected. You can do this by juggling the visibility of your components to ensure that only one's showing at a time (setVisible()), by swapping out which component is contained in the parent at any given time, or by using a layout manager such as CardLayout.

Extra Credit

There are a lot of ways to augment this assignment, should you choose to. Some obvious ways are:

Special Extra Credit Opportunity

Remember in class how we discussed the Model-View-Controller (MVC) pattern that represents an optional, different way to architect components. In "normal" components, the paintComponent() method does all the drawing. This works fine, and is what was used by all Swing components up and 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 architecture.

For special extra credit, you can use this style of implementation in your component. Be forewarned that this takes a bit more work, and is a bit more difficult to debug. It does, however, give you lots of flexibility in the future.

In order to use MVC, you need to do several things:

There's a bit of standard 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.

To get credit for this architectural style, note that you have to do more than just lump all your data structures into a "model" class. The model, UI, and component classes need to be cleanly separated from each other, communicating via ChangeEvents or PropertyChangeEvents. All event handling code needs to be delegated to the UI class.

If you correctly follow the MVC pattern we'll give up to +10 points on this project. Please let us know in the README if you've implemented this functionality.

Deliverable

This is an INDIVIDUAL assignment; while you may ask others for help on Java or Swing details, please build your application on your own.

Here are the details for how to turn in the assignment. We'll be using this structure for all of the turn-ins:

1. Create an executable JAR file named courier.jar that contains your runnable application. (See here for details on how to create executable JAR files.) We should be able to run your program by typing "java -jar courier.jar" on the command line; please be sure that your application runs correctly using ONLY this command, and that it doesn't require any additional CLASSPATH or other environment variables, no additional parameters, no classfiles or images located outside the JAR file, etc.

2. Create a new directory using your last name as the name of the directory.

3. Put the courier.jar file into the top level of this directory, and all of your source files into a "source" subdirectory inside this directory.

4. Put a README.txt file into the top level of this directory. This file should contain your name and email address, the version of Java you used (Java 1.6.x only, please) as well as any special info we might need to know about your program (let us know if you did extra credit, for example).

5. ZIP this directory and submit via T-Square (instructions are here).

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 JAR file and that your code will refer to them and load them properly when they're in this JAR file (see this page for some details on how to include and load images from within a JAR file).

Grading for this assignment, and future assignments, will roughly follow this breakdown: