22.4 Document Events


When changes are made to a Document, observers of the Document are notified by the event types DocumentEvent and UndoableEditEvent, defined in the javax.swing.event package. UndoableEditEvent and its associated listener interface are discussed in Chapter 18. In this section, we'll look at DocumentEvent and its relatives.

22.4.1 The DocumentListener Interface

Document uses this interface to notify its registered listeners of changes. It calls one of three methods depending on the category of change and passes a DocumentEvent to specify the details.

22.4.1.1 Methods
public void changedUpdate(DocumentEvent e)

Signal that an attribute or set of attributes has changed for some of the Document's content. The DocumentEvent specifies exactly which part of the Document is affected.

public void insertUpdate(DocumentEvent e)

Signal that text has been inserted into the Document. The DocumentEvent specifies which part of the Document's content is new.

public void removeUpdate(DocumentEvent e)

Signal that text has been removed from the Document. The DocumentEvent specifies where the text was located in the Document before it was deleted.

Suppose we want the parentheses matcher we wrote in the last section to update its colors "live" instead of waiting for the user to click on a button. All we have to do is register with the pane's Document as a DocumentListener. Whenever we're notified that text has been inserted or deleted, we recolor the parentheses. It's that easy.

We do have to be careful not to call run( ) directly from insertUpdate( ) or removeUpdate( ). This results in an IllegalStateException when our code attempts to obtain the Document's write lock. The solution is to avoid coloring the parentheses until the Document is finished with its event dispatching, which is easily done with SwingUtilities.invokeLater( ).

// LiveParenMatcher.java // import javax.swing.*; import javax.swing.text.*; import javax.swing.event.*; // Like ParenMatcher but continuously colors as the user edits the document public class LiveParenMatcher extends ParenMatcher implements DocumentListener {   public LiveParenMatcher( ) {     super( );     getDocument( ).addDocumentListener(this);   }   public void changedUpdate(DocumentEvent de) {     // No insertion or deletion, so do nothing   }   public void insertUpdate(DocumentEvent de) {     SwingUtilities.invokeLater(this); // Will call run( )   }   public void removeUpdate(DocumentEvent de) {     SwingUtilities.invokeLater(this); // Will call run( )   }   public static void main(String[] args) {     JFrame frame = new JFrame("LiveParenMatcher");     frame.setContentPane(new JScrollPane(new LiveParenMatcher( )));     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(300, 200);     frame.setVisible(true);   } }

If an insertion is made that doesn't contain any parentheses, brackets, or braces, it is wasteful to recolor the text because the colors don't change. A more sophisticated program would recognize this and not attempt to recolor. We'll add this feature to our LiveParenMatcher example in the next section.

22.4.2 The DocumentEvent Interface

A DocumentEvent is fired when a change is made to a Document. It contains information about the portion of the Document that was modified along with information about the details of the change.

Unlike most events, DocumentEvent is an interface rather than a class. This is so an undo-capable class (such as AbstractDocument) can create an event implementation that extends a class from the undo package and implements DocumentEvent. We'll see the details of how this works over the next few pages.

22.4.2.1 Properties

Table 22-27 shows the properties defined by the DocumentEvent interface. The document property is the Document object that fired this event. The offset and length properties specify where the change took place (the affected characters) relative to the beginning of the document. The type property indicates the kind of change that occurred. It is one of three constants from the Document.EventType inner class (described next): INSERT, REMOVE, CHANGE.

Table 22-27. DocumentEvent properties

Property

Data type

get

is

set

Default value

document

Document

·

     

length

int

·

     

offset

int

·

     

type

DocumentEvent.EventType

·

     

22.4.2.2 Element tree details
public DocumentEvent.ElementChange getChange(Element elem)

Use this method to discover what changes were made to the structure of the Document's Element tree. It returns an ElementChange object (described later in this chapter) that stores the child Elements that were added and/or deleted from the parent Element you passed in as an argument. If no child Elements were added or deleted, this method returns null, even if large changes were made to some of the child Elements.

