[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Swing Text

Using the Swing Text Package
Its Author Explains Exactly How It's Designed

By Tim Prinzing

Quill and scrollWe 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:


Features of the Swing text package

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

Link to Editor Kit article JavaEditorKit link 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 don’t have the capabilities they need and they aren’t 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.


The Swing text-component hierarchy

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.

Tree diagram of text classes

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.


Text commands

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 .

Text commands graphicIn 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.

Editor/Actions image

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.


Keymaps

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.

Keymap graphic


Example: Using a keymap

Source code iconThe 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:

  1. FocusManager: First, the currently active FocusManager gets a chance to take the event.

  2. 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.

  3. 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.

  4. 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:

Key-translation flow chart


How keymaps resolve

A useful characteristic of keymaps is that they resolve hierarchically, as shown in the following diagram. If a binding doesn’t 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.

Keymap resolution graphic

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 doesn’t resolve through any other maps. To set a keymap in that way, just don't specify a parent keymap.


Undo-redo support

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.)

Undo-redo graphic

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.


Pluggable look and feel

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.

Text-component tension graphic


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.

EditorKit graphic


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:

  1. 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.

  2. 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).
  3. It installs a suitable set of key bindings (a Keymap) for the UI.

  4. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

The text-component model

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:

Coordinate system graphic

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

Note iconIt 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.

Text insert-removal graphic

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?

Note iconAs 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.

Document-chapter-paragraph graphic


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:

  1. 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.

  2. 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.

  3. 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.

The View class

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.

Extensible formatting

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:

Default style-view formatting graphic

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:

Attribute-set definitions graphic


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).

Box view/paragraph view/label view graphic

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.

[an error occurred while processing this directive]