I l@ve RuBoard |
![]() ![]() |
22.4 Document EventsWhen 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 InterfaceDocument 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
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 InterfaceA 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 PropertiesTable 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.
22.4.2.2 Element tree details
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 ClassThis 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.
22.4.4 The DocumentEvent.ElementChange InterfaceThis 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 PropertiesTable 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.
DocumentEvent.ElementChange is used in a small debugging example in the next section. 22.4.5 The ElementIterator ClassThis 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
22.4.5.2 Methods
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 ClassWe 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 PropertiesTable 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.)
22.4.6.2 ConstantSegment defines the constant shown in Table 22-31.
22.4.6.3 Fields
22.4.6.4 Constructors
22.4.6.5 Methods
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 InterfaceWe 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
22.4.8 The StringContent ClassThe 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
22.4.9 The GapContent ClassThe 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
22.4.9.2 AbstractDocument.Content methodsThe following methods implement the AbstractDocument.Content interface:
22.4.10 Undo Event ExampleWe 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![]() // 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 ClassThis 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
22.4.11.2 Method22.4.12 The Position InterfacePosition 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 PropertyTable 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( ).
22.4.13 The Position.Bias ClassThis 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 ConstantsAs 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.
|
I l@ve RuBoard |
![]() ![]() |