Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 19. (Advanced topics) Inside Text Components. Easy for reading, Click here!

Custom Search
Swing Chapter 19. (Advanced topics) Inside Text Components. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 1/2 



Next Page (2/2) Next Page

Chapter 19. Inside Text Components

Subpages: 1. Text package overview
2. Best Date and time editor

In this chapter:

  •    Text package overview
  •    Date and time editor by David M. Karr

19.1  Text package overview

A truly exhaustive discussion of the text package is beyond the scope of this book. However, in this chapter we hope to provide enough information about text components, and their underlying constituents, to leave you with a solid understanding of their inner workings. Picking up where chapter 11 left off, we continue our discussion of the most significant aspects of the text package classes and interfaces. This chapter concludes with an example of a custom text field used for several variations of date and time selection. In the next chapter, we continue our study of text components with the development of a full-featured word processor application. The examples in chapter 20 demonstrate practical applications of many of the complex topics covered in this chapter.

19.1.1  More about JTextComponent

abstract class javax.swing.text.JTextComponent

Associated with each JTextComponent is a set of Actions which are normally bound to specific KeyStrokes (see 2.13) and managed in a hierarchically resolving set of Keymaps (see 19.1.23). We can retrieve a text component's Actions as an array with the getActions() method. We can retrieve and assign a new Keymap with getKeymap() and setKeymap() respectively.

All text components share a set of default Actions. Each of these Actions are instances of TextAction by default (see 19.1.24). JTextComponent provides a private static EditorKit (see 19.1.25) which consists of a set of four pre-built TextActions shared by all text components through the use of a default Keymap instance (see 19.1.26).

JTextComponent maintains a private reference to the text component that most recently had the keyboard focus. TextActions are designed to take advantage of this, and each TextAction will operate on this component when invoked in the event that the source of the invoking event is not a text component.

Document content is structured hierarchically by Element implementations (see 19.1.9). Each Element maintains a set of attributes encapsulated in implementations of the AttributeSet interface (see 19.1.12). Many Elements also contain one or more child Elements. Attributes that apply to one element also apply to all child Elements, but not vice versa. Each Element has an associated start and end Position (see 19.1.6).

AttributeSets can be applied manually to a region of text. However, it is often more convenient to use Styles (see 19.1.14). Styles are AttributeSet implementations that we do not instantiate directly. Rather, Styles are created and maintained by instances of StyleContext (see 19.1.16), and each Style has an associated name allowing easy reference. StyleContext also provides a means for sharing AttributeSets across a document or possibly multiple documents, and is particularly useful in large documents.

The cursor of a text component is defined by implementations of the Caret interface (see 19.1.19). We can retrieve the current Caret with getCaret(), and assign a new one with setCaret(). A text component's Caret is instantiated (but not maintained) by its UI delegate. So when the L&F of a particular text component changes, the Caret in use will also change. JTextComponent supports the addition of CaretListeners that will receive CaretEvents whenever the position of the Caret changes.

Text components also support an arbitrary number of highlights through implementations of the Highlighter interface (see 19.1.17). Highlighters are most often used to indicate a specific selection. They can also be used for many other things such as marking new text additions. Highlighter maintains each highlighted region as an implementation of Highligher.Highlight, and each Highlight can be rendered using a Highlighter.HighlightPainter implementation. As with Carets, a text area's Highlighter is instantiated by its UI delegate. We can assign/retrieve a text component's Highlighter with setHighlighter() and getHighlighter() respectively.

JTextComponent also maintains a bound focusAccelerator property, as we discussed in chapter 11, which is a char that is used to transfer focus to a text component when the corresponding key is pressed simultaneously with the ALT key. JTextComponent defines a private Action called focusAction whose actionPerformed() method calls requestFocus(). Initially focusAction is not attached to the text component (that is, it is turned off).  To activate it we use the setFocusAccelerator() method. Sending '\0' to the setFocusAccelerator() method turns it off. Internally, this method searches through all registered KeyStrokes and checks whether any are associated with focusAction, using the getActionForKeyStroke() method. If any are found they are unregistered, using the unregsiterKeyboardAction() method of JComponent. Finally the character passed in is used to construct a KeyStroke to register and associate with focusAction. This action is registered such that it will be invoked whenever the top-level window containing the given text component has the focus:

    // from JTextComponent.java

    registerKeyboardAction(

      focusAction,KeyStroke.getKeyStroke(aKey,ActionEvent.ALT_MASK),

      JComponent.WHEN_IN_FOCUSED_WINDOW);

Each text component uses a sub-class of BasicTextUI as its UI delegate. As we mentioned above, each text component also has an EditorKit for storing Actions. This EditorKit is referenced by the UI delegate. JTextField and JTextArea have default editor kits assigned by the UI delegate, whereas JEditorPane and JTextPane maintain their own editor kits independent of their UI delegate.

Unlike most Swing components, a text component's UI delegate does not directly define how that text component is rendered and laid out. Rather, it implements the ViewFactory interface (see 19.1.29) which requires the implementation of one method: create(Element e). This method returns a View instance (see 19.1.28) responsible for rendering the given Element. Each Element has an associated View that is used to render it. There are many different views provided in the text package, and it is rare that we will need to implement our own (although this is certainly possible). JTextArea, JTextField, and JPasswordField have specific Views returned by their UI delegate's create() method. JEditorPane and JTextPane Views are created by the current EditorKit.

We can retrieve a Point location in the coordinate system of a text component corresponding to a character offset with JTextComponent's viewToModel() method. Similarly, we can retrieve a Rectangle instance describing the size and location of the View responsible for rendering an Element occupying a given character offset with modelToView().

JTextComponent's margin property specifies the space to use between its border and its document content. Also note that standard clipboard operations can be programmatically performed with the cut(), copy(), and paste() methods.

19.1.2  The Document interface

abstract interface javax.swing.text.Document

In MVC terms, the model of a text component contains the text itself, and the Document interface describes this model. A hierarchical set of Elements (see 19.1.9) define the structure of a Document. Each Document contains one or more root Elements, potentially allowing more than one way of structuring the same content. Most documents only have one structure, and hence one root element. This element can be accessed with getDefaultRootElement(). All root elements, including the default root element, are accessible with getRootElements(), which returns an Element array.

Note: We will not discuss the details of maintaining multiple structures, as this is very rarely desired. See the API docs for examples of situations in which multiple structures might be useful.

Documents maintain two Positions which keep track of the beginning and end positions of the content. These can be accessed with getStartPosition() and getEndPosition() respectively. Documents also maintain a length property, accessible with getLength(), that maintains the number of contained characters.

The Document interface declares methods for adding and removing DocumentListeners (see 19.1.8), for notification of any content changes, and UndoableEditListeners (allowing easy access to built-in undo/redo support  -- refer back to chapter 11 for an example of adding undo/redo support to a text area).

Methods for retrieving, inserting, and removing content are also declared: getText(), insertString(), and remove(). Each of these throws a BadLocationException if an illegal (i.e. nonexistent) location in the document is specified. The insertString() method requires an AttributeSet instance describing the attributes to apply to the given text (null can be used for this parameter). Plain text components will not pay any attention to this attribute set. Text components using a StyledDocument instance most likely will pay attention to these attributes.

The createPosition() method is intended for inserting a Position instance at a given index, and the putProperty() and getProperty() methods are meant to insert and retrieve various properties stored in an internal collection. 