Here's how we can change our insertUpdate( ) method to query the DocumentEvent and recolor the parentheses only when appropriate:

  public void insertUpdate(DocumentEvent de) {     Document doc = de.getDocument( );     int offset = de.getOffset( );     int length = de.getLength( );     String inserted = "";     try {       inserted = doc.getText(offset, length);     } catch (BadLocationException ble) { }     for (int j=0; j < inserted.length( ); j+=1) {       char ch = inserted.charAt(j);       if (ch == '(' || ch == '[' || ch == '{' ||           ch == ')' || ch == ']' || ch == '}'  ) {         SwingUtilities.invokeLater(this); // Will call run( )         return; // No need to check further       }     }   }

Note that this wouldn't work for removeUpdate( ). The values returned by getOffset( ) and getLength( ) refer to the content of the Document before the deletion occurred.

22.4.3 The DocumentEvent.EventType Class

This inner class is simply a type-safe enumeration used to define different types of DocumentEvents. It has no public constructors, so the only instances of the class are the three constants listed in Table 22-28.

Table 22-28. DocumentEvent.EventType constants

Constant

Type

Description

CHANGE

DocumentEvent.EventType

Attributes have changed.

INSERT

DocumentEvent.EventType

Content has been inserted.

REMOVE

DocumentEvent.EventType

Content has been removed.

22.4.4 The DocumentEvent.ElementChange Interface

This inner interface of DocumentEvent describes a set of Element changes made to a single parent Element. The changes may include adding new children, deleting existing children, or both (a replacement). An ElementChange is generated only when entire Elements are added or removed from a parent element. For example, merely deleting content from a child does not generate an ElementChange unless you remove the entire child Element.

22.4.4.1 Properties

Table 22-29 shows the properties defined by the DocumentEvent.ElementChange interface. The childrenAdded and childrenRemoved properties indicate the set of child Elements that was added to or removed from the Element represented by the element property. The array contents are in the order the Elements appear (or used to appear) within their parent Element. The index property indicates where the children were added or removed (an index into the Element's child list, not a document offset). If childrenAdded is empty, the index indicates the location of the first element removed; otherwise, it indicates the location of the first added element.

Table 22-29. DocumentEvent.ElementChange properties

Property

Data type

get

is

set

Default value

childrenAdded

Element[]

·

     

childrenRemoved

Element[]

·

     

element

Element

·

     

index

int

·

     

DocumentEvent.ElementChange is used in a small debugging example in the next section.

22.4.5 The ElementIterator Class

This utility class performs a depth-first traversal of an Element tree. It is similar to Enumeration in concept, but there are differences. In particular, ElementIterator has separate methods for retrieving the first and subsequent Elements.

22.4.5.1 Constructors
public ElementIterator(Document document)

Create an iterator that begins at the given Document's default root element.

public ElementIterator(Element root)

Create an iterator that begins at the specified root Element.

22.4.5.2 Methods
public Element first( )

Reset the iterator to the first Element and return it.

public Element next( )

Move the iterator to the next Element and return it. If there are no more Elements, return null.

public Element current( )

Return the current Element in the iterator.

public int depth( )

Return the depth of the current Element. The depth of the iterator's root is 1, the depth of the root's children is 2, and so on.

public Element previous( )

Return the previous Element. It does not change the state of the iterator, so calling next( ); previous( ); next( ); previous( ); moves the iterator forward by two Elements.

public Object clone( )

Return a copy of the iterator. The clone is positioned at the same point as the original iterator.

In this debugging example, we use an ElementIterator to search for non-null ElementChange objects:

  public void removeUpdate(DocumentEvent de) {     // Print some debugging information.     ElementIterator iter = new ElementIterator(de.getDocument( ));     for (Element elem = iter.first( ); elem != null; elem = iter.next( )) {       DocumentEvent.ElementChange change = de.getChange(elem);       if (change != null) { // null means there was no change in elem         System.out.println("Element "+elem.getName( ) + " (depth " +             iter.depth( )+") changed its children: " +             change.getChildrenRemoved( ).length+" children removed, " +             change.getChildrenAdded( ).length+" children added.\n");       }     }   }

22.4.6 The Segment Class

We saw the Segment class back in the Document interface's getText( ) method. It is essentially a char array that allows fast access to a segment of text. The motivation is entirely speed. To this end, it breaks some fundamental rules of object-oriented programming by exposing its data as public fields. If a data source already has the requested text in a char array, it can just replace the Segment's array with its own (so you will be sharing it) and set the index fields appropriately.

Think of using Segment as you would String. Strings are immutable once the constructor is done with them, and you should treat Segments this way too, at least most of the time. When accessing a Segment, use the retrieval methods instead of accessing the array directly.

22.4.6.1 Properties

Table 22-30 shows the properties defined by Segment. The beginIndex and endIndex properties define the portion of the char array that contains meaningful data. The index property represents the current position in the array and is manipulated by the retrieval methods. The partialReturn property allows you to trade convenience for speed. If you set this property to true, you get only as much text as can be managed without copying, which may be less than you asked for. (You can always ask for the rest later.)

Table 22-30. Segment properties

Property

Data type

get

is

set

Default value

beginIndex

int

·

     

endIndex

int

·

     

index

int

·

 

·

 

partialReturn1.4

boolean

 

·

·

false

1.4since 1.4

22.4.6.2 Constant

Segment defines the constant shown in Table 22-31.

Table 22-31. DefaultStyledDocument constant

Constant

Data type

Description

DONE

char

Char value returned when index is out of bounds (same value as java.text.CharacterIterator.DONE)

22.4.6.3 Fields
public char array[]

The array of characters used to store the data. It should not be modified.

public int offset

The offset into the array that represents the beginning of the meaningful text.

public int count

The number of characters that comprise the meaningful text.

22.4.6.4 Constructors
public Segment( )

Create a Segment with no data.

public Segment(char array[], int offset, int count)

Create a Segment that references an existing array. The offset and count delimit the portion of the array that contains meaningful text.

22.4.6.5 Methods
public char current( )

Return the char at the location specified by the index property.

public char next( )

Increment the index property and return the char at the new location. (If positioned at endIndex, this method returns DONE instead.)

public char previous( )

Decrement the index property and return the char at the new location. (If positioned at beginIndex, this method returns DONE instead.)

public char first( )

Set index to the value of the beginIndex property and return the char at the new location.

public char last( )

Set index to the value of the endIndex property and return the char at the new location.

public String toString( )

Return the Segment as a String of length count.

public Object clone( )

Create a shallow copy of the Segment. (That is, the char array is shared.)

We can now reimplement our insertUpdate( ) method to use a Segment when it queries the Document for the inserted text. It's pretty straightforward.

  public void insertUpdate(DocumentEvent de) {     Document doc = de.getDocument( );     int offset = de.getOffset( );     int length = de.getLength( );     Segment seg = new Segment( );     try {       doc.getText(offset, length, seg); // Text placed in Segment     } catch (BadLocationException ble) { }     // Iterate through the Segment.     for (char ch = seg.first( ); ch != seg.DONE; ch = seg.next( ))       if (ch == '(' || ch == '[' || ch == '{' ||           ch == ')' || ch == ']' || ch == '}'  ) {         SwingUtilities.invokeLater(this); // Will call run( )         return; // No need to check further       }   } 

22.4.7 The AbstractDocument.Content Interface

We briefly mentioned this interface earlier, but let's take a closer look. The five methods of this interface define the API that AbstractDocument uses to actually store its text content. Many methods in AbstractDocument simply delegate to its Content model. (We discuss Swing's two implementations of this interface, StringContent and GapContent, next.)

22.4.7.1 Methods
public int length( )

Return the current length of the content.

public String getString(int where, int len) throws BadLocationException

Return a String containing the specified range of text.

public void getChars(int where, int len, Segment txt) throws BadLocationException

Place the specified range of text in the supplied Segment object. This is often faster than calling getString( ).

public UndoableEdit insertString(int where, String str) throws BadLocationException

Insert the specified string at the requested offset in the content. If the Content model supports undo, an UndoableEdit object is returned; otherwise, this method returns null.

public UndoableEdit remove(int where, int len) throws BadLocationException

Remove the specified range of characters from the document content. If the Content model supports undo, an UndoableEdit object is returned; otherwise, this method returns null.

public Position createPosition(int offset) throws BadLocationException

Create a Position that tracks the content at the specified offset as text is inserted or deleted over time.

22.4.8 The StringContent Class

The StringContent class is an implementation of the AbstractDocument.Content interface that maintains its content in consecutive cells of a character array. This is very inefficient because part of the array must be copied every time a character is inserted or deleted (except at the very end). This was the default Content model for all text components in the early days of Swing, before GapContent was perfected.

StringContent does support undoable edits, so the insertString( ) and remove( ) methods always return non-null UndoableEdit objects. It expands its character array (by doubling) when it gets too small and keeps its Position objects in a Vector that it updates when necessary.

22.4.8.1 Constructors
public StringContent( )

Create an array with a default initial size (10) containing a single newline character.

public StringContent(int initialLength)

Create an array with the specified initial size containing a single newline character.

22.4.9 The GapContent Class

The GapContent class is an implementation of the AbstractDocument.Content interface and is the default Content model. Its inspiration was the venerable Emacs text editor, which employs a similar storage scheme for its buffers.

GapContent takes advantage of the fact that text is typically inserted sequentially. In other words, if the user inserts a character at position 10 in the document, chances are good that the next insertion is at position 11. With StringContent, each insertion results in an array copy to make room for the new text. In contrast, GapContent keeps a "gap" in its character array, located at the current insertion point. When text is entered, the gap gets smaller, but no characters need to be copied in the array (except the newly entered ones). When the insertion point moves one slot left or right, only a single character has to be copied to maintain the gap at the insertion point.

Like StringContent, GapContent expands its array when necessary and supports undoable edits. The insertString( ) and remove( ) methods always return non-null UndoableEdit objects. GapContent keeps its Position objects sorted so it can easily find the marks that need to be updated when the gap is shifted.

22.4.9.1 Constructors
public GapContent( )

Create a content object with a default initial array size (10) containing a single newline character.

public GapContent(int initialLength)

Create a content object with the specified initial array size containing a single newline character.

22.4.9.2 AbstractDocument.Content methods

The following methods implement the AbstractDocument.Content interface:

public Position createPosition(int offset) throws BadLocationException

Create a Position at the specific document offset. This implementation manages a sorted array of positions to make it easy to find the marks that need to be updated when the gap is shifted.

public void getChars(int where, int len, Segment chars) throws BadLocationException

Populate the given Segment with the requested text (len characters, starting at where). If the requested text falls entirely on one side of the gap, the Segment refers to the internal array. If not, the result depends on the value of the Segment's partialReturn property. If partialReturn is false (the default), a new array that contains the specified range with no gap is created for the Segment. If it is true, the Segment refers to the internal array, but only the characters below the gap are available through the Segment. (Examine the Segment's count field to see how many characters are available.)

public String getString(int where, int len) throws BadLocationException

Return the requested portion of the content in String form.

public UndoableEdit insertString(int where, String str) throws BadLocationException

Insert the specified text at the given location. The gap and Positions are adjusted as necessary. This method returns an UndoableEdit that can be used to restore the content to its previous state.

public int length( )

Return the length of the content. The gap does not count in this value.

public UndoableEdit remove(int where, int nitems) throws BadLocationException

Remove the specified range of characters from the content. The gap and Positions are updated as necessary. This method returns an UndoableEdit that can be used to restore the content to its previous state.

22.4.10 Undo Event Example

We learned about Swing's undo classes in Chapter 18, but let's see an undo example with text components. All we have to do is register with the Document as an UndoableEditListener, and we'll automatically receive UndoableEditEvents.

In this example, we subclass the StyleFrame program in Section 22.2.7 to add menu items and buttons for undo/redo. We create a pair of Action objects as inner classes that are in charge of enabling/disabling the menu items and button. We also register the Actions as UndoableEditListeners so they can update themselves. We do this through calls to textPane.getDocument( ).addUndoableEditListener( ).

So when you type characters, the menu item and the button say Undo addition. When you delete characters, they say Undo deletion. And when you change the logical style of a paragraph, they say Undo style change. It just works. (See Figure 22-10.)

Figure 22-10. Undo in a JTextFrame
figs/swng2.2210.gif
// UndoStyleFrame.java // import javax.swing.*; import javax.swing.undo.*; import javax.swing.event.*; import java.awt.event.*; // Add undo support to the StyleFrame example (keep just the most recent edit to keep // things simple). public class UndoStyleFrame extends StyleFrame {   protected UndoAct undoAction = new UndoAct( ); // An Action for undo   protected RedoAct redoAction = new RedoAct( ); // An Action for redo   public UndoStyleFrame( ) {     super( );     setTitle("UndoStyleFrame");     // Register the Actions as undo listeners (we inherited textPane).     textPane.getDocument( ).addUndoableEditListener(undoAction);     textPane.getDocument( ).addUndoableEditListener(redoAction);     // Create menu for undo/redo.     JMenu editMenu = new JMenu("Edit");     editMenu.add(new JMenuItem(undoAction));     editMenu.add(new JMenuItem(redoAction));     menuBar.add(editMenu); // we inherited menuBar from superclass     // Create buttons for undo/redo.     JPanel buttonPanel = new JPanel( );     buttonPanel.add(new JButton(undoAction));     buttonPanel.add(new JButton(redoAction));     getContentPane( ).add(buttonPanel, java.awt.BorderLayout.SOUTH);   }   // Begin inner classes ------------      public class UndoAct extends AbstractAction implements UndoableEditListener {     private UndoableEdit edit;     public UndoAct( ) {       super("Undo");       setEnabled(false);     }     public void updateEnabled( ) {       setEnabled(edit.canUndo( ));     }     public void undoableEditHappened(UndoableEditEvent event) {       edit = event.getEdit( );       putValue(NAME, edit.getUndoPresentationName( ));       updateEnabled( );     }     public void actionPerformed(ActionEvent ae) {       edit.undo( );       updateEnabled( ); // Disable undo.       redoAction.updateEnabled( ); // Enable redo.     }   }      public class RedoAct extends AbstractAction implements UndoableEditListener {     private UndoableEdit edit;     public RedoAct( ) {       super("Redo");       setEnabled(false);     }     public void updateEnabled( ) {       setEnabled(edit.canRedo( ));     }     public void undoableEditHappened(UndoableEditEvent event) {       edit = event.getEdit( );       putValue(NAME, edit.getRedoPresentationName( ));       updateEnabled( );     }     public void actionPerformed(ActionEvent ae) {       edit.redo( );       updateEnabled( ); // Disable redo.       undoAction.updateEnabled( ); // Enable undo.     }   }      // End inner classes ------------   public static void main(String[] args) {     JFrame frame = new UndoStyleFrame( );     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setSize(400, 300);     frame.setVisible(true);   } }

22.4.11 The BadLocationException Class

This exception is thrown by many of the text classes to indicate that an attempt has been made to access an invalid Document offset.

22.4.11.1 Constructor
public BadLocationException(String message, int offset)

Create a new exception as a result of an attempt to access the specified offset.

22.4.11.2 Method
public int offsetRequested( )

Return the offending offset that caused the exception to be thrown.

22.4.12 The Position Interface

Position is an interface used to represent a location in a Document. Positions are intended to have more lasting value than simple Document offsets. For example, if you set a Position at the beginning of a sentence and then insert text before the sentence, the Position is still located at the beginning of the sentence, even though its offset has changed.

22.4.12.1 Property

Table 22-32 shows the property defined by the Position interface. The offset property indicates the Position's current offset from the start of the Document. The only method in this interface is the accessor for this property, getOffset( ).

Table 22-32. Position property

Property

Data type

get

is

set

Default value

offset

int

·

     

22.4.13 The Position.Bias Class

This is a very simple static inner class used to define a type-safe enumeration. The values of the enumeration indicate a position's bias toward the character before or after a location. The idea is that there is slightly more detail in saying "the user clicked nearest offset 14 from the left" versus "the user clicked nearest offset 14." Bias can also be useful with bidirectional text.

Though a fair number of Swing's text methods take an argument of type Position.Bias, support for it is spotty. Most of those methods ignore the Bias argument.

22.4.13.1 Constants

As a type-safe enumeration, Position.Bias has no public constructors, so the only instances of the class are the two constants listed in Table 22-33.

Table 22-33. Position.Bias constants

Constant

Data type

Description

Backward

Position.Bias

A bias toward the previous character in the model

Forward

Position.Bias

A bias toward the next character in the model



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