I l@ve RuBoard |
![]() ![]() |
22.2 AttributeSets and StylesAttributeSet 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.
Figure 22-2. High-level AttributeSet class diagram![]() 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.
22.2.1 The AttributeSet InterfaceAttributeSet 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 PropertiesTable 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.
22.2.1.2 ConstantsAttributeSet defines the constants shown in Table 22-3.
22.2.1.3 Methods
22.2.1.4 Inner interfacesAttributeSet 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.
22.2.2 The MutableAttributeSet InterfaceMutableAttributeSet 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 PropertyMutableAttributeSet 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.)
22.2.2.2 Methods
22.2.3 The Style InterfaceStyle 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 PropertyTable 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.
22.2.3.2 ConstantsStyle 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 EventsWhen 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: 22.2.4 The SimpleAttributeSet ClassSwing 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 PropertiesTable 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.
22.2.4.2 ConstantIn addition to those it inherits from the AttributeSet interface, SimpleAttributeSet defines the constant shown in Table 22-7.
22.2.4.3 Constructors
22.2.4.4 Add/remove methods
22.2.4.5 Query methods
22.2.5 The StyleConstants ClassThis 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:
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 methodsUsually, 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 keysTable 22-8 shows the constants and utility methods for character attributes.
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 keysTable 22-9 shows the constants and utility methods for paragraph attributes.
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 keysTable 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.
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 constantsStyleConstants also defines a few constants used as attribute values (not attribute keys). These are shown in Table 22-11.
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 classesStyleConstants has four public static inner classes that correspond to the four inner interfaces defined by AttributeSet. These are shown in Table 22-12.
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 ClassA 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:
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.
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 PropertiesTable 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.
22.2.6.2 EventsA ChangeEvent is fired whenever a new Style is created by, or an existing Style is removed from, the StyleContext.[16]
The following two methods are provided for managing ChangeListeners:
22.2.6.3 ConstantThe StyleContext class defines the constant shown in Table 22-14.
22.2.6.4 Constructor
22.2.6.5 Static default accessor method
22.2.6.6 AttributeContext methodsThese 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.
22.2.6.7 Font and color accessor methods
22.2.6.8 Style management methods
22.2.6.9 Serialization methodsThe 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.
22.2.7 A Stylized EditorThough 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:
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![]() 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![]() The menu options function as follows:
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![]() 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 ClassWe 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 PropertiesThe 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).
22.2.8.2 Alignment constantsTable 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![]()
22.2.8.3 Leader constantsTable 22-17 lists values for the leader property and describes what they would do if this feature were implemented.
22.2.8.4 Constructors
22.2.9 The TabSet ClassIt 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 PropertiesThe 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.
22.2.9.2 Constructor
22.2.9.3 Methods
22.2.9.4 ExampleHere'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); } } ![]() |
I l@ve RuBoard |
![]() ![]() |