The render() method is unique. It takes a Runnable as parameter, and is intended to ensure thread safety by not allowing document content to change while that Runnable is running. This method is used by each text component's UI delegate during painting.

19.1.3  The StyledDocument interface

abstract interface javax.swing.StyledDocument

This interface extends the Document interface to add functionality for working with Styles and other AttributeSets. Implementations are expected to maintain a collection of Style implementations. This interface also declares the notion of character and paragraph attributes, and logical styles. What these mean is specific to each StyledDocument implementation (we will discuss these more when we talk about DefaultStyledDocument in 19.1.11).

The setCharacterAttributes() method is intended to assign a given set of attributes to a given range of document content. A boolean parameter is also required which is meant to specify whether or not pre-existing attributes of the affected content should be overwritten (true) or merged (false -- only new attributes are assigned). The setParagraphAttributes() method should work the same as setCharacterAttributes(), but is meant to apply to the number of paragraphs spanned by a given range of content. The getFont(), getBackground(), and getForeground() methods take an AttributeSet parameter, and are intended to be used for convenient access to the corresponding attribute in the given set (if it exists).

StyledDocuments are meant to allow addition, removal, and retrieval of Styles from an internal collection of Styles. The addStyle() method is intended to take a String and parent Style as parameters and return a new Style with the given name and given Style as its resolving parent. The getLogicalStyle() method should return a Style corresponding to the paragraph containing the given character offset. The setLogicalStyle() method is intended to assign a Style to the paragraph containing the given character offset. The getStyle() and removeStyle() methods should retrieve and remove a Style with the given name, respectively, in the internal collection.

The getCharacterElement() and getParagraphElement() methods are intended to allow retrieval of Elements corresponding to a given character offset. The definition of these methods will vary based on the definition of paragraph and character Elements in a StyledDocument implementation. Typically, a character Element represents a range of text containing a given offset, and a paragraph Element represents a paragraph containing the given offset.

19.1.4  AbstractDocument

abstract class javax.swing.text.AbstractDocument

AbstractDocument implements the Document interface and provides a base implementation for text component models. Two provided classes that extend AbstractDocument are used by the Swing text components as their default model: PlainDocument and DefaultStyledDocument. PlainDocument is used by all the plain text components, such as JTextArea, JTextField and its subclass, JPasswordField. It provides support for character data content only and does not support markup (i.e. multiple fonts, colors, etc.) of this content. DefaultStyledDocument is used by more sophisticated text components such as JEditorPane and its subclass, JTextPane. It provides support for markup of text by implementing the StyleDocument interface.

AbstractDocument specifies a mechanism that separates the storage of character data from the structuring of that data. Thus, we have the capability to store our text however we like without concern for how the document is structured and marked up. Similarly, we can structure a document with little concern for how its data is stored. The significance of this structure-storage separation will make more sense after we have discussed Elements and attributes below. Character data is stored in an instance of the inner Content interface which we will also discuss below.

This class defines functionality for a basic read/write locking scheme. This scheme enforces the rule that no write can occur while a read is occurring. However, multiple reads can occur simultaneously. To obtain a read lock we use the render() method. This method releases the read lock when it finishes executing the Runnable passed to it. No other access methods acquire such a lock (making them non-thread safe). The getText() method, for example, does not acquire a read lock. In a multithreaded environment, any text retrieved with this method may be corrupted if a write was occurring at the time the text was retrieved.

The read lock is basically just an increment in an internal variable that keeps track of the number of readers. The readLock() method does this for us, and will force the current thread to wait until no write locks exist. When the Runnable finishes executing, the internal reader-count variable is decremented. The readUnlock() method is responsible for this. Note that both of these methods will simply do nothing and return if the current thread is the writer thread. Also note that a StateInvariantError exception will be thrown if a read unlock is requested when there are no readers.

The write lock is a reference to the writing thread. The writeLock() and writeUnlock() methods take care of this for us. Whenever a modification is requested the write lock must first be obtained. If the writer thread is not null, and is not the same as the invoking thread, writeLock() blocks the current thread until the current writer releases the lock by calling writeUnlock().

If we intend to use the protected reader and writer locking methods ourselves in a sub-class, we are encouraged to ensure that a readUnlock() call will be made no matter what happens in the try block, using the following semantics:

  // From AbstractDocument.java

  try {

    readLock();

    // do something

  } finally {

    readUnlock();

  }

All methods that modify document content must obtain a write lock before any modification can take place. These methods include insertString() and remove().

AbstractDocument's dump() method prints the document's Element hierarchy to the given PrintStream for debugging purposes. For example, the following class will dump a JTextArea's Element hierarchy to standard output.

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.text.*;

public class DumpDemo extends JFrame

{

  JTextArea m_editor;

  public DumpDemo() {

    m_editor = new JTextArea();

    JScrollPane js1 = new JScrollPane(m_editor);

    getContentPane().add(js1, BorderLayout.CENTER);

    JButton dumpButton = new JButton("Dump");

    dumpButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {

        ((PlainDocument) m_editor.getDocument()).dump(System.out);

      }

    });

    JPanel buttonPanel = new JPanel();

    buttonPanel.add(dumpButton);

    getContentPane().add(buttonPanel, BorderLayout.SOUTH);

    setSize(300,300);

    setVisible(true);

  }

  public static void main(String[] args) {

    new DumpDemo();

  }  

}

Typing this text in the JTextArea:

Swing is

powerful!!

...produces the following output when the dump() method is invoked (this will make more sense after we discuss Elements in 19.1.9).

<paragraph>

  <content>

    [0,9][Swing is

]

  <content>

    [9,20][powerful!!

]

  <content>

    [20,21][

]

<bidi root>

  <bidi level

    bidiLevel=0

  >

    [0,21][Swing is

powerful!!

]

AbstractDocument also includes several significant inner classes and interfaces. Most we will discuss in appropriate places later in this chapter. A brief overview is appropriate here:

abstract class AbstractDocument.AbstractElement: This class implements the Element and MutableAttributeSet interfaces, allowing instances to act both as Elements and the mutable AttributeSets that describe them. This class also implements the TreeNode interface, providing an easy means of displaying document structure with a JTree.

class AbstractDocument.BranchElement: A concrete sub-class of AbstractDocument.AbstractElement that represents an Element which can contain multiple child Elements (see 19.1.9).

class AbstractDocument.LeafElement: A concrete sub-class of AbstractDocument.AbstractElement that represents an Element which cannot contain child Elements (see 19.1.9).

static abstract interface AbstractDocument.Content: Defines the data storage mechanism used by AbstractDocument sub-classes (see 19.1.9).

static abstract interface AbstractDocument.AttributeContext: Used for efficient AttributeSet management (see 19.1.16).

static class AbstractDocument.ElementEdit: Extends AbstractUndoableEdit,  implements DocumentEvent.ElementChange (see 19.1.7), and allows document changes to be undone and redone.

class AbstractDocument.DefaultDocumentEvent: Extends CompoundEdit and implements DocumentEvent (see 19.1.7). Instances of this class are used by documents to create UndoableEdits, which can be used to create UndoableEditEvents for dispatching to UndoableEditListeners. Instances of this class are also fired to any registered DocumentListeners (see 19.1.8) for change notification.

19.1.5  The Content interface

