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 notebook application. The notepage component 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:

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 notebook application. The basic idea is that this notepage component serves as a container for text and drawn strokes, simulating the appearance of a page in a paper notebook. 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.

There are several things you have to do in order to use the MVC style in Swing:

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.

#2: 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 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 it gets drawn before any stuff that you render later in that route.

#3: Support for drawn strokes.

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). What this means is that 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.

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.

#4: Support for typed text.

Your component should also allow typed text to be entered onto the page. 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. 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: 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 page. This strategy will take care of word wrap and refilling via one implementation in the component's painting code.

#5: Integration with the rest of the application

Once you've implemented and debugged your notepage component, it's time to integrate it into the application you wrote for Homework #1. When your application starts up, it should create one notepage component that represents the first (and only) page in your notebook. This component 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 page, although you can implement this if you want to.)

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 notepage components so that you can keep track of them.

Selecting Previous and Next should move between the pages.

Extra Credit

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

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 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 page 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 notepage components 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 "java NotebookApp" when run from inside your project directory (described below). 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 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 we migiht need in order to run your code.

4. 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 depencence 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 inside the directory that's created when I unZIP your code.

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

Please let the TA or me know if you have any questions.