|
The tape view
The tape-style view is also straightforward, though it uses a somewhat different internal architecture than does the keypad view (see Figure 12). As is the case with the keypad, the Tape
is a Jpanel
, but it contains only two fields: a JTextField
(input
: see Listing 4, line 30) into which you type your input and a Scrollable_JTextArea
(output
: see Listing 4, line 31), which stores a record of your data entry in a manner similar to the tape on a tape calculator. A log file (log
: see Listing 4, line 33) -- implemented using the Log
class discussed in January's Java Toolbox -- also stores the contents of this window. As is also the case with the keypad, any object interested in input from this view should call addActionListener(...)
(Listing 4, line 191) to register as an ActionListener
in the observers
list.
In contrast to the keypad view with its listener-based strategy, the tape view employs a Swing-model object to discover when the user enters characters. For reasons that are not clear to me, the JTextField
doesn't support the AWT TextField
's TextListener
(which was notified when new text was entered into the control). Instead, JTextField
supports an extremely complex set of listeners such as the CaretListener
, which is notified when the cursor position changes, but none of these let you easily find out when the text changes.
The easiest way to trap character-by-character text entry is to provide an implementation of the PlainDocument
-- the Swing model associated with the text objects -- and override its insertString()
method, which is called when a character is inserted into the string. I've done that in insertString(...)
(Listing 4, line 94), which works much like the keypad's Controller
object, accumulating alphanumeric strings until it encounters a non-alphanumeric character, but processing non-alphanumerics immediately. The Model
object calls process_newline()
(Listing 4, line 123) to dispatch the string off to the listeners by sending an actionPerformed()
message to the observers
multicaster (Listing 4, line 190).
An instance of the model is installed into the JTextArea
UI delegate when the object is created on line 30. (See Resources for more on the Swing architecture.)
Another main difference between the keypad and the tape view is that you can write to a tape view. (The messages are displayed on the tape.) There are two string overrides of write
: They are write(String,Color)
(Listing 4, line 142) and write(String)
(Listing 4, line 145). There's another for printing numbers (write(double)
: see Listing 4, line 154). Strings are just appended to the current line buffer (buffer
: see Listing 4, line 32) and flushed to the tape when a new line is encountered. Numbers are formatted consistently and then appended to the buffer.
|
The Parser itself
Now we can look at the Parser
class itself, in Listing 5.
You'll find the UML in Figure 13. Starting at the UML's bottom, the Parser
needs to talk to both the Keypad_viewer
and the Tape_viewer
in a consistent way. Though these classes are similar, the Tape
supports write()
methods and the Calculator_keypad
doesn't. I'm reluctant, in a situation such as this, to introduce bogus write()
methods to a class like Calculator_keypad
, which has no possible valid implementation of those methods. The only robust implementation is to throw an exception if the bogus method is called, but I don't like to move a compile-time error ("method not found") into a runtime error (the exception toss).
The problem can be solved with the Gang of Four Adapter pattern, in this case a Class Adapter. Viewer_ui
(Listing 5, line 264) defines a uniform interface to the two view classes. I then implement the interface in two adapters: The Keypad_viewer
(Listing 5, line 272) is a Calculator_keypad
(it extends Calculator_keypad
) that implements the Viewer_ui
, and Tape_viewer
(Listing 5, line 278) is a Tape
that implements the Viewer_ui
. The Keypad_viewer
implements empty write methods, since in the current context it isn't an error to throw away the output. The Tape_viewer
interestingly, has no methods at all: all the methods that the Viewer_ui
interface requires are inherited from the Tape
base class. Java, unlike C++, doesn't need us to supply any derived-class methods in this situation.
As we see at the center of Figure 13, the Parser
's visual proxy is an instance of Viewer
(Listing 5, line 288), which is a JPanel
that contains either a Calculator_keypad
or a Tape
, depending on which UI the user requests. The Viewer
's other roles include: installing the Interface menu we discussed earlier on the main-frame's menu bar and dynamically updating the menu bar with the menu items appropriate for the current view. Finally, the Parser
can write messages on the Viewer
, which relays the messages to the current contained view object.