abstract static interface javax.swing.text.AbstractDocument.Content

In order to implement a data storage mechanism for text, AbstractDocument provides the static Content interface. Every Document character storage mechanism must implement this interface. (Images and other embedded objects are not considered part of a document's content.) Each Content instance represents a sequence of character data, and provides the ability to insert, remove, and retrieve character data with the insertString(), remove(), getString(), and getChars() methods.

Note: A special convenience class called Segment allows access to fragments of actual document text without the need to copy characters into a new array for processing.  This is used internally by text components to speed up searching and rendering large documents.

Implementations of Content must also provide the ability to create position markers that keep track of a certain location between characters in storage with the createPosition() method.  These markers are implementations of the Position interface.

Content implementations provide UndoableEdit objects that represent the state of storage before and after any change is made. The insertString() and remove() methods are meant to return such an object each time they are invoked, allowing insertions and removals to be undone and redone.

Two Content implementations are included in the javax.swing.text package: StringContent and GapContent. StringContent stores character data in a normal char array. GapContent also stores data in a char array but it purposefully leaves an empty space, a gap, in this array. "The gap is moved to the location of changes to take advantage of common behavior where most changes are in the same location. Changes that occur at a gap boundary are generally cheap and moving the gap is generally cheaper than moving the array contents directly to accommodate the change"API. This gap is strictly used for internal efficiency purposes and is not accessible outside of this class. 

Note: StringContent was used in earlier implementations of PlainDocument and DefaultStyledDocument, but has been replaced by GapContent (which extends a package private class called GapVector). The gap buffer algorithm used in GapContent is very efficient for keeping track of large numbers of Positions and, interestingly, is used in the popular emacs editor.

19.1.6  The Position interface

abstract interface javax.swing.text.Position

This interface consists of one method, getOffset(), which returns an int value representing the location, or offset, from the beginning of the document's content. Figure 19.1 illustrates what happens to a Position marker when text is inserted and removed from storage. This figure starts by showing a document containing "Swing text" as its content.  There are initially Position markers at offsets 0, 4, and 7.  When we remove the characters from offset 4 through 9 the Position at offset 7 is moved to offset 4.  At this point there are two Positions at offset 4 and the document content is "Swin".  When we insert "g is" at offset 4 both Positions at offset 4 are moved to offset 8 and the document content becomes "Swing is".

Note: The term range refers to a sequence of characters between two Position markers as shown in figure 19.1. 

Figure 19.1 Position movement

19.1.7  The DocumentEvent interface

abstract interface javax.swing.event.DocumentEvent

Changes to a Document's content are encapsulated in implementations of the DocumentEvent interface, the default implementation of which is AbstractDocument.DefaultDocumentEvent. There are three types of changes that can occur to document content: CHANGE, INSERT, and REMOVE (fields defined within the DocumentEvent.EventType inner class). DocumentEvent also defines an interface within it called ElementChange. Implementations of this interface, the default of which is AbstractDocument.ElementEdit, are responsible for storing information about changes to the structure of a document for use in, among other things, undo and redo operations. AbstractDocument handles the firing of DefaultDocumentEvents appropriately with its fireXXUpdate() methods.

The getChange() method is meant to take an Element instance as parameter, and return an instance of DocumentEvent.ElementChange describing the elements that were added and/or removed, as well as the location of a change. The getDocument() method should return a reference to the Document instance that generated this event. The getLength() method is intended to return the length of a change, and the getOffset() method should return the offset at which a change began. The getType() method is meant to return an instance of Document.EventType specifying the type of change that occurred to the document.

19.1.8  The DocumentListener interface

abstract interface javax.swing.event.DocumentListener

Instances of this interface can be attached to Document implementations to be notified of changes in that document's content. It is important to note that this notification will always occur after any content has been updated. Knowing this, it is even more important to realize that we should not perform any changes to the content of a document from within a DocumentListener. This can potentially result in an infinite loop in situations where a document event causes another to be fired.

Note: Never modify the contents of a document from within a DocumentListener.

The insertUpdate() and removeUpdate() methods are meant to give notification of content insertions and removals respectively. The changedUpdate() method is intended to provide notification of attribute changes.

19.1.9  The Element interface

abstract interface javax.swing.text.Element

Elements provide a hierarchical means of structuring a Document's content. Associated with each Element is a set of attributes encapsulated in an AttributeSet implementation. These attributes provide a means of specifying the markup of content associated with each Element. AttributeSets most often take the form of Style implementations and are grouped together inside a StyleContext object. StyleContext objects are used by StyledDocument implementations such as DefaultStyledDocument. The objects that are responsible for actually rendering text components are implementations of the abstract View class. Each Element has a separate View object associated with it, and each View recognizes a predefined set of attributes used in the actual rendering and layout of that Element.

Note: Elements are objects that impose structure on a text component's content. They are actually part of the document model, but they are also used by views for text component rendering.

The getAttributes() method is meant to retrieve an AttributeSet collection of attributes describing an Element. The getElement() method is intended to fetch a child Element at the given index, where the index is given in terms of the number of child Elements. The getElementCount() method should return the index of the Element closest to the provided document content offset. The getElementCount() method is meant to return the number of child Elements an Element contains (0 if the parent Element itself is a leaf). The isLeaf() method is intended to tell us whether or not an Element is a leaf element, and getParentElement() should return an Element's parent Element.

The getDocument() method is meant to retrieve the Document instance an Element belongs to. The getStartOffset() and getEndOffset() methods should return the offset of the beginning and end of an Element, respectively, from the beginning of the document. The getName() method is intended to return a short String description of an Element.

AbstractDocument defines the inner class AbstractElement, which implements the Element interface. As we mentioned earlier, there are two subclasses of AbstractElement defined within AbstractDocument: LeafElement and BranchElement. Each LeafElement has a specific range of content text associated with it (this range can change when content is inserted, removed, or replaced--figures 19.2 and 19.3 illustrate).  LeafElements cannot have any child Elements. BranchElements can have any number of child Elements. The range of content text associated with BranchElements is the union of all content text associated with their child LeafElements. (Thus the start offset of a BranchElement is the lowest start offset of all its child LeafElements, and its end offset is the highest end offset of all its child LeafElements.) DefaultStyledDocument provides a third type of element called SectionElement which extends BranchElement.  The meaning of each type of element differs depending on the type of document.

The text package also includes an ElementIterator class, which is designed to traverse an Element hierarchy in a depth first fashion (i.e. postorder -- see 17.1.2). Methods first(), current(), depth(), next(), and previous() can be used to obtain information about, and programmatically traverse, an Element hierarchy. We can construct an ElementIterator object by providing either a Document or an Element to the ElementIterator constructor. If a Document is provided, the default root Element of that document is used as the root of the Element hierarchy traversed by ElementIterator.

Note: ElementIterator does not provide any thread safety by default, so it is our responsibility to ensure that no Element changes occur during traversal.

19.1.10            PlainDocument

class javax.swing.text.PlainDocument

This class extends AbstractDocument and is used by the basic text components: JTextField, JPasswordField, and JTextArea. When enforcing certain input, usually in a JTextField, we normally override AbstractDocument's insertString() method in a PlainDocument sub-class (see the discussion of JTextField in chapter 11 for an example).

PlainDocument uses a BranchElement as its root and has only LeafElements as children. In this case each LeafElement represents a line of text and the root BranchElement represents the whole document text. PlainDocument identifies a BranchElement as "paragraph" and a LeafElement as "content". Note that the notion of a paragraph in PlainDocument is much different than our normal notion of a paragraph. Usually we think of paragraphs as sections of text separated by line breaks. However, PlainDocument considers each section of text ending with a line break as a line of "content" in its never-ending "paragraph". Figure 19.2 and 19.3 show the structure of a sample PlainDocument and illustrate how Elements and their associated Positions can change when document content changes.

Figure 19.2 Sample PlainDocument structure

In figure 19.2 we see a PlainDocument containing three elements. Two LeafElements represent two lines of text and are children of the root BranchElement. Note that the this root element begins at offset 0, the start offset of the first LeafElement, and ends at 19, the end offset of the last LeafElement. This document would be displayed in a JTextArea as:

Swing is

powerful!!

Note: The line break at the end of the second LeafElement is always present at the end of the last Element in any PlainDocument. It does not represent a line break that was actually inserted into the document and is not counted when the document length is queried using the getLength() method. Thus the length of the document shown in figure 19.2 would be returned as 19.

Now suppose we insert two line breaks at offset 5. Figure 19.3 shows the structure that would result from this addition.

Figure 19.3 Sample PlainDocument structure after inserting two line breaks at offset 19

This document would now be displayed in a JTextArea as:

Swing

 is

powerful!!

JTextArea, JTextField, and JPasswordField use PlainDocument as their model. Only JTextArea allows its document to contain multiple LeafElements. JTextField and its JPasswordField subclass allow only one LeafElement.

19.1.11            DefaultStyledDocument

class javax.swing.text.DefaultStyledDocument

DefaultStyledDocument provides significantly more power over the PlainDocument structure described above. This StyledDocument implementation (see 19.1.3) is used for marked up (styled) text. JTextPane uses an instance of DefaultStyledDocument by default (although this may change based on its content type).

DefaultStyledDocument uses and instance of its inner SectionElement class as its root Element, which has only instances of AbstractDocument.BranchElement as children. These BranchElements represent paragraphs, referred to as paragraph Elements, and they contain instances of AbstractDocument.LeafElement as children. These LeafElements represent what are referred to as character Elements. Character Elements represent regions of text (possibly multiple lines within a paragraph) that share the same attributes.

We can retrieve the character Element occupying a given offset with the getCharacterElement() method, and we can retrieve the paragraph Element occupying a given offset with the getParagraphElement() method.

We will discuss attributes, AttributeSets, and their usage details soon enough. However, it is important to understand here that AttributeSets assigned to DefaultStyledDocument Elements resolve hierarchically. For instance, a character Element will inherit all attributes assigned to itself, as well as those assigned to the parent paragraph Element. Character Element attributes override those of the same type defined in the parent paragraph Element's AttributeSet.

Note: The Elements used by DefaultStyledDocument are derived from AbstractDocument.AbstractElement, which implements both the Element and MutableAttributeSet interfaces. This allows these Elements to act as their own AttributeSets, and use each other as resolving parents.

Figure 19.4 shows a simple DefaultStyledDocument in a JTextPane with two paragraphs.

Figure 19.4 A two-paragraph DefaultStyledDocument, with several different attributes, in a JTextPane.

Using AbstractDocument's dump() method to display this document's Element structure to standard output (see 19.1.4), we get the following:

<section>

  <paragraph

    RightIndent=0.0

    LeftIndent=0.0

    resolver=NamedStyle:default {name=default,nrefs=2}

    FirstLineIndent=0.0

  >

    <content

      underline=false

      bold=true

      foreground=java.awt.Color[r=0,g=128,b=0]

      size=22

      italic=false

      family=SansSerif

    >

      [0,6][Swing

]

  <paragraph

    RightIndent=0.0

    LeftIndent=0.0

    resolver=NamedStyle:default {name=default,nrefs=2}

    FirstLineIndent=0.0

  >

    <content

      underline=false

      bold=false

      foreground=java.awt.Color[r=0,g=0,b=0]

      size=12

      italic=false

      family=SansSerif

    >

      [6,9][is ]

    <content

      underline=false

      bold=false

      foreground=java.awt.Color[r=0,g=0,b=0]

      size=12

      italic=false

      family=SansSerif

    >

      [9,19][extremely ]

    <content

      underline=false

      bold=false

      foreground=java.awt.Color[r=0,g=0,b=192]

      size=18

      italic=true

      family=SansSerif

    >

      [19,27][powerful]

    <content

      underline=false

      bold=true

      foreground=java.awt.Color[r=255,g=0,b=0]

      size=20

      italic=false

      family=SansSerif

    >

      [27,28][!]

    <content>

      [28,29][

]

<bidi root>

  <bidi level

    bidiLevel=0

  >

    [0,29][Swing

is extremely powerful!

]

Note the use of <section>, <paragraph>, and <content> to denote SectionElement, BranchElement, and LeafElement respectively. Also note that the <paragraph> and <content> tags each contain several attributes. The <paragraph> attributes represent paragraph Element attributes and the <content> attributes represent character Element attributes. We will discuss specific attributes in more detail below. Note that the <bidi root> tag specifies a second root Element allowing bidirectional text (this functionality is incomplete as of Java 2 FCS).

We can assign paragraph and character attributes to a region of text with the setParagraphAttributes() and setCharacterAttributes() methods respectively. These methods require a start and end offset, specifying the region to apply the attributes to, as well as an AttributeSet containing the attributes, and a boolean flag specifying whether or not to replace pre-existing attributes with the new attributes.

Regarding the range of text, paragraph attributes will be applied to paragraph Elements that contain at least some portion of the specified range. Character attributes will be applied to all character Elements that intersect that range. If the specified range only partially extends into a character Element, that Element will be split into two, so that only the specified range of text will receive the new attributes (this splitting is handled by an instance of the ElementBuffer inner class).

Regarding the boolean flag, if the flag is true, all pre-existing paragraph Element attributes are removed before the new set is applied. Otherwise, the new set is merged with the old set, and any new attributes overwrite pre-existing attributes. Character attributes work in a similar way, but they do not change paragraph attributes at all--they simply override them.

DefaultStyledDocument also defines the notion of logical paragraph Styles. A logical paragraph Style acts as the resolving parent of a paragraph Element's AttributeSet. So attributes defined in a paragraph Element's AttributeSet override those defined in that paragraph's logical Style. We can change a specific paragraph Element's logical style with the setLogicalStyle() method. The logical style of each paragraph defaults to StyleContext.DEFAULT_STYLE (which is empty by default).

JTextPane implements getParagraphAttributes(), setParagraphAttributes(), getLogicalStyle(), and setLogicalStyle() methods which communicate directly with its StyledDocument. JTextPane's paragraph attributes and logical style setXX() methods apply to the paragraph the caret currently resides in if there is no selection. If there is a selection, these methods apply to all paragraphs spanned by the selected region. JTextPane's paragraph attributes and logical style getXX() methods apply to the paragraph currently containing the caret.

JTextPane also implements getCharacterAttributes() and setCharacterAttributes() methods. If there is a selection, the setCharacterAttributes() method will act as described above, splitting Elements as needed. If there is no selection, this method will modify JTextPane's input attributes.

Note: JTextPane's input attributes is a reference to an AttributeSet which changes with the location of the caret. This reference always points to the attributes of the character Element at the current caret location. We can retrieve it at any time with JTextPane's getInputAttributes() method. Whenever text is inserted in a JTextPane, the current input attributes will be applied to that text by default. However, any attributes explicitly assigned to newly inserted text will override those defined in the current input attributes.

A StyleContext instance (see 19.1.16) is associated with each DefaultStyledDocument. As we mentioned in the beginning of this chapter, the Style interface describes a named mutable AttributeSet, and the StyledDocument interface describes a Document which manages a set of Styles. A DefaultStyledDocument's StyleContext instance is what performs the actual management, creation, and assignment of that document's Styles. If a StyleContext is not provided to the DefaultStyledDocument constructor, a default version is created.

JTextPane defines several methods for adding, removing, and retrieving Styles, as well as specific attributes within a given AttributeSet (such as the getFont() and getForeground() methods). Calls to these methods are forwarded to methods of the same signature in JTextPane's StyledDocument, and, in the case of DefaultStyledDocument, these calls are forwarded to the StyleContext in charge of all the Styles.

DefaultStyledDocument also includes several significant inner classes:

static class DefaultStyledDocument.AttributeUndoableEdit: This class extends AbstractUndoableEdit to allow AttributeSet undo/redo functionality with Elements.

class DefaultStyledDocument.ElementBuffer: Instances of this class are used to manage structural changes in a DefaultStyledDocument, such as the splitting of Elements, or the insertion and removal of text, resulting in the modification of, and the insertion and/or removal of, various Elements. This class also plays a critical role in constructing AbstractDocument.DefaultDocumentEvents (see 19.1.4).

static class DefaultStyledDocument.ElementSpec: This class describes an Element that can be created and inserted into a document in the future with an ElementBuffer.

protected class DefaultStyledDocument.SectionElement: This class extends AbstractDocument.BranchElement and acts as a DefaultStyledDocument's default root Element. It contains only BranchElement children (representing paragraphs).

19.1.12            The AttributeSet interface

abstract interface javax.swing.text.AttributeSet

An attribute is simply a key/value pair (as in a Hashtable) that should be recognized by some View implementation available to the text component being used. As we know from our discussion above, each Element in a DefaultStyledDocument has an associated set of attributes which resolves hierarchically. The attributes play a critical role in how that piece of the document will be rendered by a View. For example, one commonly used attribute is FontFamily. The FontFamily attribute key is an Object consisting of the String "family". The FontFamily attribute value is a String representing the name of a font (i.e. "monospaced"). Other examples of attribute keys include "Icon" and "Component," whose values are instances of Icon and Component respectively.

If an attribute is not recognized by a View, the Element associated with that view will not be rendered correctly. Thus, there is a predefined set of attributes that is recognized by the Swing View classes, and these attribute keys should be considered reserved -- in other words, all new attributes should use new keys. These predefined attribute keys are all accessible as static Objects in the StyleConstants class (see 19.1.15).

Sets of attributes are encapsulated in implementations of either the AttributeSet interface, the MutableAttributeSet interface (see 19.1.13), or the Style interface (see 19.1.14). Style extends MutableAttributeSet which, in turn, extends AttributeSet. The AttributeSet interface describes a read-only set of attributes because it does not provide methods for changing, adding, or removing attributes from that set.

The containsAttribute() and containsAttributes() methods are intended for checking whether an AttributeSet contains a given attribute key/value pair, or any number of such pairs. The copyAttributes() method is meant to return a fresh, immutable copy of the AttributeSet it is invoked on. The getAttributeCount() method should return the number of attributes contained in a set, and getAttributeNames() should retrieve an Enumeration of the keys describing each attribute. The isDefined() method is intended for checking whether a given attribute key corresponds to an attribute directly stored in the AttributeSet the method is invoked on (resolving parents are not searched). The isEqual() method is meant to compare two AttributeSets and return whether or not they contain identical attribute key/value pairs. The getResolveParent() method should return a reference to an AttributeSet's resolving parent, if any, and the getAttribute() method is intended to return the value of an attribute corresponding to a given key.

The AttributeSet interface also provides four empty static interfaces: CharacterAttribute, ColorAttribute, FontAttribute, ParagraphAttribute. The only reason these interfaces exist is to provide a signature (i.e. information about the class in which it is defined) which is expected of each attribute key.  This signature can be used to verify whether an attribute belongs to a certain category (see 19.1.15).

Only one direct implementation of the AttributeSet interface exists within the text package: StyleContext.SmallAttributeSet. A SmallAttributeSet is an array of attribute key/value pairs stored in the alternating pattern: key1, value1, key2, value2, etc. (thus the number of attributes contained in a SmallAttributeSet is actually half the size of its array). An array is used for storage because AttributeSet describes a read-only set of attributes, and using an array is more memory-efficient than dynamically resizable storage such as that provided by a Hashtable. However, it is less time-efficient to search through an array than a Hashtable.  For this reason, SmallAttributeSet is intended to be used only for small sets of attributes. These sets are usually shared between several Elements. Because of the way sharing works (see 19.1.16), the smaller the set of attributes the better candidate that set is for being shared.

19.1.13            The MutableAttributeSet interface

abstract interface javax.swing.text.MutableAttributeSet

The MutableAttributeSet interface extends the AttributeSet interface and declares additional methods intended to allow attribute addition, removal, and resolving parent assignment: addAttribute(), addAttributes(), setResolveParent(), removeAttribute(), and two variations of removeAttributes().

MutableAttributeSet also has two direct implementations within the text package: AbstractDocument.AbstractElement and SimpleAttributeSet. The fact that AbstractElement implements MutableAttributeSet allows such Elements to act as resolving parents to one another. It also reduces object overhead by combining structural information about a region of text with that region's stylistic attributes.

SimpleAttributeSet uses a Hashtable to store attribute key/value pairs because it must be dynamically resizable. By nature, a Hashtable is less efficient than an array in memory usage, but more efficient in look-up speed. For this reason, SimpleAttributeSets are used for large sets of attributes that are not shared.

Note: In the past few sections we have alluded to the importance of efficiency in attribute storage.  Efficiency here refers to both memory usage and speed of attribute location. To summarize the issues: A View uses attributes to determine how to render its associated Element. These attribute values must be located, by key, within that Element's attribute set hierarchy. The faster this location occurs the quicker the view is rendered and the more responsive the user interface becomes. So look-up speed is a large factor in deciding how to store attribute key/value pairs. Memory usage is also a large issue. Obtaining efficient look-up speed involves sacrificing efficient memory usage, and vice-versa. This necessary trade-off is taken into account through the implementation of the different attribute storage mechanisms described above, and the intelligent management of when each mechanism is used. We will soon see that the StyleContext class acts as, among other things, this intelligent manager.

19.1.14            The Style interface

abstract interface javax.swing.text.Style

The Style interface extends MutableAttributeSet and provides the ability to attach listeners for notification of changes to its set of attributes.  Style also adds a String used for name identification. The only direct implementation of the Style interface is provided by StyleContext.NamedStyle. Internally, NamedStyle maintains its own private AttributeSet implementation that contains all its attributes. This AttributeSet can be an instance of StyleContext.SmallAttributeSet or SimpleAttributeSet, and may switch back and forth between these types over the course of its lifetime (this will become clear after our discussion of StyleContext).

19.1.15            StyleConstants

class javax.swing.text.StyleConstants

The StyleConstants class categorizes predefined attribute keys into members of four static inner classes: CharacterConstants, ColorConstants, FontConstants, and ParagraphConstants. These Objects are all aliased from their outer class, StyleConstants, so they are more easily accessible (aliasing here means providing a reference to an object of an inner class). Also, both ColorConstants and FontConstants keys are aliased by CharacterConstants to provide a sensible hierarchy of attribute key organization.

Note: Not all aliased keys use the same name in each class. For instance, FontFamily in StyledConstants is an alias of Family in StyledConstants.CharacterConstants. However, Family in StyledConstants.CharacterConstants is an alias of Family (the actual key) in StyledConstants.FontConstants. Each is a reference to the same key object and it makes no difference which one we use.

Most keys are self-explanatory in meaning. The StyleConstants API documentation page contains a helpful diagram illustrating the meaning of some of the less self-explanitory attribute keys that apply to paragraphs of styled text. (Each of the keys illustrated in this diagram is an alias of the actual key defined in StyleConstants.ParagraphConstants.)

StyleConstants also defines static methods for assigning and retrieving many predefined attributes in an AttributeSet. For example, to assign a specific font family attribute to an AttributeSet (assuming it is mutable), we can use StyleConstants' setFontFamily() method.

19.1.16            StyleContext

class javax.swing.text.StyleContext

StyleContext implements the AbstractDocument.AttributeContext interface, and declares a set of methods used to modify or fetch new instances of AttributeSet implementations. AbstractContext was designed with the understanding that the implementor may use more than one type of AttributeSet implementation to store sets of attributes. The decision to use one type over another may be based on any number of factors, and StyleContext takes full advantage of this design.

StyleContext's main role is to act as a container for Styles that may be used by one or more DefaultStyledDocuments. It maintains a private NamedStyle instance used to store its Styles and allow access by name. Each of these contained Styles is also an instance of NamedStyle. So, to clarify, StyleContext maintains a NamedStyle instance whose key/value pairs are of the form String/NamedStyle.

StyleContext also maintains a subset of these NamedStyle values in a Hashtable. Only those NamedStyle's whose AttributeSet contains 9 or less attributes are stored in this Hashtable and their AttributeSets are maintained as instances of SmallAttributeSet. Those NamedStyles with an AttributeSet containing 10 or more attributes are not stored in the Hashtable, and their AttributeSets are maintained as instances of SimpleAttributeSet.

This partitioning is managed dynamically by StyleContext, and is the result of combining the AbstractContext design with the use of a compression threshold (a hard-coded int value of 9).  Whenever an attribute is added or removed, StyleContext checks the number of attributes in the target AttributeSet.  If the resulting set will contain 9 or less attributes it remains, or is converted to, a SmallAttributeSet and is added to the Hashtable if it wasn't already there.  If the resulting set will contain 10 or more attributes it remains, or is converted to, a SimpleAttributeSet and is removed from the Hashtable if it was already there.

The reason for this partitioning is to support efficient AttributeSet sharing. Most styled documents contain many distinct regions of identically styled text. These regions normally have a small number of attributes associated with them. It is clear that the best thing to do in this situation is to assign the same AttributeSet to each of these regions. And the best AttributeSet implementation to use for this is SmallAttributeSet due to its superior memory efficiency (since look-up speed is a minor issue with a very small number of attributes). Larger sets of attributes are, in general, rare. The best AttributeSet implementation to use for this is SimpleAttributeSet due to its superior look-up capabilities (since memory usage will most likely be a minor issue with a relatively small number of SimpleAttributeSets).

19.1.17            The Highlighter interface

abstract interface javax.swing.text.Highlighter

This interface describes how specific regions of text can be marked up with instances of the inner Highlighter.Highlight interface. A Highlight maintains a beginning and end offset, and a reference to an instance of the inner Highlighter.HighlightPainter interface. A HighlightPainter's only responsibility is to render the background of a specific region of text.

A text component's UI delegate is responsible for maintaining its Highlighter. For this reason the Highlighter can change when a text component's look and feel changes. JTextComponent provides methods for working with a text component's Highlighter so we generally ignore the fact that such methods really get forwarded to the UI delegate.

A Highlighter is intended to maintain an array of Highlighter.Highlight instances, and we are meant to be able to add to this array using the addHighlight() method. This method takes two ints defining the range of text to highlight, as well as a Highlighter.HighlightPainter instance specifying how that Highlight should be rendered. Thus, by defining various HighlightPainters, we can add an arbitrary number of highlighted regions with distinct visual effects.

The range a Highlight encompasses is meant to be modified with the changeHighlight() method, and Highlights can be removed from a Highlighter's array with the removeAllHighlights() or removeHighlight() methods. The paint() method is meant to manage the rendering of all a Highligher's Highlights.

We can assign a new Highlighter with JTextComponent's setHighlighter() method. Similarly, we can retrieve a reference to the existing one with JTextComponent's getHighlighter() method. Each JTextComponent also maintains a selectionColor property which specifies the color to use in rendering default highlights.

19.1.18            DefaultHighlighter

class javax.swing.text.DefaultHighlighter

DefaultHighlighter extends the abstract LayeredHighlighter class. LayeredHighlighter implements the Highlighter interface and defines a paintLayeredHighlights() method, which is responsible for managing potentially multiple overlapping Highlights.  LayeredHighlighter also declares an inner abstract static class called LayerPainter from which the static DefaultHighlighter.DefaultHighlightPainter extends. This implementation paints a solid background, behind the specified region of text, in the current text component selection color.

19.1.19            The Caret interface

abstract interface javax.swing.text.Caret

This interface describes a text component's cursor. The paint() method is responsible for rendering the caret, and the setBlinkRate()/getBlinkRate() methods are meant to assign/retrieve a specific caret blink interval (normally in milliseconds). The setVisible() and isVisible() methods are intended to hide/show the caret and check for caret visibility, respectively.

The setDot()/getDot() methods are meant to assign/retrieve the offset of the caret within the current document. The getMark() method should return a location in the document where the caret's mark has been assigned. The moveDot() method is intended to assign a mark position, and move the caret to a new location while highlighting the text between the dot and the mark. The setSelectionVisible()/isSelectionVisible() methods are meant to assign/query the visible state of the highlight specifying the currently selected text.

The setMagicCaretPosition()/getMagicCaretPosition() methods manage a dynamic caret position used when moving the caret up and down between lines with the arrow keys. When moving up and down between lines with an unequal number of characters, the magic position should place the caret as close to the same location within each line as possible. If the magic position is greater than the length of the current line, the caret should be placed at the end of the line. Note that this feature is common in almost all modern text applications, and is implemented for us in the DefaultCaret class.

The Caret interface also declares methods for the registration of ChangeListeners for notification of changes in the caret's position: addChangeListener(), removeChangeListener().

19.1.20            DefaultCaret

class javax.swing.text.DefaultCaret

This class extends java.awt.Rectangle, and represents a concrete implementation of the Caret interface used by all text components by default. It is rendered as a blinking vertical line in the color specified by its associated text component's caretColor property. DefaultCaret also implements the FocusListener, MouseListener, and MouseMotionListener interfaces.

The only MouseListener methods without empty implementations are mouseClicked() and mousePressed(). If a mouse click occurs with the left mouse button, and the click count is two (i.e. a double-click), mouseClicked() will invoke the Action returned by DefaultEditorKit.selectWordAction() to select the word containing the caret. If the click count is three, mouseClicked() will invoke the Action returned by DefaultEditorKit.selectLineAction() to select the line of text containing the caret. The mousePressed() method sends its MouseEvent parameter to DefaultCaret's positionCaret() method, which sets the dot property to the document offset corresponding to the mouse press, and clears the magicCaretPosition property. The mousePressed() method also checks to see if the text component is enabled, and if it is, its requestFocus() method is invoked.

The only MouseMotionListener method without an empty implementation is mouseDragged(). This method simply passes its MouseEvent parameter to DefaultCaret's moveCaret() method. The moveCaret() method determines the offset of the caret destination by passing the MouseEvent's coordinates to the text component's viewToModel() method. The moveDot() method is then invoked to actually move the caret to the determined position (recall that the moveDot() method sets the mark property and selects the text between the mark position and the new dot position).

Both FocusListener methods are nonempty. The focusGained() method checks whether the text component is editable, and if it is, the caret is made visible. The focusLost() method simply hides the caret. These methods are invoked when the text component gains or loses the focus.

We can customize the way a selection's highlight appears by overriding DefaultCaret's getSelectionPainter() method to return our own Highlighter.HighlightPainter implementation. We can also customize the appearance of a caret by overriding the paint() method. If we do reimplement the paint() method, however, we must also override the damage() method. The damage() method is passed a Rectangle representing the region of the text component to repaint when the caret is moved.

For instance, the following is a simple DefaultCaret sub-class that renders a wide black caret.[3]

class WideCaret extends DefaultCaret

{

  protected int caretWidth = 6;

  protected void setWidth(int w) {

    caretWidth = w;

  }

  // Since DefaultCaret extends Rectangle, it inherits

  // the x, y, width, and height variables which are

  // used here to allow proper repainting.

  protected synchronized void damage(Rectangle r) {

    if (r != null) {

      x = r.x - width;

      y = r.y;

      width = width;

      height = r.height;

      repaint();

    }

  }

  public void paint(Graphics g) {

    if(isVisible()) {

      try {

        TextUI mapper = getComponent().getUI();

        Rectangle r = mapper.modelToView(

          getComponent(), getComponent().getCaretPosition());

        g.setColor(getComponent().getCaretColor());

        g.fillRect(r.x, r.y, caretWidth, r.height - 1);

      }

      catch (Exception e) {

        System.err.println("Problem painting cursor");

      }

    }

  }

}

19.1.21            The CaretListener interface

abstract interface javax.swing.event.CaretListener

This interface describes a listener that is notified whenever a change occurs in a text component's caret position. It declares one method, caretUpdate(), which takes a CaretEvent as parameter. We can attach and remove CaretListeners to any JTextComponent with the addCaretListener() and removeCaretListener() methods respectively.

19.1.22            CaretEvent

class javax.swing.event.CaretEvent

This event simply encapsulates a reference to its source object (normally a text component). CaretEvents are passed to all attached CaretListeners whenever the associated text component's caret position changes.

19.1.23            The Keymap interface

abstract interface javax.swing.text.Keymap

This interface describes a collection of bindings between KeyStrokes (see 2.13.2) and Actions (see 12.1.23). We are meant to add new KeyStroke/Action bindings to a Keymap with the addActionForKeyStroke() method. Like AttributeSets, Keymaps resolve hierarchically. Like Styles, Keymaps have a name used to reference them by.

We are meant to query the Action that corresponds to a specific KeyStroke with the getAction() method. If no corresponding Action is located in the Keymap, its resolving parents should be searched until either no more resolving parents exist, or a match is found. Similarly, we are intended to retrieve an array of KeyStrokes mapped to a given Action with the getKeyStrokesForAction() method.[4] The isLocallyDefined() method is meant to check whether or not a given KeyStroke is bound to an Action in the Keymap under investigation. The removeBindings() method should remove all bindings in a Keymap, and the removeKeyStrokeBinding() method is intended to remove only those bindings corresponding to a given KeyStroke.

By default, all JTextComponents share the same Keymap instance. This is what enables the default functionality of the Backspace, Delete, and left and right arrow keys on any text component. For this reason, it is not a good idea to retrieve a text component's Keymap and modify it directly. Rather, we are encouraged to create our own Keymap instance, and assign the default Keymap as its resolving parent. Also note that by assigning a resolving parent of null, we can effectively disable all bindings on a text component, other than those in the given component's Keymap itself (the underlying role Keymaps play in text components will become clear after we discuss DefaultEditorKit below).

We can obtain a text component's Keymap with either of JTextComponent's getKeymap() methods. We can assign a text component a new Keymap with the setKeymap() method, and we can add a new Keymap anywhere within the Keymap hierarchy with the addKeymap() method. We can also remove a Keymap from the hierarchy with the removeKeymap() method.

For example, to create and add a new Keymap to a JTextField and use the default text component Keymap as resolving parent, we might do something like the following:

  Keymap keymap = myJTextField.getKeymap();

  Keymap myKeymap = myJTextField.addKeymap("MyKeymap", keymap);

We can then add KeyStroke/Action pairs to myKeymap with the addActionForKeyStroke() method (we will see an example of this in the next section).

Note: Recall from section 2.13.4 that KeyListeners will receive key events before a text component's Keymap. Although the use of Keymaps is encouraged, handling keyboard events with KeyListeners is still allowed.

19.1.24            TextAction

abstract class javax.swing.text.TextAction

EditorKits are, among other things, responsible for making a set of Actions available for performing common text editor functions based on a given content type. EditorKits normally use inner sub-classes of TextAction for this, as it extends AbstractAction (see 12.1.24), and provides a relatively powerful means of determining the target component to invoke the action on (by taking advantage of the fact that JTextComponent keeps track of the most recent text component with the focus, retrievable with its static getFocusedComponent() method). The TextAction constructor takes the String to be used as that action's name, and passes it to its super-class constructor. When sub-classing TextAction, we normally define an actionPerformed() method, which is responsible for performing the desired action when passed an ActionEvent. Within this method, we can use TextAction's getTextComponent() method to determine which text component the action should be invoked on.

19.1.25            EditorKit

abstract class javax.swing.text.EditorKit

EditorKits are responsible for the following functionality:

Support for an appropriate Document model. An EditorKit is meant to specifically support one type of content, a String description of which should be retrievable with the getContentType() method. A corresponding Document instance should be returned by the createDefaultDocument() method, and the EditorKit should be able to read() and write() that Document to InputStreams/OutputStreams and Readers/Writers, respectively

Support for View production through a ViewFactory implementation. This behavior is actually optional, as View production will default to a text component's UI delegate if its EditorKit's getViewFactory() method returns null (see 19.1.28 and 19.1.29 for more about Views and the ViewFactory interface).

Support for a set of Actions that can be invoked on a text component using the appropriate Document. Normally these Actions are instances of TextAction and are defined as inner classes. An EditorKit's Actions are meant to be retrievable in an array with its getActions() method.

19.1.26            DefaultEditorKit

class javax.swing.text.DefaultEditorKit

DefaultEditorKit extends EditorKit, and defines a series of TextAction sub-classes and corresponding name Strings (see API docs). Eight of these forty-six inner action classes are public, and can be instantiated with a default constructor: BeepAction, CopyAction, CutAction, DefaultKeyTypedAction, InsertBreakAction, InsertContentAction, InsertTabAction, and PasteAction. DefaultEditorKit maintains instances of all its inner Action classes in an array retrievable with its getActions() method. We can access any of these Actions easily by defining a Hashtable with Action.NAME keys and Action values:[5]

  Hashtable actionTable = new Hashtable

  Action[] actions = myEditorKit.getActions();

  for (int i=0; i < actions.length; i++) {

    String actionName = (String) actions[i].getValue(Action.NAME);

    actionTable.put(actionName, actions[i]);

  }

We can then retrieve any of these Actions with DefaultEditorKit's static String fields. For example, the following retrieves the action responsible for selecting all text in a document:

  Action selectAll = (Action) actionTable.get(

    DefaultEditorKit.selectAllAction);

These Actions can be used in menus and toolbars, or with other controls, for convenient control of plain text components.

DefaultEditorKit's getViewFactory() method returns null, which means the UI delegate is responsible for creating the hierarchy of Views necessary for rendering a text component correctly. As we mentioned in the beginning of this chapter, JTextField, JPasswordField, and JTextArea all use a DefaultEditorKit.

Although EditorKits are responsible for managing a set of Actions and their corresponding names, they are not actually directly responsible for making these Actions accessible to specific text components. This is where Keymaps fit in. For instance, take a look at the following code showing how the default JTextComponent Keymap is created (from JTextComponent.java):

/**

 * This is the name of the default keymap that will be shared by all

 * JTextComponent instances unless they have had a different

 * keymap set.

 */

public static final String DEFAULT_KEYMAP = "default";

/**

 * Default bindings for the default keymap if no other bindings

 * are given. 

 */

static final KeyBinding[] defaultBindings = {

  new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),

    DefaultEditorKit.deletePrevCharAction),

  new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),

    DefaultEditorKit.deleteNextCharAction),

  new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),

    DefaultEditorKit.forwardAction),

  new KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),

    DefaultEditorKit.backwardAction)

};

