Using the Swing Text Package
Its Author Explains
Exactly How It's Designed
By Tim Prinzing
We
created the Swing text package with an aggressive set of goals in
mind. Its design is based on a fundamental principle that text-oriented
operations should be easy.
But in this case, easy does not mean the same thing as simple.
Indeed, the Swing text package is admittedly somewhat complex --
as a result of original goals.
The rationale is that even though the underlying package is complex,
a set of simple wrappers has been layered over its complex capabilities
to simplify common tasks.
This article provides an overview of how the Swing text package
works. It covers the following major topics:

Five important features are built into the
Swing text-component package:
- Model-view separation.
- A pluggable look-and-feel design.
- Scalability.
- Extensibility.
- A blurring of text-component boundaries.
In this section, these five capabilities
of the Swing component package are examined in more detail.
Model-view separation
Swing's text components, like all Swing components, are
based on an architecture that features model-view separation.
(For more about this topic, see the "Swing
Architecture Overview" article
in this issue.)
An architecture based on model-view separation
is not something that one ordinarily sees in a text component. But
a model-view framework can actually be very useful in a text component
-- not only for the usual reasons (such as simplifying testing,
improving accessibility, and so on), but also because in a text
component, multiple views are even more likely to be required.
Printed views and screen views, for example,
are usually formatted differently when text is displayed (unless
the screen view is a WYSIWYG view) -- and this requirement is easily
met when printed and screen views are implemented as separate views.
Text components
and Swing's PL&F design
Another feature of all Swing components --
including the Swing text components -- is Swing's pluggable look-and-feel
(PL&F) design. In most Swing components, PL&F simply
means that the component delegates its view and control capabilities
to another object (specifically, a ComponentUI object). But implementing
PL&F is not that simple in a text component -- especially if
the designer of the component is trying to reduce the amount of
code needed for different applications of text. (For more about
the relationship between the Swing text components and Swing's PL&F
capabilities, see the "Pluggable look and feel" section
later in this article.)
Scalability
Because of the layered construction of the
Swing text package, users of Swing's text components don't have
to pay for capabilities that they don't need to use. In a word,
Swing's text components are scalable.
One difficult problem that designers of text
packages often face is that people can find so many different kinds
of things to do with text. As a result, a developer is often forced
to use an excessively large set of features to create a component
that handles a particular set of requirements -- or to create some
kind of homemade text support from scratch.
For example, many people try to hook into
word processors when they really need only a subset of the capabilities
of a word processor. Others decide to write their own text-support
mechanisms because existing components dont have the capabilities
they need and they arent willing to pay the cost of using
a larger component that vastly exceeds their requirements.
Extensibility
All too often, a text package hands a developer
a particular set of text-support capabilities and then offers the
developer no way to modify them. A better idea would be to offer
the developer a way to extend whatever set of capabilities has been
made available. In other words, a text package should be extensible.
The emacs editor owes its popularity to its extensibility. Similarly,
because of its expressive modeling capabilities, SGML has also become
a popular text package for designing electronic documents.
Because extensibility is such an important
feature in a well-designed text package, an important design goal
for the Swing text package was to provide a set of classes that
could easily be extended to meet different kinds of needs.
Blurring text-component
boundaries
As computers present more and more information
in richer and richer ways, the boundaries between text components
and other kinds of components begin to blur. In today's graphics-intensive
GUI packages, components are often embedded in text -- and text
is often embedded in components. Because text components and other
kinds of components blend into each other in a GUI environment,
the boundaries between them should be difficult to detect -- particularly
if the user is allowed to interact with their mostly text-based
data.
To perform well
in today's GUI environments, text components should be versatile
in other ways, too. They should not have to be opaque. They should
not have to have a rectangular shape. And finally, a text component
should not have to display all its text in the same region.
For example, a developer should be able to
use the support supplied by a text editor with minimal modification,
without having to devise some kind of homemade support that is written
from scratch. When developers are forced to create their own functionalities
from the ground up, they are not likely to provide proper support
for other Swing capabilities, such as pluggable look-and-feel requirements,
full accessibility support, internationalization, and so on. Consequently,
they generally wind up with non-scalable, non-extensible text editing
mechanisms.

