diff options
Diffstat (limited to 'libjava/classpath/javax/swing/text/DefaultCaret.java')
-rw-r--r-- | libjava/classpath/javax/swing/text/DefaultCaret.java | 435 |
1 files changed, 398 insertions, 37 deletions
diff --git a/libjava/classpath/javax/swing/text/DefaultCaret.java b/libjava/classpath/javax/swing/text/DefaultCaret.java index 33c3ae3bf28..3ebeceb947d 100644 --- a/libjava/classpath/javax/swing/text/DefaultCaret.java +++ b/libjava/classpath/javax/swing/text/DefaultCaret.java @@ -40,15 +40,24 @@ package javax.swing.text; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.EventListener; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.event.EventListenerList; /** @@ -60,12 +69,164 @@ import javax.swing.event.EventListenerList; public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener { + + /** + * Controls the blinking of the caret. + * + * @author Roman Kennke (kennke@aicas.com) + * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) + */ + private class BlinkTimerListener implements ActionListener + { + /** + * Forces the next event to be ignored. The next event should be ignored + * if we force the caret to appear. We do not know how long will it take + * to fire the comming event; this may be near immediately. Better to leave + * the caret visible one iteration longer. + */ + boolean ignoreNextEvent; + + /** + * Receives notification when the blink timer fires and updates the visible + * state of the caret. + * + * @param event the action event + */ + public void actionPerformed(ActionEvent event) + { + if (ignoreNextEvent) + ignoreNextEvent = false; + else + { + visible = !visible; + repaint(); + } + } + } + + /** + * Listens for changes in the text component's document and updates the + * caret accordingly. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class DocumentHandler implements DocumentListener + { + /** + * Receives notification that some text attributes have changed. No action + * is taken here. + * + * @param event the document event + */ + public void changedUpdate(DocumentEvent event) + { + // Nothing to do here. + } + + /** + * Receives notification that some text has been inserted from the text + * component. The caret is moved forward accordingly. + * + * @param event the document event + */ + public void insertUpdate(DocumentEvent event) + { + if (policy == ALWAYS_UPDATE || + (SwingUtilities.isEventDispatchThread() && + policy == UPDATE_WHEN_ON_EDT)) + { + int dot = getDot(); + setDot(dot + event.getLength()); + } + } + + /** + * Receives notification that some text has been removed into the text + * component. The caret is moved backwards accordingly. + * + * @param event the document event + */ + public void removeUpdate(DocumentEvent event) + { + if (policy == ALWAYS_UPDATE || + (SwingUtilities.isEventDispatchThread() && + policy == UPDATE_WHEN_ON_EDT)) + { + int dot = getDot(); + setDot(dot - event.getLength()); + } + else if (policy == NEVER_UPDATE) + { + int docLength = event.getDocument().getLength(); + if (getDot() > docLength) + setDot(docLength); + } + } + } + + /** + * Listens for property changes on the text document. This is used to add and + * remove our document listener, if the document of the text component has + * changed. + * + * @author Roman Kennke (kennke@aicas.com) + */ + private class PropertyChangeHandler implements PropertyChangeListener + { + + /** + * Receives notification when a property has changed on the text component. + * This adds/removes our document listener from the text component's + * document when the document changes. + * + * @param e the property change event + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName().equals("document")) + { + Document oldDoc = (Document) e.getOldValue(); + oldDoc.removeDocumentListener(documentListener); + Document newDoc = (Document) e.getNewValue(); + newDoc.addDocumentListener(documentListener); + } + } + + } + + /** The serialization UID (compatible with JDK1.5). */ + private static final long serialVersionUID = 4325555698756477346L; + /** - * The serial version UID for DefaultCaret. + * Indicates the Caret position should always be updated after Document + * changes even if the updates are not performed on the Event Dispatching + * thread. + * + * @since 1.5 */ - private static final long serialVersionUID = 228155774675466193L; + public static final int ALWAYS_UPDATE = 2; /** + * Indicates the Caret position should not be changed unless the Document + * length becomes less than the Caret position, in which case the Caret + * is moved to the end of the Document. + * + * @since 1.5 + */ + public static final int NEVER_UPDATE = 1; + + /** + * Indicates the Caret position should be updated only if Document changes + * are made on the Event Dispatcher thread. + * + * @since 1.5 + */ + public static final int UPDATE_WHEN_ON_EDT = 0; + + /** Keeps track of the current update policy **/ + int policy = UPDATE_WHEN_ON_EDT; + + /** * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}. */ protected ChangeEvent changeEvent = new ChangeEvent(this); @@ -76,6 +237,16 @@ public class DefaultCaret extends Rectangle protected EventListenerList listenerList = new EventListenerList(); /** + * Our document listener. + */ + DocumentListener documentListener; + + /** + * Our property listener. + */ + PropertyChangeListener propertyChangeListener; + + /** * The text component in which this caret is installed. */ private JTextComponent textComponent; @@ -106,15 +277,65 @@ public class DefaultCaret extends Rectangle private Point magicCaretPosition = null; /** - * Indicates if this <code>Caret</code> is currently visible or not. + * Indicates if this <code>Caret</code> is currently visible or not. This is + * package private to avoid an accessor method. */ - private boolean visible = true; + boolean visible = false; /** * The current highlight entry. */ private Object highlightEntry; + private Timer blinkTimer; + + private BlinkTimerListener blinkListener; + + /** + * Creates a new <code>DefaultCaret</code> instance. + */ + public DefaultCaret() + { + // Nothing to do here. + } + + /** + * Sets the Caret update policy. + * + * @param policy the new policy. Valid values are: + * ALWAYS_UPDATE: always update the Caret position, even when Document + * updates don't occur on the Event Dispatcher thread. + * NEVER_UPDATE: don't update the Caret position unless the Document + * length becomes less than the Caret position (then update the + * Caret to the end of the Document). + * UPDATE_WHEN_ON_EDT: update the Caret position when the + * Document updates occur on the Event Dispatcher thread. This is the + * default. + * + * @since 1.5 + * @throws IllegalArgumentException if policy is not one of the above. + */ + public void setUpdatePolicy (int policy) + { + if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE + && policy != UPDATE_WHEN_ON_EDT) + throw new + IllegalArgumentException + ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT"); + this.policy = policy; + } + + /** + * Gets the caret update policy. + * + * @return the caret update policy. + * @since 1.5 + */ + public int getUpdatePolicy () + { + return policy; + } + /** * Moves the caret position when the mouse is dragged over the text * component, modifying the selection accordingly. @@ -123,7 +344,7 @@ public class DefaultCaret extends Rectangle */ public void mouseDragged(MouseEvent event) { - // FIXME: Implement this properly. + moveCaret(event); } /** @@ -153,7 +374,7 @@ public class DefaultCaret extends Rectangle */ public void mouseClicked(MouseEvent event) { - // FIXME: Implement this properly. + // TODO: Implement double- and triple-click behaviour here. } /** @@ -175,6 +396,7 @@ public class DefaultCaret extends Rectangle */ public void mouseExited(MouseEvent event) { + // Nothing to do here. } /** @@ -187,7 +409,7 @@ public class DefaultCaret extends Rectangle */ public void mousePressed(MouseEvent event) { - // FIXME: Implement this properly. + positionCaret(event); } /** @@ -208,6 +430,7 @@ public class DefaultCaret extends Rectangle */ public void focusGained(FocusEvent event) { + setVisible(true); } /** @@ -217,6 +440,8 @@ public class DefaultCaret extends Rectangle */ public void focusLost(FocusEvent event) { + if (event.isTemporary() == false) + setVisible(false); } /** @@ -227,7 +452,8 @@ public class DefaultCaret extends Rectangle */ protected void moveCaret(MouseEvent event) { - // FIXME: Implement this properly. + int newDot = getComponent().viewToModel(event.getPoint()); + moveDot(newDot); } /** @@ -238,7 +464,8 @@ public class DefaultCaret extends Rectangle */ protected void positionCaret(MouseEvent event) { - // FIXME: Implement this properly. + int newDot = getComponent().viewToModel(event.getPoint()); + setDot(newDot); } /** @@ -253,7 +480,16 @@ public class DefaultCaret extends Rectangle textComponent.removeFocusListener(this); textComponent.removeMouseListener(this); textComponent.removeMouseMotionListener(this); + textComponent.getDocument().removeDocumentListener(documentListener); + documentListener = null; + textComponent.removePropertyChangeListener(propertyChangeListener); + propertyChangeListener = null; textComponent = null; + + // Deinstall blink timer if present. + if (blinkTimer != null) + blinkTimer.stop(); + blinkTimer = null; } /** @@ -269,6 +505,11 @@ public class DefaultCaret extends Rectangle textComponent.addFocusListener(this); textComponent.addMouseListener(this); textComponent.addMouseMotionListener(this); + propertyChangeListener = new PropertyChangeHandler(); + textComponent.addPropertyChangeListener(propertyChangeListener); + documentListener = new DocumentHandler(); + textComponent.getDocument().addDocumentListener(documentListener); + repaint(); } @@ -376,10 +617,7 @@ public class DefaultCaret extends Rectangle */ protected final void repaint() { - // FIXME: Is this good? This possibly causes alot of the component - // hierarchy to be repainted on every caret blink. - if (textComponent != null) - textComponent.repaint(); + getComponent().repaint(x, y, width, height); } /** @@ -390,7 +628,8 @@ public class DefaultCaret extends Rectangle */ public void paint(Graphics g) { - if (textComponent == null) + JTextComponent comp = getComponent(); + if (comp == null) return; int dot = getDot(); @@ -398,25 +637,33 @@ public class DefaultCaret extends Rectangle try { - rect = textComponent.modelToView(dot); + rect = textComponent.modelToView(dot); } catch (BadLocationException e) { - // This should never happen as dot should be always valid. - return; + assert false : "Unexpected bad caret location: " + dot; + return; } if (rect == null) return; - - // First we need to delete the old caret. - // FIXME: Implement deleting of old caret. - + + // Check if paint has possibly been called directly, without a previous + // call to damage(). In this case we need to do some cleanup first. + if ((x != rect.x) || (y != rect.y)) + { + repaint(); // Erase previous location of caret. + x = rect.x; + y = rect.y; + width = 1; + height = rect.height; + } + // Now draw the caret on the new position if visible. if (visible) { - g.setColor(textComponent.getCaretColor()); - g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); + g.setColor(textComponent.getCaretColor()); + g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height); } } @@ -507,6 +754,8 @@ public class DefaultCaret extends Rectangle */ public void setBlinkRate(int rate) { + if (blinkTimer != null) + blinkTimer.setDelay(rate); blinkRate = rate; } @@ -532,27 +781,71 @@ public class DefaultCaret extends Rectangle */ public void moveDot(int dot) { - this.dot = dot; - handleHighlight(); - repaint(); + if (dot >= 0) + { + this.dot = dot; + handleHighlight(); + adjustVisibility(this); + appear(); + } } /** * Sets the current position of this <code>Caret</code> within the - * <code>Document</code>. This also sets the <code>mark</code> to the - * new location. - * - * @param dot the new position to be set - * + * <code>Document</code>. This also sets the <code>mark</code> to the new + * location. + * + * @param dot + * the new position to be set * @see #moveDot(int) */ public void setDot(int dot) { - this.dot = dot; - this.mark = dot; - handleHighlight(); - repaint(); + if (dot >= 0) + { + this.mark = dot; + this.dot = dot; + handleHighlight(); + adjustVisibility(this); + appear(); + } } + + /** + * Show the caret (may be hidden due blinking) and adjust the timer not to + * hide it (possibly immediately). + * + * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) + */ + void appear() + { + // All machinery is only required if the carret is blinking. + if (blinkListener != null) + { + blinkListener.ignoreNextEvent = true; + + // If the caret is visible, erase the current position by repainting + // over. + if (visible) + repaint(); + + // Draw the caret in the new position. + visible = true; + + Rectangle area = null; + try + { + area = getComponent().modelToView(getDot()); + } + catch (BadLocationException ex) + { + assert false : "Unexpected bad caret location: " + getDot(); + } + if (area != null) + damage(area); + } + repaint(); + } /** * Returns <code>true</code> if this <code>Caret</code> is currently visible, @@ -574,8 +867,33 @@ public class DefaultCaret extends Rectangle */ public void setVisible(boolean v) { - visible = v; - repaint(); + if (v != visible) + { + visible = v; + if (visible) + if (textComponent.isEnabled() && textComponent.isEditable()) + { + if (blinkTimer == null) + initBlinkTimer(); + blinkTimer.start(); + } + else + { + if (blinkTimer != null) + blinkTimer.stop(); + } + Rectangle area = null; + try + { + area = getComponent().modelToView(getDot()); + } + catch (BadLocationException ex) + { + assert false: "Unexpected bad caret location: " + getDot(); + } + if (area != null) + damage(area); + } } /** @@ -589,4 +907,47 @@ public class DefaultCaret extends Rectangle { return DefaultHighlighter.DefaultPainter; } + + /** + * Updates the carets rectangle properties to the specified rectangle and + * repaints the caret. + * + * @param r the rectangle to set as the caret rectangle + */ + protected void damage(Rectangle r) + { + if (r == null) + return; + x = r.x; + y = r.y; + width = 1; + // height is normally set in paint and we leave it untouched. However, we + // must set a valid value here, since otherwise the painting mechanism + // sets a zero clip and never calls paint. + if (height <= 0) + height = getComponent().getHeight(); + repaint(); + } + + /** + * Adjusts the text component so that the caret is visible. This default + * implementation simply calls + * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. + * Subclasses may wish to change this. + */ + protected void adjustVisibility(Rectangle rect) + { + getComponent().scrollRectToVisible(rect); + } + + /** + * Initializes the blink timer. + */ + private void initBlinkTimer() + { + // Setup the blink timer. + blinkListener = new BlinkTimerListener(); + blinkTimer = new Timer(getBlinkRate(), blinkListener); + blinkTimer.setRepeats(true); + } } |