static {

  try {

    keymapTable = new Hashtable(17);

    Keymap binding = addKeymap(DEFAULT_KEYMAP, null);

    binding.setDefaultAction(new

      DefaultEditorKit.DefaultKeyTypedAction());

    EditorKit kit = new DefaultEditorKit();

    loadKeymap(binding, defaultBindings, kit.getActions());

  } catch (Throwable e) {

    e.printStackTrace();

    keymapTable = new Hashtable(17);

  }

}

19.1.27            StyledEditorKit

class javax.swing.text.StyledEditorKit

This class extends DefaultEditorKit and defines seven additional inner Action classes, each of which is publicly accessible: AlignmentAction, BoldAction, FontFamilyAction, FontSizeAction, ForegroundAction, ItalicAction, and UnderlineAction. All seven Actions are sub-classes of the inner StyledTextAction convenience class which extends TextAction.

Each of StyledEditorKit's Actions apply to styled text documents, and they are used by JEditorPane and JTextPane. StyledEditorKit does not define its own capabilities for reading and writing styled text. Instead this functionality is inherited from DefaultEditorKit which only provides support for saving and loading plain text. The two StyledEditorKit sub-classes included with Swing, javax.swing.text.html.HTMLEditorKit and javax.swing.text.rtf.RTFEditorKit, do support styled text saving and loading for HTML and RTF content types respectively.

