I l@ve RuBoard |
![]() ![]() |
21.1 CaretsCarets represent the location where new text is inserted. 21.1.1 The Caret InterfaceThe Caret interface provides a number of useful features for dealing with text insertion and selection. 21.1.1.1 PropertiesThe Caret interface defines the properties shown in Table 21-1. The blinkRate property specifies the number of milliseconds between Caret blinks. A value of 0 indicates that the Caret shouldn't blink at all.
The dot property is the current Caret position as an offset into the Document model. The mark is the other end of the current selection. If there is no selection, the value of mark is the same as dot. The selectionVisible property designates whether the current selection (if any) should be decorated by the component's Highlighter. The visible property indicates whether the Caret itself should be visible. This is almost always true when the Caret's text component is editable and has focus but may not be in other situations. magicCaretPosition is a Point used when moving among lines with uneven end positions to ensure that the up and down arrow keys produce the desired effect. For example, consider the following text: Line 1 is long Line 2 Line 3 is long If the caret was initially positioned before the o in long in line 3, you'd expect the up arrow key to move the caret to the end of line 2. A second up arrow press should move the caret just before the o in long on the first line. This is where the "magic" comes in. The first time the up arrow is pressed, magicCaretPosition is set to the old caret location so that this position's x-coordinate can be used if the up arrow is pressed again. You will probably never need to do anything with this property since the DefaultEditorKit manages it. 21.1.1.2 EventsWhenever the Caret's position changes, a ChangeEvent should be fired to any interested ChangeListeners. Caret defines the following standard methods for managing event listeners:
21.1.1.3 MethodsIn addition to the accessors for the properties listed earlier, the Caret interface defines the following four methods:
21.1.2 The DefaultCaret ClassThe DefaultCaret class provides a basic implementation of the Caret interface that renders itself as a thin vertical line. This class extends Rectangle; implements the FocusListener, MouseListener, and MouseMotionListener interfaces; and reacts to events sent to these listeners when they are fired by its installed JTextComponent. As we'll see, extending DefaultCaret is a great way to create your own Caret without having to worry about most of the complicated details. 21.1.2.1 PropertiesDefaultCaret does not add any properties to the Caret interface except the ones it inherits from java.awt.Rectangle. Table 21-2 shows the default values it supplies.
21.1.2.2 EventsA ChangeEvent is fired to registered listeners whenever the caret's position changes. The following standard methods are provided for working with ChangeEvents. (Note that the getChangeListeners( ) method did not exist prior to SDK 1.4, and getListeners( ) did not exist prior to SDK 1.3.)
21.1.2.3 Constructor
21.1.2.4 Caret methodsDefaultCaret provides the following implementations of the methods defined by the Caret interface:
21.1.2.5 FocusListener methodsThese methods are implemented from the FocusListener interface:
21.1.2.6 Mouse methodsThese methods are implemented from the MouseListener and MouseMotionListener interfaces. They define how DefaultCaret behaves in response to mouse gestures.
21.1.2.7 Protected methodsIn addition to implementing all of the methods defined in the Caret interface, DefaultCaret adds several useful methods of its own:
21.1.3 Custom CaretsLet's take a crack at creating our own Caret. The typical way to create a custom caret is to extend DefaultCaret and override paint( ) and damage( ). That's what we'll do for CornerCaret, a simple five-pixel-by-five-pixel, L-shaped caret, but we'll also add a constructor to make it blink by default. To use this new caret, simply call setCaret(new CornerCaret( )) on any Swing text component. (The main( ) method is provided for demonstration purposes only. CornerCaret would be complete without it.) // CornerCaret.java // import javax.swing.*; import javax.swing.text.*; import java.awt.*; public class CornerCaret extends DefaultCaret { public CornerCaret( ) { setBlinkRate(500); // Half a second } protected synchronized void damage(Rectangle r) { if (r == null) return; // Give values to x,y,width,height (inherited from java.awt.Rectangle). x = r.x; y = r.y + (r.height * 4 / 5 - 3); width = 5; height = 5; repaint( ); // Calls getComponent( ).repaint(x, y, width, height) } public void paint(Graphics g) { JTextComponent comp = getComponent( ); if (comp == null) return; int dot = getDot( ); Rectangle r = null; try { r = comp.modelToView(dot); } catch (BadLocationException e) { return; } if (r == null) return; int dist = r.height * 4 / 5 - 3; // Will be distance from r.y to top if ( (x != r.x) || (y != r.y + dist) ) { // paint( ) has been called directly, without a previous call to // damage( ), so do some cleanup. (This happens, for example, when the // text component is resized.) repaint( ); // Erase previous location of caret. x = r.x; // Set new values for x,y,width,height. y = r.y + dist; width = 5; height = 5; } if ( isVisible( ) ) { g.setColor(comp.getCaretColor( )); g.drawLine(r.x, r.y + dist, r.x, r.y + dist + 4); // Five vertical pixels g.drawLine(r.x, r.y + dist + 4, r.x + 4, r.y + dist + 4); // Five horiz px } } public static void main(String args[]) { JFrame frame = new JFrame("CornerCaret demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTextArea area = new JTextArea(8, 32); area.setCaret(new CornerCaret( )); area.setText("This is the story\nof the hare who\nlost his spectacles."); frame.getContentPane( ).add(new JScrollPane(area), BorderLayout.CENTER); frame.pack( ); frame.setVisible(true); } } There are several things worth mentioning here. First, damage( ) gets a Rectangle directly as a parameter, but paint( ) has to obtain one manually via getComponent( ).modelToView( ) (or getComponent( ).getUI( ).modelToView( )). Despite this, the fields of the rectangles are the same. The x and y fields are the coordinates of the cursor, though y is at the top of the text, not the baseline. The value of height depends on the font, but the value of width is meaningless (probably 0). The paint( ) method should check whether the cursor is visible before drawing anything and is expected to honor the value of the component's caretColor property. Because the caret is drawn "over" (after) the component's content, large cursors must take care not to obscure the text underneath. (One way to handle this is to draw in XOR [eXclusive OR] mode.) See Figure 21-2. Figure 21-2. Demonstrations of the CornerCaret and FancyCaret![]() The damage( ) method is responsible for setting the value of the caret's x, y, width, and height fields (inherited from Rectangle) to cover anything that is drawn by paint( ). If not, only part (or possibly none) of the caret actually appears after paint( ) is called, or caret fragments may be left behind when the caret moves. Finally, damage( ) calls the caret's repaint( ) method, which eventually causes the paint( ) method to be called. FancyCaret is more complicated than CornerCaret because it is rendered using the width of the character it is on. It also draws the caret in XOR mode, which allows the text to show through the caret. When a pixel is drawn in XOR mode, its new color does not necessarily become the drawing color. Instead, its new color becomes a mix of its previous color and the drawing color. If the previous color is the same as the drawing color, the new color is the XOR color. If the previous color is the same as the XOR color, the new color is the drawing color. If the previous color is a third color, the new color is some undefined (but reasonable) other color. FancyCaret takes advantage of this by setting the drawing color to the caret color and the XOR color to the component's background color. Here's the code: // FancyCaret.java // import javax.swing.*; import javax.swing.text.*; import java.awt.*; public class FancyCaret extends DefaultCaret { protected synchronized void damage(Rectangle r) { if (r == null) return; // Give values to x,y,width,height (inherited from java.awt.Rectangle). x = r.x; y = r.y; height = r.height; // A value for width was probably set by paint( ), which we leave alone. But the // first call to damage( ) precedes the first call to paint( ), so in this case we // must be prepared to set a valid width or else paint( ) receives a bogus clip // area, and caret is not drawn properly. if (width <= 0) width = getComponent( ).getWidth( ); repaint( ); // Calls getComponent( ).repaint(x, y, width, height) } public void paint(Graphics g) { JTextComponent comp = getComponent( ); if (comp == null) return; int dot = getDot( ); Rectangle r = null; char dotChar; try { r = comp.modelToView(dot); if (r == null) return; dotChar = comp.getText(dot, 1).charAt(0); } catch (BadLocationException e) { return; } if ( (x != r.x) || (y != r.y) ) { // paint( ) has been called directly, without a previous call to // damage( ), so do some cleanup. (This happens, for example, when the // text component is resized.) repaint( ); // Erase previous location of caret. x = r.x; // Update dimensions (width is set later in this method). y = r.y; height = r.height; } g.setColor(comp.getCaretColor( )); g.setXORMode(comp.getBackground( )); // Do this to draw in XOR mode. if (dotChar == '\n') { int diam = r.height; if (isVisible( )) g.fillArc(r.x-diam/2, r.y, diam, diam, 270, 180); // Half-circle width = diam / 2 + 2; return; } if (dotChar == '\t') try { Rectangle nextr = comp.modelToView(dot+1); if ((r.y == nextr.y) && (r.x < nextr.x)) { width = nextr.x - r.x; if (isVisible( )) g.fillRoundRect(r.x, r.y, width, r.height, 12, 12); return; } else dotChar = ' '; } catch (BadLocationException e) { dotChar = ' '; } width = g.getFontMetrics( ).charWidth(dotChar); if (isVisible( )) g.fillRect(r.x, r.y, width, r.height); } public static void main(String args[]) { JFrame frame = new JFrame("FancyCaret demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTextArea area = new JTextArea(8, 32); area.setCaret(new FancyCaret( )); area.setText("VI\tVirgin Islands \nVA Virginia\nVT\tVermont"); frame.getContentPane( ).add(new JScrollPane(area), BorderLayout.CENTER); frame.pack( ); frame.setVisible(true); } } The paint( ) method uses g.getFontMetrics( ).charWidth( ) to determine how wide to render the caret,[1] but this complicates things. The damage( ) method can't call charWidth( ) because it doesn't have access to a Graphics object, so it relies on paint( ) to set the width field. At first glance, it seems that this shouldn't work because paint( ) is called after damage( ), not before. But in practice, paint( ) is called twice when the caret moves: once before damage( ) (this one doesn't actually draw anything because its clip area is not set correctly) and once after (this one does draw the caret).
FontMetrics.charWidth( ) returns 0 for tabs and newlines, so they need special handling. FancyCaret shows off by drawing a half-circle for a newline and a rounded rectangle for a tab. This could be considered overkill, but it is important that something with positive width is drawn or else the caret disappears when the user moves it onto a tab or newline, which can be disorienting. 21.1.4 The CaretListener InterfaceIf you want to keep track of the position of a Caret in a JTextComponent, you don't actually need to interact directly with the component's Caret. Instead, you can simply add a CaretListener to the component. 21.1.4.1 MethodThe CaretListener interface contains one method: 21.1.5 The CaretEvent ClassAs you'd expect, there's a CaretEvent class to go along with the CaretListener we just introduced. This is actually an abstract class. The only concrete subclass is a package-private inner class within JTextComponent. 21.1.5.1 PropertiesCaretEvent defines the properties shown in Table 21-3. dot indicates the current caret location while mark shows the end of the selection, if there is one (otherwise, it is the same as dot).
21.1.5.2 Constructor
|
I l@ve RuBoard |
![]() ![]() |