[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

How the Swing Text Package
Handles Attributes

By Scott Violet

If you ever need to create a text-intensive application, you'll need the information presented in this article. It shows how the Swing text package applies attributes -- such as bold, italic, font size, and foreground color -- to sequences of text. It will also give you a better of understanding of how Swing stores and manipulates text attributes. If you haven't yet read the article in this section titled "The Element Interface," you should read it before you start reading this one.


The AttributeSet Interface

Styled text packages allow the developer to associate attributes with regions of text. Most commonly, a Swing application uses attributes to determine how to render, or represent, a run of text -- that is, a contiguous sequence text that has the same characteristics. You can use attributes to specify certain characteristics that will be applied to a run of text -- for instance, to make a specific region of the text bold.

Swing, like some other markup languages, allows attributes to be inherited. You may know that in the CSS markup language, you can specify a paragraph to be red. Subsequently, all runs of text in that paragraph inherit the red attribute unless you explicitly define a different foreground attribute for a given run of text. Swing attributes work in a similar way.

The Swing text package manages attributes using the AttributeSet interface. The AttributeSet interface is very similar to Swing's Hashtable interface; that is, it's a collection of key/value pairs. There is one important difference, though; while Hashtable is mutable, AttributeSet is immutable. (Swing also provides a mutable subclass of AttributeSet named MutableAttributeSet, which we will examine later in this article). You can implement inheritance by associating AttributeSets with each other.

In Swing, these methods define the AttributeSet interface.

    public int getAttributeCount();
    public boolean isDefined(Object attrName);
    public boolean isEqual(AttributeSet attr);
    public AttributeSet copyAttributes();
    public Object getAttribute(Object key);
    public Enumeration getAttributeNames();
    public boolean containsAttribute(Object name, 
        Object value);
    public boolean containsAttributes(AttributeSet attributes);
    public AttributeSet getResolveParent();                

The resolveParent property, a read-only property, is used for inheritance. When an AttributeSet is asked for a particular attribute (via getAttribute()) that it does not have, the request is forwarded to its resolve parent.


The MutableAttributeSet Interface

As previously mentioned, AttributeSet is immutable. But Swing provides another interface, named MutableAttributeSet, which allows attributes to be modified. The SimpleAttributeSet class provides the default implementation of MutableAttributeSet.

In Swing, these methods define MutableAttributeSet :

    public void addAttribute(Object name, Object value);
    public void addAttributes(AttributeSet attributes);
    public void removeAttribute(Object name);
    public void removeAttributes(Enumeration names);
    public void removeAttributes(AttributeSet attributes);
    public void setResolveParent(AttributeSet parent);    

Swing uses the Element interface to model the content of a text Document. Each Element is associated with a particular AttributeSet that can be returned via the getAttributes() method.

There are several ways to modify the attributes of a particular run of text. For instance, you can:

  • Insert text in a document by calling the Document method insertString() using an AttributeSet argument. The character Elements that are created as result of the text insertion will then use the AttributeSet call.

  • Change the attributes of existing character Elements by calling the StyledDocument method setCharacterAttributes().

  • Change the attributes of an existing paragraph Element by calling the StyledDocument method setParagraphAttributes().

Here is an example that calls setCharacterAttributes() to make the characters in the range 4 through 10 bold in a styled document:

    SimpleAttributeSet attrs = new SimpleAttributeSet();
    StyleConstants.setBold(sas, true);
    styledDocument.setCharacterAttributes(4, 6, attrs, false);

The StyleConstants class defines most of the attribute key values that are used throughout Swing. In developing your own application, however, you are not restricted to using the keys defined by the StyleConstants class; you can store whatever you like in your AttributeSets. For example, if you were writing your own View implementation, you might want your View to understand special constants such as blink or dithering. To create such constants, you could define them and then mark up your model with them.


Styles

In Swing, the Style interface is an extension of MutableAttributeSet. The Style interface is made up of a mutable set of attributes, plus a name property and a ChangeListener. You can change the attributes that a Style represents by calling its MutableAttributeSet methods. When you do that, any ChangeListeners associated with the Style being used are notified.

The StyledDocument interface provides a number of methods for managing Styles. These include getStyle() to look up an existing Style, addStyle() to create a new Style, and removeStyle() to remove an existing Style. Once you have created a Style, you can associate it with a particular region of text by calling setLogicalStyle().

The DefaultStyledDocument interface adds listeners to Styles that are added to the document so that the display is updated as the Style changes.

As previously mentioned, each AttributeSet has a resolveParent property. Similarly, each Element has an AttributeSet property. To changs the resolveParent property of the AttributeSet associated with a paragraph Element at a specified location, you can call the setLogicalStyle() method .

The following example creates a JTextPane, inserts three paragraphs, assigns a Style to the first two paragraphs, and then modifies the Style:

    JTextPane textPane = new JTextPane();
    textPane.setText("p 1\np 2\np 3");
    Style style = textPane.addStyle("Sample Style", null);
    textPane.getStyledDocument().setLogicalStyle(0, style);
    textPane.getStyledDocument().setLogicalStyle(4, style);
    StyleConstants.setBold(style, true);

In this example, the statement setLogicalStyle(0, style) attaches style to the first paragraph (the first paragraph is represented by the region 0-4). Similarly, setLogicalStyle(4, style) attaches style to the second paragraph (the second paragraph is represented by the region 4-8). Because the first two paragraphs are associated with the style Style, which defines the bold attribute to be true, this code sequence causes the first two paragraphs to appear in bold.

Continuing with this example, if we wanted the third character not to be bold, we could use the following code:

    SimpleAttributeSet sas = new SimpleAttributeSet();
    StyleConstants.setBold(sas, false);
    textPane.getStyledDocument().setCharacterAttributes(2, 1, 
        sas, false);

Remember that Styles are attached to a paragraph Element's AttributeSet, and that the attributes of the Style show through to the character Element's attributes (assuming that neither the character Element nor the paragraph Element's AttributeSet contains the attribute). In the preceding example, the character Element's AttributeSet at the third character defines the bold attribute, in effect shadowing the bold attribute defined in style, so that the third character appears to be regular, not bold.

Now let's say we wanted to make the third character in this example appear once again in the manner defined by the style variable. To make this change, we would have to remove the bold attribute that is defined in the character Element's AttributeSet. We could do this by calling the setCharacterAttributes() method, as shown in the following code sequence:

    SimpleAttributeSet sas = new SimpleAttributeSet();
    textPane.getStyledDocument().setCharacterAttributes
                 (2, 1, sas, true);

In this example, the value true is passed to the setCharacterAttributes() method to indicate that the current attributes should be replaced by new attributes. At this point, the character Element's AttributeSet does not define any attributes, so the attributes defined in the style show through.


AttributeContext and StyleContext

Most styled documents use a small set of unique AttributeSets to perform such specialized tasks as defining bold and italic regions of text, or defining italic regions of text, or defining colored regions of text. As a document gets bigger, reusing common AttributeSets becomes more critical. If each Element used in an application stored its own copy of each AttributeSet, the app would incur a substantial cost without deriving any benefit. For this reason, the AbstractDocument defines an interface named AttributeContext, which can be used to share AttributeSets.

In Swing, these methods define AttributeContext:

    public AttributeSet addAttribute(AttributeSet old, 
        Object name, Object value);
    public AttributeSet addAttributes(AttributeSet old, 
         AttributeSet attr);
    public AttributeSet removeAttribute(AttributeSet old, Object name);
    public AttributeSet removeAttributes(AttributeSet old, 
        Enumeration names);
    public AttributeSet removeAttributes(AttributeSet old, 
        AttributeSet attrs);
    public AttributeSet getEmptySet();
    public void reclaim(AttributeSet a);

As you may notice, these methods mirror the methods that the MutableAttributeSet interface provides for mutating an AttributeSet. You can use AttributeContext as an entry point for AttributeSet mutation. AttributeContext in turn tries to limit the number of unique AttributeSets vended.

The StyleContext class provides the default implementation of AbstractDocument.AttributeContext. StyleContext keeps a cache of all unique AttributeSets with fewer than nine attributes. (The number nine is not magical; you can use a different number by subclassing and overriding the getCompressionThreshold() method). For AttributeSets with more than nine members, Swing creates a new MutableAttributeSet.

The following code creates a StyleContext and calls addAttribute() twice. If the two AttributeSets used in the example are the same (= =), Swing prints a message to standard ouput:

    StyleContext context = new StyleContext();
    AttributeSet attrOne = context.addAttribute(context.getEmptySet(),
                           StyleConstants.Bold, Boolean.TRUE);
    AttributeSet attrTwo = context.addAttribute(context.getEmptySet(),
                           StyleConstants.Bold, Boolean.TRUE);
    if (attrOne == attrTwo) {
        System.out.println
               ("StyleContext returned the same attribute sets");
    }

In most cases, you'll never have to concern yourself with what's shown in the above example. AbstractDocument handles it for you. When you pass in an AttributeSet to methods such as setCharacterAttributes() or insertString(), the AttributeSet is uniqued for you, using the above, by AbstractDocument.

StyleContext is also responsible for creating Styles and notifying ChangeListeners when the a Style changes.


Elements and AttributeSets in AbstractDocuments

The AbstractDocument class provides the default abstract implementation of the Document interface. All Document subclasses in the Swing text package directly or indirectly subclass from the AbstractDocument class. AbstractDocument also provides the default implementation of Element that all subclasses of AbstractDocument use. This implementation, named AbstractElement, is an inner class of AbstractDocument.

AbstractElement not only implements Element, but also implements MutableAttributeSet.

AbstractElement implements the AttributeSet methods by forwarding to an instance variable of type AttributeSet. The two AttributeSet methods getResolveParent() and getAttribute() are forwarded to the AttributeSet instance variable as well, null is returned, the method is forwarded on to the Element's parent. These methods are implemented in this way to allow the attributes of parent Elements to show through to children Elements. For example, if a paragraph Element has a bold attribute, any children Elements will pick up the bold attribute, assuming they do not define it locally.

Each instance of AbstractDocument is associated with an AttributeContext. The AttributeContext is set in the constructor of AbstractDocument. AbstractDocument also provides a protected method namedgetAttributeContext() for accessing the AttributeContext.

As previously mentioned, AbstractElement implements MutableAttributeSet. The MutableAttributeSet methods are forwarded to the equivalent AttributeContext method to obtain a new AttributeSet. The AttributeSet returned from the AttributeContext methods is the AttributeSet that AbstractElements will delegate to. In this way, AttributeContext provides a way for different AttributeSets to be shared by multiple Elements, and potentially across Documents.

 

[an error occurred while processing this directive]