StyledEditorKit's getViewFactory() method returns an instance of a private static inner class called StyledViewFactory which implements the ViewFactory interface as follows (from StyledEditorKit.java):

static class StyledViewFactory implements ViewFactory {

  public View create(Element elem) {

    String kind = elem.getName();

    if (kind != null) {

      if (kind.equals(AbstractDocument.ContentElementName)) {

        return new LabelView(elem);

      } else if (kind.equals(AbstractDocument.ParagraphElementName)) {

          return new ParagraphView(elem);

      } else if (kind.equals(AbstractDocument.SectionElementName)) {

          return new BoxView(elem, View.Y_AXIS);

      } else if (kind.equals(StyleConstants.ComponentElementName)) {

          return new ComponentView(elem);

      } else if (kind.equals(StyleConstants.IconElementName)) {

          return new IconView(elem);

      }

    }

    // default to text display

    return new LabelView(elem);

  }

}

The Views returned by this factory's create() method are based on the name property of the Element passed as parameter. If an Element is not recognized, a LabelView is returned. In sum, because StyledEditorKit's getViewFactory() method doesn't return null, styled text components depend on their EditorKits rather than their UI delegates for providing Views. The opposite is true with plain text components, which rely on their UI delegate for View creation.

19.1.28            View

abstract class javax.swing.text.View