To implement the five important features
mentioned in the preceding section, the Swing text package provides
a comprehensive set of text-related classes to support various text-related
activities. These classes get combined into Swing's text components,
with the pieces replaceable with alternative implementations. This
arrangement adds a bit of complexity to the Swing text package.
But at the same time, it makes Swing's text components versatile
and more likely to meet developers' needs, reducing the work of
most Swing developers.
The text classes provided in the Swing package
subclass from a root class named JTextComponentapi
(see illustration). Here's how the Swing text-class hierarchy works:
The JTextFieldapi
and JPasswordFieldapi
components provide migration support from java.awt.TextField,
and the JTextAreaapi
component provides migration support from java.awt.TextArea.
The JTextPaneapi
component provides a simple styled text component that allows the
developer to mark up the text with style features, embed pictures
and components into the text flow, and assign logical styles to
the paragraphs.
The JEditorPaneapi
component is the extensible component that provides a convenient
way to morph into different kinds of support. JEditorPane provides
a registration mechanism that can easily extend the set of text
types that it knows how to deal with. By default, the JEditorPane
registration mechanism provides some level of support for plain
text, HTML, and RTF.
Delegation of functionality
Swing's text components delegate most all
of their duties to other objects. Consequently, you usually can't
directly extend a text component in order to change its behavior.
Instead, you must change the objects that are being delegated to.
That's how scalability is built into Swing's text components. But
it's also a source of complexity, because the path of delegation
is not immediately obvious from the source code that implements
the component.

Swing's text components export most of their
capabilities as commands. These commands include not only
the familiar Cut, Copy, and Paste operations
that are generally found on the menus of word processors and other
kinds of text editors, but also caret navigation, selection operations,
and whatever other editing operations that can be performed by the
editor .
In
the Swing text API, commands are implementations of the Swing Action
interface. Commands are typically bound to keyboard actions, and
can also be bound to other UI mechanisms such as toolbar buttons,
menu items, and the mouse.
Before a command can execute, the command
must determine its target component -- that is, the component
on which it is to operate. (In most cases, multiple components can
share a single command. Swing's Action interface allows multiple
components to share a command because it's typically more efficient
for multiple components to share a command than for a single command
to service just one target. Action commands are the topic of an
article scheduled for publication in an upcoming issue of The Swing
Connection.)
The Swing text package implements text commands
as subclasses of the TextAction class. When an application
executes a text-related command, the TextAction class tries to find
a text component to operate on by examining the value returned by
a method named ActionEvent.getSource(). If this value is an instance of a JTextComponent, the
component becomes the target of the operation. The action may also
attempt to decode additional information needed from the command
string associated with the ActionEvent.
If this attempt fails, the TextAction class
tries to target the currently (or most recently) focused component.
If that attempt also fails, no action is taken.

