22.2 AttributeSets and Styles


AttributeSet and its relatives are used to hold collections of attributes that can be used by styled text components (including JTextPane). For example, an AttributeSet might comprise an attribute for font size, an attribute for foreground color, and an attribute for indentation. Each attribute is simply a key/value pair. The Document model keeps track of which attribute sets apply to which blocks of text.

The interfaces and classes that are used for attribute sets are shown in Figure 22-2. We'll discuss each one in detail, but first we'll provide a brief overview of what they do and how they relate. At the end of this section, we'll develop a Style-based text editor example.

AttributeSet

This interface defines basic methods for accessing a read-only set of attributes. An AttributeSet may have a "resolving parent," which (if it exists) is consulted when property lookups can't be resolved by the current set.

MutableAttributeSet

This interface extends AttributeSet with methods that allow attributes to be added, given new values, or deleted from the set.

Style

This interface extends MutableAttributeSet to add two things: an optional name for the style and support for adding and removing ChangeEventListeners.

SimpleAttributeSet

A basic implementation of the MutableAttributeSet interface.

StyleConstants

This class defines the standard attribute keys used by Swing's text components. It also defines some static utility methods for getting and setting attribute values from attribute sets.

StyleContext

This utility class provides two services: it can create new AttributeSets in a space-efficient manner, and it manages a shared pool of named Style and Font information.

Figure 22-2. High-level AttributeSet class diagram
figs/swng2.2202.gif

Since Swing does not provide a top-level public implementation of the Style interface, when you need a Style, you must request it from a StyleContext.[4] If you need a MutableAttributeSet, you can instantiate a SimpleAttributeSet using one of its constructors. If you want an AttributeSet, you can ask the default StyleContext for one or you can instantiate a SimpleAttributeSet.

[4] Which StyleContext do you use? The static StyleContext.getDefaultStyleContext( ) method returns the system's default StyleContext, but it is probably better to ask your JTextPane, which has style methods that delegate (eventually) to its StyleContext. For an example, see Section 22.2.7 later in this chapter.

22.2.1 The AttributeSet Interface

AttributeSet is an interface for a read-only collection of an arbitrary set of key/value pairs. The attribute value can be any arbitrary Object. The attribute key can also be any arbitrary object, but in practice it is almost always one of the constants defined in the StyleConstants class. StyleConstants defines standard attribute keys for font names and styles, foreground and background colors, indentation, and so on. These keys follow the Type-Safe Enumeration pattern, a clean way for Java programs to define typed constants for use in methods and interfaces.

AttributeSets can be structured in a hierarchy, so it's easy (and common) to say "this AttributeSet is just like that AttributeSet except it has a new foreground color and turns on italics." This hierarchy is maintained through the resolveParent property.

An AttributeSet's resolving parent (if not null) is another AttributeSet. The relationship between an AttributeSet and its resolving parent is much like the inheritance relationship between a Java class and its superclass: attributes not found in the local AttributeSet are searched for in its resolving parent, just as method implementations not found in a class are searched for in its superclass. This can go on indefinitely until the attribute is found or a set with no parent is reached.

22.2.1.1 Properties

Table 22-2 shows the properties defined by the AttributeSet interface. The attributeCount property is the number of attributes defined locally by the set. attributeNames is an Enumeration containing the local attribute keys (not really "names," since keys are of type Object, and probably obtained from StyleConstants). resolveParent is the parent set used to resolve attribute keys not found in the current set, or null for no parent. Attributes defined in the parent set are not counted in attributeCount, nor are they present in attributeNames.

Table 22-2. AttributeSet properties

Property

Data type

get

is

set

Default value

attributeCount

int

·

     

attributeNames

Enumeration

·

     

resolveParent

AttributeSet

·

     

22.2.1.2 Constants

AttributeSet defines the constants shown in Table 22-3.

Table 22-3. AttributeSet constants

Constant

Data type

Description

NameAttribute

Object

An implementation that chooses to store its name as an attribute pair should use this as the attribute key.

ResolveAttribute

Object

An implementation that chooses to store its resolveParent as an attribute pair should use this constant as the attribute key.

22.2.1.3 Methods
public Object getAttribute(Object key)

Search for an attribute whose key matches the one supplied. If the attribute is not found locally, the set's resolving parent is searched (if it exists). This process continues up the hierarchy until the attribute is found. If the value cannot be found, null is returned.

Note that StyleConstants provides static utility methods that are often more convenient to use than this method. StyleConstants.getFontSize(mySet) can replace this more complex code:

((Integer)mySet.getAttribute(StyleConstants.FontSize)).intValue( )

These methods differ, however, if mySet (including its resolving ancestors) does not have an attribute for font size. In this case, StyleConstants.getFontSize( ) returns a default value (12) while mySet.getAttribute( ) returns null (which causes a NullPointerException if you blindly attempt to call its intValue( ) method).

public boolean containsAttribute(Object key, Object value)

Return true if this set (or its resolving ancestors) contains the attribute key and its value matches (in the sense of equals( )) value.

public boolean containsAttributes(AttributeSet attrs)

Return true if this set (or its resolving ancestors) contains values for all of the attributes in the given AttributeSet, and all of these values match. Note that this comparison does not include attributes contributed by any parent attribute sets. This distinction is not spelled out in the AttributeSet documentation but is true of standard implementations.

public boolean isDefined(Object key)

Return true if the given attribute key is defined locally in this set. The resolving ancestors are not searched.

public AttributeSet copyAttributes( )

Create and return a clone of this AttributeSet.

public boolean isEqual(AttributeSet attrs)

Return true if this set and the given set are equivalent. What this means exactly is left up to the implementation, and Swing's implementations can be a bit sloppy with respect to the resolving parents. For example, two SimpleAttributeSets are considered equivalent if all the attributes defined locally in the one on which you invoke the method have matches anywhere in the resolving hierarchy of the other, and both sets have the same number of locally defined attributes at the top level. In this case it is possible for an attribute defined locally in the second set, even at the top level, to fail to match anything in its comrade's resolving hierarchy, so this kind of "equivalence" is not symmetric. (Though such an example is possible, it would be convoluted because, due to how SimpleAttributeSet is implemented, both sets would have to have the same resolving parent.)

22.2.1.4 Inner interfaces

AttributeSet contains four inner interfaces. Each of these is entirely empty and serves only to mark attribute keys as belonging to a particular category. There is no requirement that attribute keys implement these interfaces, but most of the keys that Swing uses do.

public interface CharacterAttribute
public interface ColorAttribute
public interface FontAttribute
public interface ParagraphAttribute

22.2.2 The MutableAttributeSet Interface

MutableAttributeSet is an extension of the AttributeSet interface that provides methods for modifying (as opposed to just examining) the attributes in a set.

22.2.2.1 Property

MutableAttributeSet does not define any properties beyond those it inherits from AttributeSet but does extend the resolveParent read/write property by providing the setResolveParent( ) mutator method. (See Table 22-4.)

Table 22-4. MutableAttributeSet property

Property

Data type

get

is

set

Default value

resolveParento

AttributeSet

·

 

·

 

ooverridden

See also properties from the AttributeSet interface (Table 22-2).

