ORIGINAL DRAFT
A lot of today’s applications require painted shapes to be filled in with various patterns. JPattern is a component which lets you pick patterns and color them as you see fit. To keep things flexible, this implementation is designed to offer both a compact combo box (Figure 1) and a more expansive list-based (Figure 2) display.
We’ll develop a ListCellRenderer implementation that you can use to display colors, textures or gradients - anything that implements the 2D Paint interface - in lists or combo boxes. We’ll also put together a couple of classes which define constants for more than a hundred named colors and a couple of dozen patterns. Finally, we’ll implement a convenient PatternPaint class which extends TexturePaint to make it easier to define common patterns.
Because the Java 2D API defines both Color and TexturePaint objects as implementations of the Paint interface, we can use the same mechanism to draw both patterns and color selections in a list or combo box. All we need to do is implement the ListCellRenderer interface and we’re done. Listing 1 shows how easy this is.
The PaintCellRenderer class extends JPanel and implements a paintComponent method that draws a rectangle, filling the drawing space while accounting for the insets. We pre-declare three LineBorder instances to draw the normal, selected and focused states we need to distinguish visually. The normal border is white, selected is black, and the focus border is blue, each two lines in thickness. We set a preferred size in the constructor and implement the only required method in ListCellRenderer, accounting for cases in which the value is not a Paint instance by ignoring it.
With the PaintCellRenderer in hand, we can now easily implement lists and combo boxes with the behavior we want. You’ll find two classes online (at www.java-pro.com) which we won’t bother listing here because they are trivial to implement. JPaintList extends JList and calls setCellRenderer to set our PaintCellRenderer in the constructor, passing a list of Paint elements to the superclass in the process. We implement a getSelectedPaint method that calls the JList getSelectedValue method to get the selected Paint object, casting it for convenience.
JPaintCombo extends the JComboBox class to accomplish the same thing, with two minor differences. JComboBox uses a setRenderer method instead of setCellRenderer. It also uses a method called getSelectedItem instead of getSelectedValue to get the current selection.
To keep things convenient we’ll implement two classes to store static final declarations for both colors and patterns. The ColorConstants class declares more than a hundred Color objects with constants that reflect their names. We follow the standard Java convention and use upper case names with underscores to separate words. You’ll find standard Color entries like BLACK, WHITE, RED, GREEN and BLUE, but you’ll also find more exotic colors like AQUAMARINE, CHARTREUSE, LAVENDER, SALMON and TURQUOISE, to name just a few.
The ColorConstants class also declares a few list constants to use with our JPaintList and JPaintCombo implementations, these include SHORT_LIST, MEDIUM_LIST and LONG_LIST, each of which declare a different number of entries, convenient in differing circumstances. We do the same basic things in the PatternConstants class, though we are dealing with PatternPaint objects instead of Color objects. We won’t bother listing ColorConstants or PatternConstants since they exist primarily for convenience, but we do need to take a look at the PatternPaint implementation.
Listing 2 shows the code for PatternPaint, which extends TexturePaint. Our objective is to make it easy to define bit maps that can be used as textures for drawing with the Paint interface. We’d like to define small images in terms of their width, height, foreground and background colors, and an integer array that map zero values for background colors and ones for the foreground colors. This makes it easy to define collections of bit values that can be used to draw the patterns we have in mind.
Most of the work is done in the createTexture method, which takes the arguments mentioned above and uses them to create a BufferedImage, which can then be used by the superclass. Notice that createTexture is declared static so that it can be called before the PatternPaint instance is fully created. The only other argument required by TexturePaint, besides an image, is a rectangle that describes the region to draw. In our case, this is the whole image, so we simply use the width and height to construct a suitable Rectangle object.
You’ll see four different constructors in the PatternPaint code. The last one calls createTexture to initialize the superclass and stores the various instance variables for later use. Each of the other three constructors call this one to do the work. The first constructor allows us to clone a PatternPaint object. The second provides a means to do the same thing, but gives us control over the foreground and background colors. This is important in our context since we plan to define patterns and their colors based on different user responses. The final constructor merely provides a default pair of colors, black for the foreground and white for the background.
PatternPaint implement the equals method so that we can recognize equivalent instances of a PatternPaint object. This is useful when we preset the list or combo box values to reflect standard pattern and color selections. Given that the PatternPaint object we want to select may not have the same color settings, we need to ignore colors in the comparison. The equals method, therefore, compares the bit mapped array value as well as the width and height values, but intentionally ignores the foreground and background values. We also implement the toString method to make debugging as easy as possible.
The PreviewPanel we use in JPattern is similar to the PaintCellRenderer, though it doesn’t need to implement the ListCellRenderer interface. Listing 3 shows the PreviewPanel class. Two points of difference are the use of a specific PatternPaint instance rather than the more generic Paint, and the use of the parent object’s background color to erase before drawing so that the component’s background color can be used for the pattern. As final precaution, there’s an if statement that returns to that paint commands are ignored when the pattern variable is set to null.
Listing 4 shows the main class in our JPattern component implementation. The constructor takes a single boolean argument that lets us define whether we’ll be using the combo box display or the larger list view. We declare two constants - COMBO and LIST - to reflect the two options. If the boolean value is true, we assume the compact combo box option and create the three combo boxes we’ll need in a panel placed at the top of the overall display. If the boolean value is false, we create three list objects we’ll be using instead. In each case, we register as either an ActionListener or a ListSelectionListener and respond to selections by setting the pattern attribute in our PreviewPanel.
The actionPerformed method handles ActionEvents. The valueChanged method handles ListSelectionEvents. Since these events are mutually exclusive in this component, we can handle them as discrete options. In each case, we call PreviewPanel’s setPattern, setForeground and setBackground methods, depending the on component we’re receiving an event from. The last thing we do in each case is call the repaint method to update the visual display.
One difference between the two views is that JList components are placed in a JScrollPane wrapper with the vertical scroll bars always on and the horizontal scroll bars always off. Two additional methods we need to implement are getPattern, which returns the PatternPaint setting in the PreviewPanel object, reflecting the currect color selections, and the setPattern method, which makes sure the combo box and list selections reflect the pattern, foreground and background colors.
You can listen for action events to respond to changes in the JPattern component. To support this, we implement three methods: addActionListener, removeActionListener and fireActionEvent. The first two simply add and remove an ActionListener instance to or from an listeners ArrayList. The fireActionEvent method is called whenever an event is received from one of the list or combo box objects, or when the setPattern method is called. When you receive an ActionEvent from JPattern, you can get the new PatternPaint object by calling the getPattern method. You can see how this works with the JPatternTest class, which you’ll also find online.
JPattern is a simple component but is likely to prove useful in a variety of conditions. The ColorConstants and PatternConstants classes are useful in an of themselves, especially when you want to keep handy Paint options available for 2D drawing. They are as easy to use as the standard Color specifiers (such as Color.black) in a Graphics 2D setPaint method. You’re also likely to find the JPaintCombo and JPaintList, along with the PaintCellRenderer, useful in cases where you want to allow users to pick colors, textures or gradients.