Because most of the manipulations performed
on text components are issued from a keyboard, a text package should
make it easy to bind keyboard events to the components' capabilities.
To accomplish this goal, Swing's text components support keymaps,
which map keyboard events to commands.
Swing does not absolutely require that you
use keymaps in text operations -- but text operations that don't
make use of keymaps don't play very well in Swing's pluggable look-and-feel
environment.
To understand how Swing text components handle
keymapping, it helps to have a fundamental understanding of the
following points:
- What is a keymap?
- What are KeyEvents and how are they propagated?
- What steps are involved in translating a key event?
- How do keymaps resolve?
In this section, I'll answer each of those questions.
What is a keymap?
A keymap is a map of key events to some kind of command that should
be executed when that event is received. Every
text component has a keymap as a bound property. Multiple components
can share the same keymap, because a keymap has no tie to any particular
component.
Within the Swing API, keymaps are implemented using the Keymapapi
interface. The JTextComponent class has a default Keymap implementation,
as well as some static methods for manipulating keymaps. The JTextComponent
also provides a default registry for keymaps.
How keymaps work in Swing
The following diagram represents a possible keymap. As the diagram
illustrates, Swing uses a hash table to map keystrokes to commands
such as Cut, Copy, and Paste. Keymaps also
bind keys to other kinds of actions, such as moving the caret backward
or forward.
Example: Using
a keymap
The
following code snippet illustrates the use of a keymap such as
the one shown above.
The example shows how a Swing application can use a keymap to
perform actions such as those shown in the diagram.
static final JTextComponent.KeyBinding[]
defaultBindings = {
new JTextComponent.KeyBinding(
KeyStroke.getKeyStroke(KeyEvent.VK_C,
InputEvent.CTRL_MASK),
DefaultEditorKit.copyAction),
new JTextComponent.KeyBinding(
KeyStroke.getKeyStroke
(KeyEvent.VK_V, InputEvent.CTRL_MASK),
DefaultEditorKit.pasteAction),
new JTextComponent.KeyBinding(
KeyStroke.getKeyStroke
(KeyEvent.VK_X, InputEvent.CTRL_MASK),
DefaultEditorKit.cutAction),
};
JTextComponent c = new JTextPane();
Keymap k = c.getKeymap();
JTextComponent.loadKeymap(k,
defaultBindings, c.getActions());
How are KeyEvents propagated?
In Swing, as in pre-Swing versions of the Abstract Windowing Toolkit
(AWT), KeyEvents are generated as keys are pressed and released.
Actually, there are three different kinds of key events:
- A keyTyped event, which is generated as soon as the user
types the keys necessary to generate a Unicode character into
a component being listened to by a key listener. This may result
from multiple keys being pressed and released.
- A keyPressed event, which is dispatched as soon as the
user presses a key on the keyboard.
- A keyReleased event, which is generated as soon as the
user releases a key on the keyboard.
For more details about each of these kinds of events, and for an
explanation of how key listeners work, refer to a good basic AWT
text. One such text is The
Java Tutorial.
Key-event propagation step by step
To learn more about how keymaps work in Swing,
it might be helpful to take a look at how the propagation of a KeyEvent
works in a text component. This subsection presents a step-by-step
breakdown of a KeyEvent-propagation operation.
Once a KeyEvent has been delivered to a component
and has reached the processKeyEvent() method, it goes by default to JComponent, which then
causes the following things to happen:
- FocusManager:
First, the currently active FocusManager gets a chance to take
the event.
- Listeners:
If the event has not been used by the active FocusManager, it
is sent to registered KeyEventListeners, which can process the
key event. This operation works the same way it did with native
java.awt text components.
- Keymapping:
If the event has still not been consumed, JComponent gives the
component itself a chance to grab the event. JTextComponent uses
this operation to feed Swing's keymap-handling functionality.
The KeyEvent (illustrated earlier
in this section) is exchanged with a
Swing KeyStroke object, which is then used as the key for a lookup
in the component's current keymap. If an action can be found,
it is fired, and the event is consumed.
The one action that is not easy to map from keystrokes is the
keyTypedEvent. When a keyTypedEvent is detected, it is sent to
a special Action in the keymap that is then used as the default
handler. By default, an attempt is made during this operation
to filter out unprintable characters and inconsistencies from
the native level.
- Accelerators:
If there has been no keymap handling of the event at this point,
the event is passed on to Swing's keyboard UI logic, which handles
special operations such as the operation of accelerators.
How multiple keystrokes are bound to an Action
Because a KeyStroke is the key used on a
Keymap and a KeyStroke corresponds to a single KeyEvent, you might
wonder how multiple keystrokes can be bound to an action. The answer
is that successive keyboard actions can shift the keymap on the
target text component. For example, if the key sequence Control-X
Control-S is used to fire an action to save a file, the Control-X
action can set a new keymap on the component and the new map can
have a Save command bound to Control-S.
Translating a KeyEvent
When a key event has been detected, it must
be translated into an action. This flowchart shows how the Swing
text package translates a key event into an action:
How keymaps resolve
A useful characteristic of keymaps is that
they resolve hierarchically, as shown in the following diagram. If
a binding doesnt resolve locally and a parent keymap is registered,
an attempt is made to find a binding in the parent, and so on. This
behavior is useful in creating keymaps for the various look-and-feel
definitions, and is what is practiced by the BasicTextUI-based implementations.
There's one keymap -- called JTextComponent.DEFAULT_KEYMAP -- that is the parent to all BasicTextUI keymaps. It
holds common definitions that are shared for all text components,
no matter what look-and-feel choice is selected. Changing this
keymap affects all text components unless a particular binding is
overridden by the current look-and-feel (TextUI implementation).
Each TextUI implementation that is based upon BasicTextUI creates
a keymap to represent the set of key bindings that is appropriate
for the corresponding type of look-and-feel definition. This
operation is carried out by the BasicTextUI.createKeymap()
method. The createKeymap()
method tries to find a keymap for the appropriate UI. If it can't
find one, it creates one by calling JTextComponent.loadKeymap(),
using the set of bindings that has been fetched from the UIManager.
Each type of look-and-feel definition can then be easily located
by name and altered if desired. By default, the name of the
keymap for a particular look-and-feel is the appropriate class name,
without the package prefix.
You can create a component-specific definition without losing the
look-and-feel definition that you are using. To do that, simply
create a keymap that resolves into a look-and-feel keymap. By
default, a keymap is shared across all instances of a given type
of text component for a particular look-and-feel -- for example,
JTextField shares XXXTextFieldUI
keymaps, JTextArea shares XXXTextAreaUI
keymaps, and so on.
The following code fragment shows how an application can set a
private keymap for a component that resolves into the current look-and-feel
for that kind of component (in this case, a JTextField):
JTextField field = new JTextField();
Keymap laf = field.getKeymap();
Keymap myMap = JTextComponent.addKeymap(null, laf);
field.setKeymap(myMap);
If you like, of course you can set a standalone keymap that doesnt
resolve through any other maps. To set a keymap in that way, just
don't specify a parent keymap.

