21.1 Carets


Carets represent the location where new text is inserted.

21.1.1 The Caret Interface

The Caret interface provides a number of useful features for dealing with text insertion and selection.

21.1.1.1 Properties

The 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.

Table 21-1. Caret properties

Property

Data type

get

is

set

default Value

blinkRate

int

·

 

·

 

dot

int

·

 

·

 

magicCaretPosition

Point

·

 

·

 

mark

int

·

     

selectionVisible

boolean

 

·

·

 

visible

boolean

 

·

·

 

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 Events

Whenever the Caret's position changes, a ChangeEvent should be fired to any interested ChangeListeners. Caret defines the following standard methods for managing event listeners:

public abstract void addChangeListener(ChangeListener l)
public abstract void removeChangeListener(ChangeListener l)
21.1.1.3 Methods

In addition to the accessors for the properties listed earlier, the Caret interface defines the following four methods:

public void install( JTextComponent c)

Signal that the Caret is responsible for the given component. In addition to giving the Caret access to the component, it also provides access to the Document model (to which the Caret can listen so it can update the caret location when text is added or removed).

public void deinstall( JTextComponent c)

Signal that the Caret is no longer responsible for the given component. The Caret should no longer be used once this method has been called.

public void moveDot(int dot)

Called when a selection is being made. It should update the Caret to the specified position and update the JTextComponent's Highlighter to reflect the new selection range.

public void paint(Graphics g)

Render the Caret using the given Graphics object.

21.1.2 The DefaultCaret Class

The 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 Properties

DefaultCaret 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.

Table 21-2. DefaultCaret properties

Property

Data type

get

is

set

Default value

blinkRate

int

·

 

·

0

dot

int

·

 

·

0

magicCaretPosition

Point

·

 

·

null

mark

int

·

   

0

selectionVisible

boolean

 

·

·

false

visible

boolean

 

·

·

false

See also the properties of java.awt.Rectangle (not in this book).

21.1.2.2 Events

A 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.)

protected void fireStateChanged( )
public void addChangeListener(ChangeListener l)
public void removeChangeListener(ChangeListener l)
public ChangeListener[] getChangeListeners( )
public EventListener[] getListeners(Class listenerType)
21.1.2.3 Constructor
public DefaultCaret( )

Create a nonblinking DefaultCaret.

21.1.2.4 Caret methods

DefaultCaret provides the following implementations of the methods defined by the Caret interface:

public void install(JTextComponent c)

Set the caret's component property and register a FocusListener, MouseListener, MouseMotionListener, and PropertyChangeListener for the component as well as a DocumentListener for the component's Document. For the first three it registers itself, either directly or indirectly. For the other two it registers an instance of UpdateHandler, an inner class.

public void deinstall(JTextComponent c)

Remove all the listeners registered by the install( ) method, set the component property to null, and (if the blink rate has been set) stop the Timer that controls the blinking (see Chapter 27).

public void moveDot(int dot)

Move the Caret to the specified position and update the component's Highlighter so it can highlight the area over which the cursor has been dragged.

public void paint(Graphics g)

Convert the current caret position (dot) to view coordinates, then render the caret by drawing a thin vertical line. Subclasses that override this method must also override the damage( ) method (or else g's clipping area may be off, which can prevent the caret from being drawn).

21.1.2.5 FocusListener methods

These methods are implemented from the FocusListener interface:

public void focusGained(FocusEvent e)

Called when the caret's component gains focus. If the component is enabled and editable, the caret becomes visible.

public void focusLost(FocusEvent e)

Called when the caret's component loses focus. A permanent loss of focus (in which e.isTemporary( ) returns false) causes the caret to become invisible.

21.1.2.6 Mouse methods

These methods are implemented from the MouseListener and MouseMotionListener interfaces. They define how DefaultCaret behaves in response to mouse gestures.

public void mouseClicked(MouseEvent e)

Update the position of the caret. A double-click selects a word; a triple-click selects a line.

public void mouseDragged(MouseEvent e)

Call moveCaret( ) (described later).

public void mousePressed(MouseEvent e)

Call positionCaret( ) (described later) and request focus for the caret's component if it is enabled.

public void mouseEntered(MouseEvent e)
public void mouseExited(MouseEvent e)
public void mouseMoved(MouseEvent e)
public void mouseReleased(MouseEvent e)

These methods do nothing in this implementation.

21.1.2.7 Protected methods

In addition to implementing all of the methods defined in the Caret interface, DefaultCaret adds several useful methods of its own:

protected void adjustVisibility(Rectangle nloc)

Called whenever the caret's position changes. This implementation calls the component's scrollRectToVisible( ) method (from the proper thread using SwingUtilities.invokeLater( ) if necessary) to ensure that the caret is visible. Subclasses may choose to change this policy.

protected void damage(Rectangle r)

This is an important method in the implementation of DefaultCaret. It is responsible for asking the caret's component to partially repaint itself, which causes the caret to be actually drawn. It is also responsible for setting values to the caret's x, y, width, and height fields (inherited from java.awt.Rectangle) so that other parts of DefaultCaret's implementation know where (in screen coordinates) the caret is located. This information is used, for example, to erase the previous location of the caret when the caret moves. Also, it sometimes determines the clipping area of the Graphics object passed into paint( ). The next section explains in detail how this works.

protected final JTextComponent getComponent( )

Provide access to the caret's component. The install( ) method (which is called when the Caret is added to the component) stores the component for future use, making it available here.

protected Highlighter.HighlightPainter getSelectionPainter( )

Return an object capable of making highlight decorations, which can be passed to the addHighlight( ) method of the component's Highlighter when a new selection is made. This implementation returns an instance of DefaultHighlightPainter (an inner class of DefaultHighlighter) with a value of null for its color property. This and other classes related to Highlighter are covered later in this chapter.

protected void moveCaret(MouseEvent e)

Called when the mouse is dragged (by mouseDragged( )) or Shift-clicked (indirectly by mousePressed( )), which usually updates the current selection.

protected void positionCaret(MouseEvent e)

Called when the mouse is clicked (indirectly by mousePressed( )). It moves the dot without making a selection. It also clears the magicCaretPosition property.

protected final void repaint( )

This method simply calls getComponent( ).repaint(x, y, width, height), which eventually causes the paint( ) method to be called. The caret's x, y, width, and height fields should have been set properly by the damage( ) method. Unlike most Swing methods, repaint( ) is thread-safe.

21.1.3 Custom Carets

Let'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
figs/swng2.2102.gif

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).

[1] FancyCaret is too simplistic for the more complex Swing text components (JTextPane and JEditorPane) that support multiple fonts. For these we would need to determine from the Document model the particular Font in use at the dot location and call g.getFontMetrics(theParticularFont).

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 Interface

If 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 Method

The CaretListener interface contains one method:

public void caretUpdate(CaretEvent e)

Called any time the caret's position changes.

21.1.5 The CaretEvent Class

As 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 Properties

CaretEvent 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).

Table 21-3. CaretEvent properties

Property

Data type

get

is

set

Default value

dot

int

·

   

Abstract

mark

int

·

   

Abstract

21.1.5.2 Constructor
public CaretEvent(Object source)

Create a new event. Subclasses must manage the dot and mark properties themselves. Since caret positions change frequently, it's best to create a single CaretEvent object and reuse it each time the caret position changes.



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