22.2.2.2 Methods
public void addAttribute(Object key, Object value)

Add an attribute to this set with the given key and value. If an attribute with the same key exists locally in this set, the new value replaces it. Be wary of using a mutable object (such as a StringBuffer) as an attribute value because MutableAttributeSet presumes that the attribute value Object does not change while it is part of the set. (Instead of altering the attribute value Object itself, use this method to replace it with a different Object.)

Note that StyleConstants provides static utility methods that are often more convenient to use than this method. For example, StyleConstants.setFontSize(mySet,14) can replace this more complex code:

mySet.addAttribute(StyleConstants.FontSize, new Integer(14));
public void addAttributes(AttributeSet attrs)

Add all the attributes present in the supplied AttributeSet to this set. Swing's implementations ignore the resolving parent of attrs and add only the locally defined attributes.

public void removeAttribute(Object key)

Remove the attribute with the specified key from this set. This method has no effect if there is no matching local key.

public void removeAttributes(AttributeSet attrs)

Remove the attributes that match those in the supplied attrs (in both key and value). If you want attributes removed even if the values don't match, call removeAttributes(attrs.getAttributeNames( )) instead.

public void removeAttributes(Enumeration names)

Remove all attributes with keys that match those found in names.

22.2.3 The Style Interface

Style is a simple extension of MutableAttributeSet that allows a set of attributes to be given a name. This allows the Style to be easily referenced (e.g., by JTextPane's getStyle( ) method, which takes a name String as an argument and returns the requested Style). Style names are often used to populate a menu in the user interface, allowing the user to select a named Style for a block of text.

In addition, Style provides support for registering change listeners that will be notified whenever the attributes that define a Style are modified.

22.2.3.1 Property

Table 22-5 shows the property defined by the Style interface. The only property added by Style is the name property. It is permissible for a Style to be unnamed, in which case getName( ) returns null.

Table 22-5. Style property

Property

Data type

get

is

set

Default value

name

String

·

     

See also properties from the MutableAttributeSet interface (Table 22-4).

22.2.3.2 Constants

Style does not define any constants beyond those it inherits from AttributeSet; one of these is NameAttribute. We mention it here because an implementation that chooses to store its name as an attribute pair should use NameAttribute as the attribute key.

22.2.3.3 Events

When a change is made to the attributes that make up a Style, listeners registered for change events are notified. The Style interface includes the following standard methods for registering change listeners:

public void addChangeListener(ChangeListener l)
public void removeChangeListener(ChangeListener l)

22.2.4 The SimpleAttributeSet Class

Swing provides a basic implementation of the MutableAttributeSet interface that uses a Hashtable to maintain its attributes. This is Swing's only top-level public class implementing AttributeSet or MutableAttributeSet.

22.2.4.1 Properties

Table 22-6 shows the default property values defined by SimpleAttributeSet. A newly created SimpleAttributeSet contains an empty Hashtable. The resolveParent (when set) is stored in the table just like the other attributes are, using the constant ResolveAttribute[5] as the key. This means that the attributeCount property includes this attribute, and attributeNames includes this object. The empty property is false if the set has any local attributes, even if the set's only local attribute is its resolveParent.

[5] SimpleAttributeSet inherits this constant from the AttributeSet interface, but StyleConstants also defines this constant. AttributeSet.ResolveAttribute, SimpleAttributeSet.ResolveAttribute, and StyleConstants.ResolveAttribute are three names for the same constant.

Table 22-6. SimpleAttributeSet properties

Property

Data type

get

is

set

Default value

attributeCounto

int

·

   

0

attributeNameso

Enumeration

·

   

Empty Enumeration

empty

boolean

 

·

 

true

resolveParento

AttributeSet

·

 

·

null

ooverridden

22.2.4.2 Constant

In addition to those it inherits from the AttributeSet interface, SimpleAttributeSet defines the constant shown in Table 22-7.

Table 22-7. SimpleAttributeSet constant

Constant

Data type

Description

EMPTY

AttributeSet

An empty, immutable AttributeSet

22.2.4.3 Constructors
public SimpleAttributeSet( )

Create a new set containing no attributes.

public SimpleAttributeSet(AttributeSet source)

Create a set containing the attributes and values from the given set. It does not use source as a resolving parent; it just copies its local attributes. (But note that this could very well set the resolveParent to be the same as source's resolveParent.)

22.2.4.4 Add/remove methods
public void addAttribute(Object key, Object value)

Add an attribute to this set with the given key and value. If an attribute with the specified key already exists locally in this set, the supplied value replaces it. (The use of a Hashtable as the attribute storage mechanism prohibits an attribute from having a value of null. Hashtable.put( ) would throw a NullPointerException.)

Note that StyleConstants provides more convenient static utility methods. StyleConstants.setFontSize(mySet,14) can replace mySet.addAttribute(Style-Constants.FontSize, new Integer(14)), for example.

public void addAttributes(AttributeSet attrs)

Add all of the attributes defined locally in the supplied AttributeSet to this set.

public void removeAttribute(Object key)

Remove the attribute with the specified key from this set. It is removed only from the local set, not from any resolving ancestors. This method has no effect if there is no matching local key.

public void removeAttributes(AttributeSet attrs)

Remove all of the local attributes that match (in both key and value) a local attribute in attrs. If you want attributes removed even if the values don't match, call removeAttributes(attrs.getAttributeNames( )) instead.

public void removeAttributes(Enumeration names)

Remove all local attributes with keys that match those found in names.

22.2.4.5 Query methods
public Object getAttribute(Object key)

Search for an attribute whose key matches the given key. If the attribute is not found locally, the set's resolving ancestors (if any) are searched. If the value cannot be found, null is returned.

Note that StyleConstants provides more convenient static utility methods. StyleConstants.getFontSize(mySet) can be used instead of this more complex code:

((Integer)mySet.getAttribute(StyleConstants.FontSize)).intValue( )

These methods differ, however, if mySet (including its resolving ancestors) does not have an attribute for font size. In this case StyleConstants.getFontSize( ) returns a default value (12), while mySet.getAttribute( ) returns null (which throws a NullPointerException if you attempt to call its intValue( ) method).

public boolean containsAttribute(Object key, Object value)

Return true if this set (or its resolving ancestors) contains the attribute key and its value matches (in the sense of equals( )) value.

public boolean containsAttributes(AttributeSet attrs)

Return true if this set (or its resolving ancestors) contains values for all of the local attributes in the given AttributeSet, and all of these values match.

public boolean isDefined(Object key)

Return true if the given attribute key is defined locally in this set. Resolving ancestors are not searched.

public boolean isEqual(AttributeSet attr)

Return true only if the given set has the same contents (attribute keys and values, including the pseudoattribute for the resolving parent) as the current set. The precise behavior of this method is it returns true if the given set has the same number of local attributes as this set and if containsAttributes(attr) returns true. This is only an approximation of actual equivalence.

public AttributeSet copyAttributes( )

Create and return a copy of this set. Except for the declared return type, this method is identical to the clone( ) method.

22.2.5 The StyleConstants Class

This class defines a collection of well-known attribute keys. You are free to use attributes of any Object type as the keys in the attribute sets you create, but the attribute keys defined here are the ones that JTextPane expects to see and knows how to handle. (Barring customization on your part, other attributes are typically ignored.)

StyleConstants also provides static utility methods for getting and setting these well-known attributes. These are handy for three reasons:

  • They free you from having to use attribute keys directly.

  • They take care of wrapping and unwrapping base types into their Object counterparts. (boolean to java.lang.Boolean, int to Integer, etc.)

  • The get methods return default values (instead of null) when they are unable to find an appropriate attribute pair.[6]

    [6] Therefore, if you want to be able to detect the absence of an attribute, don't use StyleConstants's static methods. Instead, call the AttributeSet's getAttribute( ) method directly. You should also be aware that the default values that StyleConstants provides are not universal. For example, without a Background attribute, StyleConstants.getBackground( ) returns Color.black, but JTextFrame uses the color specified by its Background property (inherited from Component/JComponent.)

Consider the code needed to retrieve the font size from one attribute set and to set the font size of another set to be two points larger. Without these static utility methods, the code looks something like this:

Object sizeObj = origSet.getAttribute(StyleConstants.FontSize); int size = (sizeObj == null) ? 12 : ((Integer)sizeObj).intValue( ); otherSet.addAttribute(StyleConstants.FontSize, new Integer(size+2));

With them, the code can be as simple as:

int size = StyleConstants.getFontSize(origSet); StyleConstants.setFontSize(otherSet, size+2);
22.2.5.1 Attribute key constants and utility methods

Usually, we cover a class's constants and methods separately, but we make an exception for StyleConstants. The following tables show the constants defined by StyleConstants, grouped by how they are used. Each row contains an attribute key constant, the object type used by the key's values, the utility methods that get or set values using that key, and the default value returned by the get method when it is unable to find a value using the key.

22.2.5.1.1 Character attribute keys

Table 22-8 shows the constants and utility methods for character attributes.

Table 22-8. StyleConstants: character attribute values

Constant

Value type

Public static methods[7]

Default

Background
Color
Color getBackground(aset) void setBackground(maset, Color bg)
Color.black
BidiLevel
Integer
int getBidiLevel(aset) void setBidiLevel(maset, int bidi)
0
Bold
Boolean
boolean isBold(aset) void setBold(maset, boolean b)
false
ComponentAttribute
Component
Component getComponent(aset) void setComponent(maset, Component c)
[8]
null
FontFamily
String
String getFontFamily(aset) void setFontFamily(maset, String fam)
"Mono-spaced"
FontSize
Integer
int getFontSize(aset) void setFontSize(maset, int s)
12
Foreground
Color
Color getForeground(aset) void setForeground(maset, Color fg)
Color.black
IconAttribute
Icon
Icon getIcon(aset) void setIcon(maset, Icon i)
[9]
null
Italic
Boolean
boolean isItalic(aset) void setItalic(maset, boolean b)
false
Strikethrough
Boolean
boolean isStrikethrough(aset) void setStrikethrough(maset, boolean b)
false
Subscript
Boolean
boolean isSubscript(aset) void setSubscript(maset, boolean b)
false
Superscript
Boolean
boolean isSuperscript(aset) void setSuperscript(maset, boolean b)
false
Underline
Boolean
boolean isUnderline(aset) void setUnderline(maset, boolean b)
false

[7] The first argument's type declaration is omitted for brevity. The type of aset in the get methods is AttributeSet. The type of maset in the set methods is MutableAttributeSet. If a get method is unable to find the appropriate attribute in the set, it returns the value listed in the Default column.

[8] The setComponent( ) method actually sets two attributes. It sets ComponentAttribute as expected, but it also sets AbstractDocument.ElementNameAttribute to ComponentElementName. Though attribute sets usually specify the style in which text should be displayed, these attributes specify that a Component should replace the text.

[9] The setIcon( ) method actually sets two attributes. It sets IconAttribute as expected, but it also sets AbstractDocument.ElementNameAttribute to IconElementName. Though attribute sets usually specify the style in which text should be displayed, these attributes designate that an Icon should replace the text.

The FontSize, FontFamily, Bold, and Italic attributes together determine the character's font. A method in the StyleContext class may save you the trouble of retrieving these attribute values separately: getFont(AttributeSet attr) examines the values of these four properties (plus Subscript and Superscript, which can alter the actual size of the font) and returns an appropriate java.awt.Font object.

The Foreground and Background attributes determine the color in which the characters are painted. The Subscript and Superscript attributes alter the font size and placement. The Strikethrough and Underline attributes determine whether the character is decorated with lines below or through it, respectively.

The BidiLevel attribute is the character's bidirectional level as defined by the Unicode bidi algorithm. This is used to determine whether the character is a left-to-right character or a right-to-left character.

ComponentAttribute and IconAttribute are used to specify a Component or an Icon that might be displayed instead of the character, but this replacement does not happen unless AbstractDocument.ComponentNameAttribute is set as well. The setComponent( ) and setIcon( ) methods therefore set this attribute in addition to ComponentAttribute or IconAttribute. (There is no special significance to the "Attribute" suffix of these constants.)

22.2.5.1.2 Paragraph attribute keys

Table 22-9 shows the constants and utility methods for paragraph attributes.

Table 22-9. StyleConstants: paragraph attribute values

Constant

Value type

Public static methods[10]

Default

Alignment
Integer
int getAlignment(aset) void setAlignment(maset, int a)
ALIGN_LEFT
[11]
FirstLineIndent
Float
float getFirstLineIndent(aset) void setFirstLineIndent(maset, float i)
0
LeftIndent
Float
float getLeftIndent(aset) void setLeftIndent(maset, float i)
0
LineSpacing
Float
float getLineSpacing(aset) void setLineSpacing(maset, float i)
0
Orientation
Not used
   
RightIndent
Float
float getRightIndent(aset) void setRighIndent(maset, float i)
0
SpaceAbove
Float
float getSpaceAbove(aset) void setSpaceAbove(maset, float i)
0
SpaceBelow
Float
float getSpaceBelow(aset) void setSpaceBelow(maset, float i)
0
TabSet
TabSet
[12]
TabSet getTabSet(aset) void setTabSet(maset, TabSet tabs)
null

[10] The first argument's type declaration is omitted for brevity. The type of aset in the get methods is AttributeSet. The type of maset in the set methods is MutableAttributeSet. If a get method is unable to find the appropriate attribute in the set, it returns the value in the Default column.

[11] See Table 22-11 for the values that can be assigned to this attribute.

[12] The TabSet class is described later in this chapter.

The Alignment attribute determines how the paragraph is justified. Table 22-11 lists values for left-, right-, center-, and full-justification. (Don't confuse Alignment with Orientation; StyleConstants mysteriously defines an attribute key for Orientation even though it is not used by Swing.)

The LeftIndent, RightIndent, SpaceAbove, and SpaceBelow attributes specify an amount of extra space (in points) on the left, right, top, and bottom of the paragraph, respectively. The LineSpacing attribute determines how much extra vertical space (in points) appears between consecutive lines of the paragraph.

The FirstLineIndent attribute determines how far (in points) to indent the first line of the paragraph with respect to the remaining lines. It may have a negative value to create a hanging indent.

The TabSet attribute is a collection of TabStops that determine the placement of text with embedded tab (\t) characters. A TabSet supports any number of left, right, centering, or decimal-aligned TabStops. We discuss the TabSet and TabStop classes later in this chapter.

22.2.5.1.3 Other attribute keys

Table 22-10 shows some keys for attributes that aren't used directly to specify the styling text. These attributes are for the convenience of classes that implement the AttributeSet/Style interfaces for storing extra information. StyleConstants does not provide any utility methods for these attributes.

Table 22-10. StyleConstants: other attribute values

Constant

Value type

Notes

ComposedTextAttribute

java.text.AttributedString

 

ModelAttribute

Object

Used for HTML form elements

NameAttribute

String

Another name for AttributeSet.NameAttribute

ResolveAttribute

AttributeSet

Another name for AttributeSet.ResolveAttribute

NameAttribute and ResolveAttribute have the same values as the like-named constants defined by the AttributeSet interface. An implementation of AttributeSet (and its subinterfaces) that chooses to store its ResolveParent or its name (remember that the Style interface defines a name property) as an attribute pair should use these constants as the attribute keys. This is exactly how SimpleAttributeSet is implemented.

ComposedTextAttribute is an attribute key that is coupled with a value of type java.text.AttributedString, which is a class that has its own way of storing styled text. If a renderer encounters this attribute, it delegates drawing to the static Utilities.drawComposedText( ) method.

ModelAttribute is used by HTMLEditorKit and JEditorPane to attach a component model (ListModel, ComboBoxModel, etc.) to an HTML form element.

22.2.5.2 Attribute value constants

StyleConstants also defines a few constants used as attribute values (not attribute keys). These are shown in Table 22-11.

Table 22-11. StyleConstants: attribute values not used as keys

Constant

Type

Associated attribute key

ALIGN_CENTER

int

Alignment

ALIGN_JUSTIFIED

int

Alignment

ALIGN_LEFT

int

Alignment

ALIGN_RIGHT

int

Alignment

ComponentElementName

String

AbstractDocument.ElementNameAttribute

IconElementName

String

AbstractDocument.ElementNameAttribute

The values of the Alignment paragraph attribute determine how any extra space is allocated to a line of text. The default is ALIGN_LEFT, which puts all the extra space on the right side, pushing the line of text against the left margin. ALIGN_RIGHT puts the extra space on the left, pushing the line of text to the right. ALIGN_CENTER puts equal space on the left and right, centering the text on the line. And finally, ALIGN_JUSTIFIED spreads out the extra space within the line of text, making the line flush with both the left and right margins.

ComponentElementName and IconElementName are character attribute values intended to be paired with AbstractDocument.ElementNameAttribute (a constant not provided by StyleConstants) as the attribute key. If the value of this attribute is ComponentElementName, the renderer attempts to display the value (a Component) of the ComponentAttribute attribute instead of the text. If the value of this attribute is IconElementName, the renderer attempts to display the value (an Icon) of the IconAttribute attribute instead of the text. In essence, this character attribute allows a Component or Icon to take the place of text (usually a single space) in the Document model. This attribute is set automatically by the setComponent( ) and setIcon( ) methods.

22.2.5.3 StyleConstants inner classes

StyleConstants has four public static inner classes that correspond to the four inner interfaces defined by AttributeSet. These are shown in Table 22-12.

Table 22-12. The public static inner classes of StyleConstants

Inner class name

Implements

Contains which attribute keys?

CharacterConstants

AttributeSet.CharacterAttribute

Listed in Table 22-8

ColorConstants

AttributeSet.ColorAttribute, AttributeSet.CharacterAttribute

Foreground, Background

FontConstants

AttributeSet.FontAttribute, AttributeSet.CharacterAttribute

FontFamily, FontSize, Bold, Italic[13]

ParagraphConstants

AttributeSet.ParagraphAttribute

Listed in Table 22-9

[13] Note that the names inside the inner class are Family, Size, Bold, and Italic.

You don't need to worry about these inner classes because all of the constant keys they define are also defined in the outer class. However, they can be used with the instanceof operator to identify the category of an attribute. For example:

if (someAttr instanceof AttributeSet.ParagraphAttribute)     doSomething( ); // Or the equivalent: if (someAttr instanceof StyleConstants.ParagraphAttribute) ... 

22.2.6 The StyleContext Class

A StyleContext is, in essence, a class that caches style-related objects. This can be important because styled documents tend to have many blocks of text with the same attributes, and we'd like each of these blocks to share a single AttributeSet object rather than have its own (but identical) AttributeSet.

Consider the common case in which you want to extend JTextPane to make it easy to append text in different colors. A naïve way to do this would be:

public class ColorPane extends JTextPane {   public void append(Color c, String s) { // Naive implementation     // Bad: instiantiates a new AttributeSet object on each call     SimpleAttributeSet aset = new SimpleAttributeSet( );     StyleConstants.setForeground(aset, c);        int len = getText( ).length( );     setCaretPosition(len); // Place caret at the end (with no selection).     setCharacterAttributes(aset, false);     replaceSelection(s); // There is no selection, so insert at caret.   } }

This does work, but it's not good code because it instantiates a SimpleAttributeSet object on every call. Suppose we want to display the numbers 1 through 400 in different colors (the primes in red, the perfect squares in blue, and the rest in black) in a ColorPane:

    ColorPane pane = new ColorPane( );     for (int n=1; n <= 400; n+=1) {       if (isPrime(n)) {         pane.append(Color.red, String.valueOf(n));       } else if (isPerfectSquare(n)) {         pane.append(Color.blue, String.valueOf(n));       } else {         pane.append(Color.black, String.valueOf(n));       }     }

This causes 400 SimpleAttributeSet objects to be instantiated, even though we're using only 3 colors. Object creation is not hugely expensive, but creating 397 objects we don't really need does chew up some processing time. More than that, the pane's Document might keep references to all 400 AttributeSets, preventing the garbage collector from reclaiming them until the Document itself is reclaimed.

This is where the StyleContext comes in. Instead of creating an AttributeSet ourselves, we simply ask the StyleContext for one. It creates a new object for us if it must, but first it sees if the AttributeSet we're looking for resides in its cache. If so, it gives us the cached AttributeSet so we can reuse it.

StyleContext isn't hard to use. Fixing ColorPane to use it[14] is only a two-line change:

[14] Another worthy change is to replace len = getText( ).length( ) with len = getDocument( ).getLength( ). This is because getText( ) has to perform a moderate amount of work to convert the pane's content into String form, and this work is wasted if we're interested only in its length. There's no reason not to make this change except that we don't introduce the Document interface until later in this chapter.

public class ColorPane extends JTextPane {   public void append(Color c, String s) { // Better implementation--uses StyleContext     StyleContext sc = StyleContext.getDefaultStyleContext( );     AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY,                                         StyleConstants.Foreground, c);        int len = getText( ).length( );     setCaretPosition(len);  // Place caret at the end (with no selection).     setCharacterAttributes(aset, false);     replaceSelection(s); // There is no selection, so insert at caret.   } }

With this change, the first time we ask for a blue attribute set (for number 1), StyleContext instantiates one for us and adds it to its cache. The next time we ask for a blue attribute set (for number 4), it returns the cached set. No new object is instantiated. The same goes for the red and black sets, so only three AttributeSets are instantiated during the processing of 400 (or even 4,000) numbers.

StyleContext caches three kinds of style-related objects: AttributeSets, Fonts, and named Styles. For AttributeSets and Fonts, it makes sense to use the "default" StyleContext, as we did in the code for ColorPane. The static getDefaultStyleContext( ) method returns a StyleContext that is intended to be shared, and it makes sense to share as much as possible.

For Styles, though, sharing demands some caution because of the possibility for name-clashes. For example, two documents may define a style named "headline" in completely different ways. Because of this, a Document that supports named styles (including a DefaultStyledDocument, which JTextPane usually uses) creates its own StyleContext instead of using the default.[15] So when dealing with named styles, it is safer to use the style methods provided by your text component (which delegates to its Document) or by your Document (which delegates to its StyleContext) instead of the default StyleContext.

[15] Each Swing text component has a Document, and every Document uses a StyleContext. For efficiency, the plainer Documents all use the default StyleContext. Although DefaultStyledDocument doesn't normally share styles in this way, it has constructors that allow you to specify which StyleContext it should use. So if you want named Styles to be shared, it is possible to pass the default StyleContext (or any other StyleContext) into the constructor.

It's worth mentioning a few things about StyleContext's font handling. First, there is a handy getFont( ) method that "retrieves" an actual java.awt.Font from an AttributeSet, even though AttributeSets store font information as separate attributes for font family, font size, bold, etc. Second, StyleContext does cache Font objects but doesn't cache FontMetrics objects, even though it has a getFontMetrics( ) method.

22.2.6.1 Properties

Table 22-13 shows the properties defined by the StyleContext class. The styleNames property provides access to the names of all Styles created by this StyleContext. The StyleContext starts out with one Style, which takes its name from the DEFAULT_STYLE constant and doesn't have any attributes. The emptySet property simply gives you SimpleAttributeSet.EMPTY, which is an immutable, empty AttributeSet that doesn't have any attributes. The changeListeners( ) property gives you an array containing the StyleContext's registered change listeners.

Table 22-13. StyleContext properties

Property

Data type

get

is

set

Default value

changeListeners1.4

ChangeListener[]

·

   

Empty array

emptySet

AttributeSet

·

   

SimpleAttributeSet.EMPTY

styleNames

Enumeration (each element is a String)

·

   

An Enumeration containing only the String constant DEFAULT_STYLE

1.4since 1.4

22.2.6.2 Events

A ChangeEvent is fired whenever a new Style is created by, or an existing Style is removed from, the StyleContext.[16]

[16] When attributes are added, changed, or removed from a Style, the Style itself notifies all listeners that registered with that Style. StyleContext fires a ChangeEvent only when an entire Style is added or removed.

The following two methods are provided for managing ChangeListeners:

public void addChangeListener(ChangeListener l)
public void removeChangeListener(ChangeListener l)
22.2.6.3 Constant

The StyleContext class defines the constant shown in Table 22-14.

Table 22-14. StyleContext constant

Name

Data type

Description

DEFAULT_STYLE

String

The name of the default (initially empty) style

22.2.6.4 Constructor
public StyleContext( )

Instantiate a StyleContext that contains only a default style with no attributes. Invoke the constructor when you want to create a brand new StyleContext instead of sharing an existing one, such as the one returned by getDefaultStyleContext( ).

22.2.6.5 Static default accessor method
public static final StyleContext getDefaultStyleContext( )

Return a StyleContext that is intended to be shared.

22.2.6.6 AttributeContext methods

These methods (along with the accessor for the emptySet property) implement the AbstractDocument.AttributeContext interface. This interface exists so that Documents can expose their font-caching methods.[17] StyleContext is the only class in Swing that implements this interface.

[17] They don't have to expose their named-style methods because the interfaces that support styles provide their own style methods. See Section 22.3.5 for more details.

public synchronized AttributeSet addAttribute(AttributeSet old, Object key, Object value)

This method returns an AttributeSet similar to old except that it has the specified value as the value for an attribute with the specified key. It tries to return a set in its cache. If it has to, it instantiates a new AttributeSet, adds it to the cache, and returns it. The value passed in for old may be an empty set, such as SimpleAttributeSet.EMPTY, or the set returned by getEmptySet( ).

This method is poorly named. It doesn't "add" anything to its AttributeSet argument, but it might create a slightly different AttributeSet and add it to the cache.

public synchronized AttributeSet addAttributes(AttributeSet old, AttributeSet attr)

This method is the same as AddAttribute( ) except that it finds or creates a set whose contents include the results of adding all the attributes defined in attr to old, not just a single new attribute.

public synchronized AttributeSet removeAttribute(AttributeSet old, Object key)

This method returns an AttributeSet similar to old except that it lacks an attribute with the specified key. It tries to return a set in its cache. If it has to, it instantiates a new AttributeSet, adds it to the cache, and returns it.

This method is poorly named. It doesn't "delete" anything from its AttributeSet argument, but it might create a smaller AttributeSet and add it to the cache.

public synchronized AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs)

This method is the same as RemoveAttribute( ) except that it finds or builds a set that includes only those attributes in old that have no matching value in attr. If you want attributes removed even if the values don't match, call removeAttributes(old.getAttributeNames( )) instead.

public synchronized AttributeSet removeAttributes(AttributeSet old, Enumeration names)

This method is the same as RemoveAttribute( ) except that it returns the result of removing all attributes with keys that match those found in names.

public synchronized void reclaim(AttributeSet a)

Signal that the AttributeSet is no longer used, and the StyleContext is free to remove it from its cache. This method was more important before the advent of weak references with SDK 1.2.

22.2.6.7 Font and color accessor methods
public Font getFont(String family, int style, int size)

This method returns a java.awt.Font with the requested font family, style, and size. It tries to return a Font in the cache. If it has to, it instantiates a new Font, adds it to the cache, and returns it. (The style parameter should be a bitwise "or" of Font.PLAIN, Font.BOLD, and Font.ITALIC.)

public Font getFont(AttributeSet attr)

Look up the font attributes defined in attr (FontFamily, FontSize, Bold, Italic, Subscript, and Superscript) and return an appropriate java.awt.Font. Caching behavior is similar to the three-argument getFont( ) method.

public FontMetrics getFontMetrics(Font f)

Equivalent to Toolkit.getDefaultToolkit.getFontMetrics(f). No caching is performed for FontMetrics objects, but subclasses may override this behavior.

public Color getForeground(AttributeSet attr)
public Color getBackground(AttributeSet attr)

Equivalent to StyleConstants.getForeground(attr) and StyleConstants.getBackground(attr). No caching is performed for Color objects, but subclasses may override this.

22.2.6.8 Style management methods
public Style addStyle(String name, Style parent)

Create a new, empty Style, add it to the cache, and return it. The name of the new style is name, which may be null to create an unnamed style.[18] The parent parameter (if not null) indicates that the existing Style used to resolve attributes is not found in the new Style.

[18] Unnamed styles are not cached nor can they be accessed by the getStyle( ) method.

This method is poorly named. It doesn't "add" anything to its Style argument, but it creates a new Style with the given name and resolving parent.

public Style getStyle(String name)

Return the cached Style with the given name or null if it isn't found.

public void removeStyle(String name)

Signal that the named Style is no longer used, and the StyleContext is free to remove it from its cache.

22.2.6.9 Serialization methods

The following static methods define a mechanism for reading and writing an AttributeSet to a stream. They are written so that the constant attribute keys defined in StyleConstants are recognized when the stream is read, allowing references to the existing singleton objects to be used instead of creating new instances. For example, when StyleConstants.Bold is encountered while a serialized AttributeSet is read, the AttributeSet uses the shared StyleConstants.Bold instance instead of creating its own instance of the key. This is critical because it allows the keys to be compared with the highly efficient reference equality (the == operator) rather than with a much more expensive equals( ) method.

public static void registerStaticAttributeKey(Object key)

Register an attribute key as a well-known key. When an attribute with the given key is written to a stream, a special marker is used so that it can be recognized when it is read back in. All attribute keys defined in StyleConstants are registered using this method. If you define additional attribute keys that you want to ensure will exist as singletons (nonreplicated objects), you should register them using this method. Such keys must not be Serializable, as this is how writeAttributeSet( ) determines if it needs to save the keys in a special format.

public static Object getStaticAttributeKey(Object key)

Return the special marker that can be used to represent key in serialization.

public static Object getStaticAttribute(Object skey)

Given skey, the special marker used in serialization, return the original key.

public static void writeAttributeSet(ObjectOutputStream out, AttributeSet a) throws IOException

Write the contents of the given set to the specified stream. Any non-Serializable keys are looked up in the set of keys registered by calls to the registerStaticAttributeKey( ) method. All attribute values must be Serializable.

public static void readAttributeSet(ObjectInputStream in, MutableAttributeSet a) throws ClassNotFoundException, IOException

Read a set of attributes from the given stream, adding them to the input set. When an attribute key that matches a key registered by a call to registerStaticAttributeKey( ) is read, the registered singleton key is returned.

public void readAttributes(ObjectInputStream in, MutableAttributeSet a) throws ClassNotFoundException, IOException
public void writeAttributes(ObjectOutputStream out, AttributeSet a) throws IOException

These methods simply call the static readAttributeSet( ) and writeAttributeSet( ) methods.

22.2.7 A Stylized Editor

Though we have yet to cover the Document model, we've learned enough to implement a substantial example that shows how to create Styles and apply them to paragraphs in a document. This mini word processor has the following features:

  • The user can define Styles using a simple dialog box that allows attributes such as font size, line spacing, bold, and italics to be specified.

  • The user can set the Style for the paragraph at the cursor position.

  • The user can modify a Style and see the changes reflected in all paragraphs using the modified Style.

This last item demonstrates why the Style interface includes methods for registering ChangeListeners. We'll have to write the code that modifies the Style, but when a Style changes, JTextPane redraws the affected text automatically.

The example consists of two classes: StyleFrame and StyleBox. StyleFrame is the main application frame. It contains a JTextPane for editing text and a JMenuBar that allows the user to create and modify Styles, and set the Style for the paragraph at the current cursor position. StyleBox is a simple dialog containing various JTextFields, JComboBoxes, and JCheckBoxes that allow the user to define several paragraph attributes.

We'll look at StyleBox first to get an idea of what the program can do. Figure 22-3 shows two sample StyleBoxes. The first shows the "default" Style that we get from the document when it's created. The second shows a specification for a "Title" Style that uses a large, bold font and places extra space above and below the text.

Figure 22-3. Editing Styles with StyleBox
figs/swng2.2203.gif

Here's the code for this class. There's a lot of code related to the creation and layout of all the data entry components. Feel free to ignore these details and concentrate on the fillStyle( ) and loadFromStyle( ) methods. These show how to set attributes on a Style object and how to retrieve attributes from an already populated Style (which we do when the user wants to modify a Style).

// StyleBox.java // import javax.swing.*; import javax.swing.text.*; import java.awt.*; public class StyleBox extends JPanel {   // Control panel that can be used to edit a style's paragraph attributes   private static final String[] fonts = {"Monospaced", "Serif", "SansSerif"};   private static final String[] sizes = {"8", "10", "12", "18", "24", "36"};   private JTextField nameField;   private JComboBox fontCombo, sizeCombo;   private JTextField leftField, rightField, aboveField, belowField;   private JCheckBox boldCheck, italicCheck;   public StyleBox( ) {     // Create the fields and lay them out.     super(new BorderLayout(4, 4));     JPanel labelPanel = new JPanel(new GridLayout(8, 1, 0, 2));     JPanel valuePanel = new JPanel(new GridLayout(8, 1, 0, 2));     add(labelPanel, BorderLayout.WEST);     add(valuePanel, BorderLayout.CENTER);     JLabel lab;     JPanel sidePanel;          lab = new JLabel("Style Name", SwingConstants.RIGHT);     labelPanel.add(lab);     nameField = new JTextField( );     lab.setLabelFor(nameField);     valuePanel.add(nameField);     lab = new JLabel("Font", SwingConstants.RIGHT);     labelPanel.add(lab);     fontCombo = new JComboBox(fonts);     fontCombo.setEditable(true); // User may enter custom value     lab.setLabelFor(fontCombo);     valuePanel.add(fontCombo);     lab = new JLabel("Size", SwingConstants.RIGHT);     labelPanel.add(lab);     sizeCombo = new JComboBox(sizes);     sizeCombo.setEditable(true); // User may enter custom value     lab.setLabelFor(sizeCombo);     sidePanel = new JPanel(new BorderLayout(4, 0));     sidePanel.add(sizeCombo, BorderLayout.CENTER);     sidePanel.add(new JLabel("points"), BorderLayout.EAST);     valuePanel.add(sidePanel);     lab = new JLabel("Left Indent", SwingConstants.RIGHT);     labelPanel.add(lab);     leftField = new JTextField( );     lab.setLabelFor(leftField);     sidePanel = new JPanel(new BorderLayout(4, 0));     sidePanel.add(leftField, BorderLayout.CENTER);     sidePanel.add(new JLabel("points"), BorderLayout.EAST);     valuePanel.add(sidePanel);     lab = new JLabel("Right Indent", SwingConstants.RIGHT);     labelPanel.add(lab);     rightField = new JTextField( );     lab.setLabelFor(rightField);     sidePanel = new JPanel(new BorderLayout(4, 0));     sidePanel.add(rightField, BorderLayout.CENTER);     sidePanel.add(new JLabel("points"), BorderLayout.EAST);     valuePanel.add(sidePanel);     lab = new JLabel("Space Above", SwingConstants.RIGHT);     labelPanel.add(lab);     aboveField = new JTextField( );     lab.setLabelFor(aboveField);     sidePanel = new JPanel(new BorderLayout(4, 0));     sidePanel.add(aboveField, BorderLayout.CENTER);     sidePanel.add(new JLabel("points"), BorderLayout.EAST);     valuePanel.add(sidePanel);     lab = new JLabel("Space Below", SwingConstants.RIGHT);     labelPanel.add(lab);     belowField = new JTextField( );     lab.setLabelFor(belowField);     sidePanel = new JPanel(new BorderLayout(4, 0));     sidePanel.add(belowField, BorderLayout.CENTER);     sidePanel.add(new JLabel("points"), BorderLayout.EAST);     valuePanel.add(sidePanel);     boldCheck = new JCheckBox("Bold");     italicCheck = new JCheckBox("Italic");     sidePanel = new JPanel(new GridLayout(1, 2));     sidePanel.add(boldCheck);     sidePanel.add(italicCheck);     valuePanel.add(sidePanel);     clear( ); // Sets initial values, etc.   }   public void clear( ) {     // Reset all fields (also set nameField to be editable).     nameField.setText("");     nameField.setEditable(true);     fontCombo.setSelectedIndex(0);     sizeCombo.setSelectedIndex(2);     leftField.setText("0.0");     rightField.setText("0.0");     aboveField.setText("0.0");     belowField.setText("0.0");     boldCheck.setSelected(false);     italicCheck.setSelected(false);   }   public String getStyleName( ) {     // Return the name of the style.     String name = nameField.getText( );     if (name.length( ) > 0)       return name;     else       return null;   }   public void fillStyle(Style style) {     // Mutate 'style' with the values entered in the fields (no value checking --     // could throw NumberFormatException).     String font = (String)fontCombo.getSelectedItem( );     StyleConstants.setFontFamily(style, font);     String size = (String)sizeCombo.getSelectedItem( );     StyleConstants.setFontSize(style, Integer.parseInt(size));     String left = leftField.getText( );     StyleConstants.setLeftIndent(style, Float.valueOf(left).floatValue( ));     String right = rightField.getText( );     StyleConstants.setRightIndent(style, Float.valueOf(right).floatValue( ));     String above = aboveField.getText( );     StyleConstants.setSpaceAbove(style, Float.valueOf(above).floatValue( ));     String below = belowField.getText( );     StyleConstants.setSpaceBelow(style, Float.valueOf(below).floatValue( ));     boolean bold = boldCheck.isSelected( );     StyleConstants.setBold(style, bold);     boolean italic = italicCheck.isSelected( );     StyleConstants.setItalic(style, italic);   }   // Load the form from an existing Style.   public void loadFromStyle(Style style) {     nameField.setText(style.getName( ));     nameField.setEditable(false); // Don't allow name change.     String fam = StyleConstants.getFontFamily(style);     fontCombo.setSelectedItem(fam);     int size = StyleConstants.getFontSize(style);     sizeCombo.setSelectedItem(Integer.toString(size));     float left = StyleConstants.getLeftIndent(style);     leftField.setText(Float.toString(left));     float right = StyleConstants.getRightIndent(style);     rightField.setText(Float.toString(right));     float above = StyleConstants.getSpaceAbove(style);     aboveField.setText(Float.toString(above));     float below = StyleConstants.getSpaceBelow(style);     belowField.setText(Float.toString(below));     boolean bold = StyleConstants.isBold(style);     boldCheck.setSelected(bold);     boolean italic = StyleConstants.isItalic(style);     italicCheck.setSelected(italic);   } } 

One class ties this whole example together. StyleFrame is a JFrame that provides a JTextPane for editing text and a JMenuBar for working with Styles and exiting the application. The Style menu (see Figure 22-4) contains two submenus (Set Logical Style and Modify Style) and one menu item (Create New Style). The submenus each contain a list of the Styles that have been created (plus default, the Style we get for free).

Figure 22-4. StyleFrame example menus
figs/swng2.2204.gif

The menu options function as follows:

Set Logical Style

When a Style is selected from this menu, the setLogicalStyle( ) method is called on the frame's JTextPane, which assigns the Style to the entire paragraph in which the caret is positioned.[19]

[19] We could add code to assign the Style to multiple highlighted paragraphs, but first we need to learn more about the Document model.

Modify Style

When a Style is selected from this submenu, the StyleBox is displayed and populated with the existing definition of the selected Style. Once changes are made and the dialog is closed, the existing Style is "refilled" by the StyleBox. This causes the Style to fire a property change event, which alerts its listeners to redraw all paragraphs that use the modified Style.

Create New Style

When this item is selected, a dialog box is displayed showing an empty StyleBox to define a new Style. When the dialog is closed, a new Style is created by calling addStyle( ) on the JTextPane. We then ask the StyleBox to fill the new Style with the entered data. Finally, the new Style is added to the Set Logical Style and Modify Style menus.

Here's the code for this class:

// StyleFrame.java // import javax.swing.*; import javax.swing.text.*; import java.awt.*; import java.awt.event.*; public class StyleFrame extends JFrame implements ActionListener {   // A JTextPane with a menu for Style manipulation   protected StyleBox styleBox;   protected JTextPane textPane;   protected JMenuBar menuBar;   protected JMenu applyStyleMenu, modifyStyleMenu;   protected JMenuItem createItem;   public StyleFrame( ) {     super("StyleFrame");     styleBox = new StyleBox( );     textPane = new JTextPane( );     getContentPane( ).add(new JScrollPane(textPane), BorderLayout.CENTER);     // Set up menu.     menuBar = new JMenuBar( );     JMenu styleMenu = new JMenu("Style");     menuBar.add(styleMenu);     setJMenuBar(menuBar);     applyStyleMenu = new JMenu("Set Logical Style");     applyStyleMenu.setToolTipText(         "set the Logical Style for the paragraph at caret location");     styleMenu.add(applyStyleMenu);     modifyStyleMenu = new JMenu("Modify Style");     modifyStyleMenu.setToolTipText(         "redefine a named Style (will affect paragraphs using that style)");     styleMenu.add(modifyStyleMenu);     createItem = new JMenuItem("Create New Style");     createItem.setToolTipText(         "define a new Style (which can then be applied to paragraphs)");     createItem.addActionListener(this);     styleMenu.add(createItem);     // Add the default style to applyStyleMenu and modifyStyleMenu.     createMenuItems(StyleContext.DEFAULT_STYLE);   }   protected void createMenuItems(String styleName) {     // Add 'styleName' to applyStyleMenu and modifyStyleMenu.     JMenuItem applyItem = new JMenuItem(styleName);     applyItem.addActionListener(this);     applyStyleMenu.add(applyItem);     JMenuItem modifyItem = new JMenuItem(styleName);     modifyItem.addActionListener(this);     modifyStyleMenu.add(modifyItem);   }   public void actionPerformed(ActionEvent e) {     // Determine which menuItem was invoked and process it.     JMenuItem source = (JMenuItem)e.getSource( );     if ( applyStyleMenu.isMenuComponent(source) ) {       // Apply an existing style to the paragraph at the caret position.       String styleName = source.getActionCommand( );       Style style = textPane.getStyle(styleName);       textPane.setLogicalStyle(style);     }     if ( source == createItem ) {       // Define a new Style and add it to the menus.       styleBox.clear( );       int response = JOptionPane.showConfirmDialog(this, styleBox,           "Style Editor", JOptionPane.OK_CANCEL_OPTION,           JOptionPane.PLAIN_MESSAGE);       if (response == JOptionPane.OK_OPTION &&           styleBox.getStyleName( ).length( ) > 0) {         String styleName = styleBox.getStyleName( );         Style style = textPane.addStyle(styleName, null);         styleBox.fillStyle(style);         createMenuItems(styleName); // Add new Style to the menus.       }       }     if ( modifyStyleMenu.isMenuComponent(source) ) {       // Redefine a Style (automatically redraws paragraphs using Style).       String styleName = source.getActionCommand( );       Style style = textPane.getStyle(styleName);       styleBox.loadFromStyle(style);       int response = JOptionPane.showConfirmDialog(this, styleBox,           "Style Editor", JOptionPane.OK_CANCEL_OPTION,           JOptionPane.PLAIN_MESSAGE);       if (response == JOptionPane.OK_OPTION) styleBox.fillStyle(style);     }   }   public static void main(String[] args) {     JFrame frame = new StyleFrame( );     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(400, 300);     frame.setVisible(true);   } }

Well, folks, that's it! With these two classes (fewer than 300 lines of code), we've created the beginnings of a potentially powerful Style-based text editor. Figure 22-5 shows several different Styles similar to the ones used in this book.

Figure 22-5. StyleFrame example
figs/swng2.2205.gif

With a little extra effort, we could add code to StyleFrame to support character styles (which could be applied to individual words within a paragraph) or to StyleBox to support derived styles (use the resolveParent to allow the user to say things like "this style is just like that style except for the foreground color").

22.2.8 The TabStop Class

We came across TabSet and TabStop back in our discussion of the SwingConstants class, but now let's take a closer look. TabStop, as you might guess, is used to describe a tab position. This information is used by the text View classes to correctly handle the display of tabs encountered in the Document model.

22.2.8.1 Properties

The TabStop class defines the properties listed in Table 22-15. The alignment property specifies how the text following a tab should be positioned relative to the tab. The legal values for this property are shown in Table 22-16. The leader property describes what should be displayed leading up to the tab. Legal values for this property are shown in Table 22-17, but this property is ignored by the rest of Swing,[20] so setting its value has no effect. The position property specifies the location of the tab (in pixels from the margin).

[20] Actually, there is one obscure corner of Swing that does respect the leader property: the code that knows how to write an RTF file to disk. Except for that, it is completely ignored (at least as of SDK 1.4.1).

Table 22-15. TabStop properties

Property

Data type

get

is

set

Default value

alignment

int

·

   

ALIGN_LEFT

leader[21]

int

·

   

LEAD_NONE

position

float

·

   

From constructor

[21] Swing ignores the value of this property.

22.2.8.2 Alignment constants

Table 22-16 lists the valid values for the alignment property. (See Figure 22-6 for an example of how they look.)

Figure 22-6. TabStop alignment
figs/swng2.2206.gif

Table 22-16. TabStop alignment constants

Constant

Data type

Description

ALIGN_BAR

int

Text after the tab starts at the tab position (the same behavior as ALIGN_LEFT).

ALIGN_CENTER

int

Text after the tab is centered over the tab's position.

ALIGN_DECIMAL

int

Text after the tab is aligned so that the first decimal point is located at the tab position. (If there is no decimal point, it behaves like ALIGN_RIGHT.)

ALIGN_LEFT

int

Text after the tab starts at the tab's position.

ALIGN_RIGHT

int

Text after the tab ends at the tab's position.

22.2.8.3 Leader constants

Table 22-17 lists values for the leader property and describes what they would do if this feature were implemented.

Table 22-17. TabStop leader constants

Constant

Description

LEAD_DOTS

Precede tab with a series of dots.

LEAD_EQUALS

Precede tab with a series of equal signs.

LEAD_HYPHENS

Precede tab with a series of hyphens.

LEAD_NONE

Precede tab with blank space.

LEAD_THICKLINE

Precede tab with a thick line.

LEAD_UNDERLINE

Precede tab with a thin line.

22.2.8.4 Constructors
public TabStop(float pos)

Create a TabStop with the specified position, an alignment of ALIGN_LEFT, and a leader of LEAD_NONE.

public TabStop(float pos, int align, int leader)

Create a TabStop with the specified position, alignment, and leader. (Again, the leader value is ignored by Swing.)

22.2.9 The TabSet Class

It is often useful to define a series of TabStops that should be applied to a given block of text. TabSet allows you to do this and defines a few convenient methods for looking up the TabStops contained in the set. TabSets are immutable once the TabStops are defined (in the constructor), they cannot be added or removed.

This class bundles a collection of TabStops so that they can be applied to a block of text using an AttributeSet. Note that even if you only want to set one TabStop, you still have to wrap it in a TabSet to use it.

22.2.9.1 Properties

The TabSet class defines the properties shown in Table 22-18. The indexed tab property is used to access a given TabStop while the tabCount property holds the number of TabStops defined in the set.

Table 22-18. TabSet properties

Property

Data type

get

is

set

Default value

tabi

TabStop

·

   

From constructor

tabCount

int

·

   

From constructor

iindexed

22.2.9.2 Constructor
public TabSet(TabStop tabs[])

Create a set containing the supplied array of TabStops.

22.2.9.3 Methods
public TabStop getTabAfter(float location)

Return the first TabStop past the given location or null if there are none.

public int getTabIndex(TabStop tab)

Return the index of the given TabStop or -1 if it isn't found.

public int getTabIndexAfter(float location)

Return the index of the first TabStop past the given location or -1 if there are none.

22.2.9.4 Example

Here's a quick example of using a TabSet in a JTextPane (Figure 22-6). It's as simple as it looks. The only thing to watch out for is to call setParagraphAttributes( ), not setCharacterAttributes( ), since tabs don't apply at the character level.

// TabExample.java // import javax.swing.*; import javax.swing.text.*; // Demonstrate a TabSet in a JTextPane. public class TabExample {   public static void main(String[] args) {     JTextPane pane = new JTextPane( );     TabStop[] tabs = new TabStop[4];     tabs[0] = new TabStop( 60, TabStop.ALIGN_RIGHT,   TabStop.LEAD_NONE);     tabs[1] = new TabStop(100, TabStop.ALIGN_LEFT,    TabStop.LEAD_NONE);     tabs[2] = new TabStop(200, TabStop.ALIGN_CENTER,  TabStop.LEAD_NONE);     tabs[3] = new TabStop(300, TabStop.ALIGN_DECIMAL, TabStop.LEAD_NONE);     TabSet tabset = new TabSet(tabs);     StyleContext sc = StyleContext.getDefaultStyleContext( );     AttributeSet aset =       sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabset);     pane.setParagraphAttributes(aset, false);     pane.setText("\tright\tleft\tcenter\tdecimal\n"                 +"\t1\t1\t1\t1.0\n"                 +"\t200.002\t200.002\t200.002\t200.002\n"         swing2IX.fm        +"\t.33\t.33\t.33\t.33\n");     JFrame frame = new JFrame("TabExample");     frame.setContentPane(new JScrollPane(pane));     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(360, 120);     frame.setVisible(true);   } }


Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net