By themselves, Swing's text components don't
directly support undo-redo capabilities as that would tie them to
a specific undo-redo policy. But they do support undo/redo indirectly
by supplying their support in Swing's text-component model. Because
the view portion of a text component reflects the state of the component's
model, changing the model with undo/redo causes the views to follow
automatically.
Documents and undo-redo actions
An object called a Document, defined
in Swing's Document interfaceapi,
is the model for text components. (In Swing, a Document is a text
container that supports editing and provides notification of changes.)
A document can, of course, be shared by multiple components if desired.
A document communicates with its views via a DocumentEvent, as shown in the following diagram. The illustration
shows how a document (represented as a blue rectangle at the bottom
of the drawing) might communicate with two different JTextComponent
objects, each of which contains a view. (In Swing, a component's
view is interior to the component itself.)
Referring to this diagram, suppose that the component shown on
the left mutates the document object represented by the blue rectangle.
The document responds by dispatching a DocumentEvent to both component
views and sends an UndoableEditEvent to the listening logic,
which maintains a history buffer.
Now suppose that the component shown on the right mutates the same
document. Again, the document dispatches a DocumentEvent to both
component views and sends an UndoableEditEvent to the listening
logic that is maintaining the history buffer.
If the history buffer is then rolled back, a DocumentEvent is sent
to both views, causing both of them to reflect the undone mutation
to the document (that is, the removal of the right component's mutation).
If the history buffer again rolls back another change, another DocumentEvent
is sent to both views, causing them to reflect the undone mutation
to the document -- that is, the removal of the left component's
mutation.

As noted earlier, Swing's text components --
like all other Swing components -- have a pluggable look-and-feel
design. But it's more difficult to build pluggable look-and-feel
capabilities into text components than it is to provide standard
components with pluggable look-and-feel capabilities. (In fact,
I'm unaware of any other PL&F system that extends support pluggable
look-and-feel support to text components.)
The problem is that there's a certain tension
between the content represented inside a PL&F text component
and the user interface that determines the component's overall look
and feel. The user interface is likely to dictate some elements
of the component's behavior and appearance, such as its set of key
bindings (largely a matter of feel), its set of colors, its interaction-related
behavior when it is selected, and so on. These features and functionalities
are somewhat orthogonal to the look of the component, which is largely
determined by the kind of content that it represents.
Tension in text components
The relationship between a component's PL&F
features and its user-interface object can be expressed in a graph
like the one shown below. In the diagram, the vertical axis represents
a component's user interface, and the horizontal axis represents
the component's data content.When a text component is created, its
location on the graph can be specified using a pair of x, y coordinates.
Introducing
the EditorKit
To deal with the tension represented by the
vertical and horizontal axes of the preceding graph, the standard
UI delegate used by other kinds of Swing components is not sufficient.
A text component needs a second delegate to handle the kind of content
that it displays.
In the Swing text-component package, this
second delegate is an implementation of a class named EditorKit,
as illustrated in the following diagram. The EditorKit class is
described in more detail under the subheading "Creating an EditorKit"
later in this section.
The TextUI object
As the diagram shows, the UIManager used
by a text component is called a TextUI object. When a TextUI
object is installed into a JTextComponent, its duties are to make
sure that a default set of properties for the type of UI has been
plugged into the JTextComponent. The duties of a TextUI object consist
of at least the following:
- It sets the various colors used in a text
component, such as the caret color, the selection background color,
the selection foreground color, and the like.
- It installs a caret implementation. (In
a text component, the caret is an object used for navigating through
the text component. It is responsible for the component's selection
policy).
- It installs a suitable set of key bindings
(a Keymap) for the UI.
- It builds a view of the model. In simple
components, this task can be performed directly. In more complex
components it can be delegated to an EditorKit.
Creating an EditorKit
The EditorKit, introduced previously, is the
primary mechanism for extending Swing text components. Architecturally,
the EditorKit is a bundling of the capabilities needed by a text
component for dealing with a particular type of content.
To create an EditorKit, you register it with
a JEditorPaneapi
object. Swing's JEditorPane component has a registry for EditorKit
types that can be used to represent all types of text for which
there are EditorKit implementations.
The EditorKit has the following responsibilities:
- It creates a model (see next heading). Because Swing's EditorKit implementation knows what
kind of content it is describing, it can provide a model that
has a particular set of policies for describing that kind of content.
- It provides a factory for building views. Rather than providing a view directly,
the EditorKit provides a way of creating views. This strategy
allows the developer to customize the view further by extending
the kit to produce a different factory. If the developer desires,
the EditorKit can build different portions of the view using different
objects.
- It provides commands for editing the content. Because the EditorKit encapsulates
functionality that knows how to deal with a particular content
type, it knows what kinds of operations can be performed on that
particular content type.
- It saves data to a stream. Because different kinds of content can have their
own individual storage formats, the EditorKit is an ideal mechanism
for streaming data.
- It reads from a stream. Again, because different kinds of content can have
their own individual storage formats, the EditorKit is an ideal
mechanism for streaming data.

At the simplest level, text can be modeled
as a linear sequence of characters. To support internationalization,
the Swing text model uses unicode characters.
The sequence of characters displayed in a
text component is generally referred to as the component's content.
The interface for the object that holds the content is the Document
interfaceapi.
When a change is made to the content, the component's view (or views)
needs to be notified of the change.
Because the content of a text component can
be quite large and cannot be efficiently deduced, this notification
must include fairly detailed information about whatever change has
taken place. The interface that Swing uses to describe text-change
information is called a DocumentEvent (introduced earlier).
Architecturally, the DocumentEvent used by
text components follows the event model guidelines that are specified
for JavaBeansTM. A significant
feature of the JavaBeans event model is that once an event notification
is dispatched, all listeners must be notified before any further
mutations occur to the source of the event. The listener for a change
to the text model must implement the DocumentListener interface,
in the typical style of bean event handling.
The coordinate system
To convey information about changes to a
text view (or text views) in a proper manner, a document must have
a coordinate system, as shown in the following diagram:
As the diagram shows, a location in a text
document can be referred to as a position, or an offset.
This position is zero-based. It can be used to specify the character's
position between any other two characters.
For example, if the content of a document
is the sequence "The quick brown fox," as shown in the
preceding diagram, the location just before the word "The"
is 0, and the location after the word "The" and before
the whitespace that follows it is 3. The entire sequence of characters
in the sequence "The" is called a range.
Positions
and ranges
It would be natural and convenient to implement the
position and range of a sequence of characters as objects, but
this strategy caused problems with the JFC 1.1 garbage collector
because it caused a large number of temporary objects to be created.
This situation is dealt with in the 1.2 garbage collector, but
causes API problems in JFC 1.1. Consequently, the position value
in a text component is currently expressed as type int,
and the range value is expressed as a pair of ints.
With a coordinate system in place, we can
accurately place a caret in the model of a text component, and we
can accurately describe changes in the locations of characters in
text. (In Swing, the caret -- introduced earlier in this article -- is a view object, but refers to a
place in the model).
To make a change as simple as inserting or
removing content, you can easily track your changes using the text
package's coordinate system. In the simplest case, a DocumentEvent
carries information about insertions into and removals from the
sequence of content.
To provide consistency, this information
is provided in the form of an offset and a length.
To obtain the range of a mutation, you simply add the length of
the change to its offset. To ensure reasonable behavior in the face
of concurrency, the event associated with a mutation is dispatched
after the mutation has occurred. This means that by the time
a notification of an insertion or a removal is dispatched, the coordinate
system has already been updated.
When an insertion takes place, the effects
of this sequence are obvious. In a removal, however, the offset
is the same in the "before" and "after" coordinate
systems, and the "offset + length" value that specifies
the end of the range before the removal effectively represents the
offset after the removal.
But text is rarely represented simply as
featureless content. Rather, text typically has some sort of structure
associated with it, and there are varying levels of support for
such structures. At the simplest level, the model of a text component
provides a position that tracks changes as a document is altered.
The method to fetch a mark that specifies the position of a sequence
of text is Document.createPosition().
The Document.createPosition() method allows an application to mark a place in a sequence
of character content. This mark can then be used to tracks change
as insertions and removals are made in the content. The policy is
that insertions always occur prior to the current position (the
most common case) unless the insertion location is zero, in which
case the insertion is forced to a position that follows the original
position.
For a removal, the end of the removal range
is collapsed down to the start of the range, and any marks in the
removal range are collapsed down to the start of the range.

The Element interface
Structure imposed
upon the sequence of content is typically associated with some additional
information. To model the structure in this way, Swing uses an object
called an Element (defined in the Element interfaceapi)
to represent a fragment of structure.
What
is an element?
As
noted earlier in this article, a Document is a text container
that supports editing and provides notification of changes. In
other words, it serves as the model in a model-view-client (MVC)
relationship. Support is provided to mark up the text in a document
with a structure that tracks changes. The unit of structure used
in this construct is called an Elementapi.
In Swing text components, views are typically
built from an element structure. An arbitrary set of attributes
can be associated with an element. The interface itself is intended
to be free of any policy for structure that is provided, as the
nature of the document structure should be determined by the implementation.
To learn More how the text package's Element
structure works, see "Formatting
a text component" later in this
article.
Using an element
An implementation of an element uses the
same marks we have been discussing to track changes as the document
is altered. An element is intended to be representative of an SGML
element for a document that has been normalized with entities expanded.
Associated with the piece of structure is a set of attributes that
are typically used to convey some kind of information to views of
the model for use in rendering. The AttributeSet is effectively
a map of name-value pairs that use an open-ended set of possible
keys so that developers can effectively model any custom attributes
they want. There is no theoretical limit to the modeling capabilities,
which might be as simple as a single element (a text field) to a
sophisticated proprietary document structure.

The AbstractDocument
The Swing text package provides an abstract
implementation of the Document interface. This implementation is
called the AbstractDocument. In the Swing text package, the
AbstractDocument provides a number of useful features for modeling
text-oriented documents.
A unique feature of the AbstractDocument
is that the text-content storage is separate from the structural
modeling of the document. This feature allows a developer to establish
how a document is stored independently of what the document is modeling.
For example, an application can create a model structure designed
to support an HTML document and then independently plug in storage
mechanisms based upon different algorithms. (A more complex structure
that would be highly undesirable to have to re-implement).
These text content
storage plug-ins must implement the Content (AbstractDocument.Content)
interface. This interface requires that the following three kinds
of behavior be provided:
- General editing features: Text insertion, text removal, and text retrieval.
To enable text to be retrieved without copying, the Content implementation
may be able to use an object called a Segment to gain access
to a character sequence without having to copy the characters
into a new array. A Segment can make text operations more efficient
by providing access to the actual locations of existing character
arrays. This capability makes it unnecessary to copy existing
character sequences for rendering and search operations.
- Support for marks that represent a position between characters in storage,
that tracks changes made to the text storage. These are provided
as an implementation of the Position interface.
- Provide objects that represent the changes made to the storage so
that they can efficiently participate in undo/redo operations.
These are provided as an implementation of the UndoableEdit interface.
The Swing text package currently provides
two implementations of the Content interface: the StringContent
class and the GapContent class.
The StringContent class is a brute-force
implementation that uses a simple character array for storage. It
is useful for handling content such as text fields that are small.
The GapContent class provides storage
using a character array with a gap in it. This gap is moved to the
location being mutated if it is not already there. The gap buffer
is an algorithm used by emacs. It takes advantage of the fact that
most mutations to text storage occur in the same region of the overall
storage. The cost of keeping large numbers of marks up to date is
typically cheap with this algorithm (This depends upon how far the
gap is moved).
Some other interesting implementations of
this interface might include:
- Piece Table Algorithm.
- Memory-Mapped File implementation.
- Implementation using another Document
as its storage, for embedded documents.
- Database Storage.

A very important part of the text package
is the View class. As its name suggests, the View class represents
a view of the text model. It is this class that is responsible for
the look of the text component.
The view is not designed to be some kind
of new class that is difficult to learn, but rather behaves much
like a component. In fact, our original intent was for the View
to be a lightweight component, but we abandoned that idea for several
reasons:
- We barely had time to build lightweight
component support into the 1.1 version of the JDK. There simply
wasn't time to lighten up the component further, and as it currently
stands, it's much too heavy for representing text.
- The layout semantics aren't quite right
for text, and we feared that changing the current layout semantics
of component would break existing applications.
- The component API uses integers, but in
JDK 1.2 one can use floating point device independent coordinates.
An API that works in both 1.1 and 1.2 would be convenient for
minimizing transition difficulties.
By default, a view is very light. It contains
a reference to the parent view from which it can fetch many things
without holding state, and it contains a reference to a portion
of the model (the Element). A view does not have to exactly represent
an element in the model; that is simply a typical, and therefore
convenient, mapping. Alternatively, a view can maintain a couple
of Position objects to keep track of its location in the model (that
is, to represent a fragment of an element). This is typically the
result of formatting where views have been broken down into pieces.
The fact that views are closely related to Elements makes it easier
to use factories to produce views. In turn, using factories to produce
views makes it easier to keep track of the
various parts of views that must be changed when their corresponding
models change. (A corollary is that a simple view can represent
an Element directly, while a complex view generally does not.)
A view has the following responsibilities:
- It participates in layout. The View class has a setSize() method that works like a combination of the Component
class's doLayout() and setSize() methods. The View class also
has a preferenceChanged() method that works like the Component class's invalidate() method, with two exceptions: One can invalidate just
one axis, and the child requesting the change is identified.
- It renders a portion of the model. This is an operation that takes place in the paint
method, which is pretty much like a component paint method, except
for one difference: The view gets its allocation in the parent
at paint time, so it must be prepared to redo a layout if the
allocated area is different from what it is prepared to deal with.
- It translates between the model and
view coordinate systems. Because
view objects are produced from a factory and therefore cannot
necessarily be counted upon to be arranged in a particular pattern,
one must be able to perform translation to properly locate spatial
representation of the model. There are two methods for doing this:
modelToView() and viewToModel().
- It responds to changes from the model.
If the overall view is represented by many pieces (which is the
best situation if one wants to be able to change the view and
write the least amount of new code), it is impractical to have
a huge number of DocumentListeners. If each view listened to the
model, only a few would actually be interested in the changes
that are broadcast at any given time. Because the model has no
knowledge of views, it has no way to filter the broadcasting of
change information. Instead, the view hierarchy itself is responsible
for propagating the change information. At any level in the view
hierarchy, that view knows enough about its children to be able
distribute the change information further in the best way possible.
Changes are therefore broadcast starting from the root of the
view hierarchy.

A traditional text system has a monolithic
formatter with a predefined set of features, where nothing can be
added to or removed from the set of capabilities by the user of
the formatter. This is not a very scalable system, because it is
impossible to predict all possible requirements. Consequently, developers
must fatten up their applications with functionalities they may
not need in order to get some subset of the features provided.
By representing a view as a set of objects
instead of a monolithic formatter (which would effectively be a
single view), a number of benefits result. First, the problem of
formatting is broken down into pieces, which makes the formatting
process more manageable. Additionally, representing a view as a
set of objects makes it possible to use bits and pieces of formatting
behavior in slightly altered ways, which can lead to a substantial
reduction in code size.
Most important, objects can be replaced with
alternative objects.
This means that the formatting process can be openly extended and
can be made scalable to any set of requirements.
Formatting a text
component
Because of Swing's separation between model
and view, Swing makes it easy to extend the formatting process.
One simply adds new properties to the model, and replaces the objects
in the view with objects that know how to render the added properties.
For example, suppose you wanted to add wavy underlines to Swing's
formatting capabilities -- something that is not supported by Swing's
default set of capabilities.
To add this capability to the Swing set,
you could add a new attribute type (let's call it wavy underlines)
and apply that capability to the Document model. Then you could
extend the view representing character level attributes (LabelView,
for example) to render wavy underlines if your wavy underline
attribute is set.
To illustrate how this operation could be
performed, let's take a look at how a styled view might be formatted
by default. We will examine a simple styled document, which is what
one would get by default from the JTextPane component. The model
is DefaultStyledDocument, which by default has an element structure
with paragraph elements containing elements that model "style
runs" inside the paragraph. Here's a diagram:
The sets of attributes we are using are attached
to the elements that represent the structure of the document --
which, in this case, is fairly simple. DefaultStyledDocument produces
a relationship among the AttributeSet definitions that can be diagrammed
as follows:

The Element tree
The leaves of the Element tree hold the style
runs for character level styles (such as bold, italic, and so on)
and resolve into the paragraph elements. The paragraph elements
hold the attributes that determine the characteristics of how the
paragraphs should be viewed, and they resolve into a set of logical
styles. The logical styles are simply convenient collections of
attributes.
The well-known values for these attributes
are defined in the StyleConstants class.
From the model
that we have illustrated, we can build a view that is a tree of
objects extending the View class. By default, this is a set of objects
based upon BoxView, ParagraphView, LabelView, ComponentView, and
IconView. These are all implementations of the View class
The root element produces a vertical BoxView,
and the paragraph element produces a ParagraphView that formats
the child views into rows which fit within the span of the paragraph.
In this case, the child views are instances of the LabelView class,
which is the view normally used to represent character styles (that
is, the character styles defined in StyleConstants).
It then becomes
easy to add a wavy underline character attribute to the existing
behavior of the text package. Because the ParagraphView is performing
layout on View objects and has no knowledge of what the object actually
is, the objects we're working with are easily replaced and with
a very small amount of code.
To create our new attribute, we simply define
a wavy underline attribute in a manner similar to the existing character
attributes found in the StyleConstants class. These could be applied
to the model in the same manner as any existing attributes are applied.
To make the attribute viewable, we can implement
a new view that subclasses LabelView. This new view can delegate
its rendering to the superclass. Then, if it sees the wavy underline
attribute present in the AttributeSet of the Element in the model
it is responsible for, it can render the wavy underline as well.
Swing team member Tim Prinzing is the
author of the Swing text package.
|