This class describes an object responsible for graphically representing a portion of a text component's document model. The text package includes several extensions of this class meant for use by various types of Elements. We will not discuss these classes in detail, but a brief overview will be enough to provide a high level understanding of how text components are actually rendered.

abstract interface TabableView: Used by Views whose size depends on the size of tabs.

abstract interface TabExpander: This interface extends TabableView and is used by Views that support TabStops and TabSets (a set of TabStops). A TabStop describes the positioning of a tab character and the text appearing immediately after it.

class ComponentView: Used as a gateway view to a fully interactive embedded Component.

class IconView: Used as a gateway View to an embedded Icon.

class PlainView: Used for rendering one line of non-wrapped text with one font and one color.

class FieldView: Extends PlainView and adds specialized functionality for representing a single-line editor view (i.e. the ability to center text in a JTextField).

class PasswordView: Extends FieldView and adds the ability to render its content using the echo character of the associated component if it is a JPasswordField.

class LabelView: Used to render a range of styled text.

abstract class CompositeView: A View containing multiple child Views. All Views can contain child Views, but only instances of CompositeView and BasicTextUI's RootView (discussed below) actually contain child Views by default.

class BoxView: Extends CompositeView and arranges a group of child Views in a rectangular box.

class ParagraphView: Extends BoxView and is responsible for rendering a paragraph of styled text. ParagraphView is made up of a number of child Elements organized as, or within, Views representing single rows of styled text. This View supports line wrapping, and if an Element within the content paragraph spans multiple lines, more than one View will be used to represent it.

class WrappedPlainView: Extends BoxView and is responsible for rendering multi-line, plain text with line wrapping.

All text components in Swing use UI delegates derived from BasicTextUI by default. This class defines an inner class called RootView which acts as a gateway between a text component and the actual View hierarchy used to render it.

Note: In chapter 22 we will take advantage of BasicTextUI's root view while implementing a solution for printing styled text. The solution also requires us to implement a custom BoxView sub-class responsible for rendering each of its child Views to a Graphics instance used in the printing process (see 22.4).

19.1.29            The ViewFactory interface

abstract interface javax.swing.text.ViewFactory

This interface declares one method: create(Element elem). This method is responsible for returning a View, possibly containing a hierarchy of Views, used to render a given Element. BasicTextUI implements this interface, and unless a text component's EditorKit provides its own ViewFactory, BasicTextUI's create() method will be responsible for providing all Views. This is the case with plain text components: JTextField, JPasswordField, and JTextArea. However, styled text components, JEditorPane and JTextPane, vary greatly depending on their current content type. For this reason their Views are provided by the currently installed EditorKit. In this way, custom Views can be provided to render different types of styled content.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy