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