Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / text / DefaultCaret.java
blob776ef69e5b3b14731357fa1d8a3bc75896b22e1e
1 /* DefaultCaret.java --
2 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package javax.swing.text;
40 import java.awt.Graphics;
41 import java.awt.Point;
42 import java.awt.Rectangle;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.awt.event.FocusEvent;
46 import java.awt.event.FocusListener;
47 import java.awt.event.MouseEvent;
48 import java.awt.event.MouseListener;
49 import java.awt.event.MouseMotionListener;
50 import java.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
52 import java.util.EventListener;
54 import javax.swing.JComponent;
55 import javax.swing.SwingUtilities;
56 import javax.swing.Timer;
57 import javax.swing.event.ChangeEvent;
58 import javax.swing.event.ChangeListener;
59 import javax.swing.event.DocumentEvent;
60 import javax.swing.event.DocumentListener;
61 import javax.swing.event.EventListenerList;
63 /**
64 * The default implementation of the {@link Caret} interface.
66 * @author orgininal author unknown
67 * @author Roman Kennke (roman@kennke.org)
69 public class DefaultCaret extends Rectangle
70 implements Caret, FocusListener, MouseListener, MouseMotionListener
73 /**
74 * Controls the blinking of the caret.
76 * @author Roman Kennke (kennke@aicas.com)
77 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
79 private class BlinkTimerListener implements ActionListener
81 /**
82 * Forces the next event to be ignored. The next event should be ignored
83 * if we force the caret to appear. We do not know how long will it take
84 * to fire the comming event; this may be near immediately. Better to leave
85 * the caret visible one iteration longer.
87 boolean ignoreNextEvent;
89 /**
90 * Receives notification when the blink timer fires and updates the visible
91 * state of the caret.
93 * @param event the action event
95 public void actionPerformed(ActionEvent event)
97 if (ignoreNextEvent)
98 ignoreNextEvent = false;
99 else
101 visible = !visible;
102 repaint();
108 * Listens for changes in the text component's document and updates the
109 * caret accordingly.
111 * @author Roman Kennke (kennke@aicas.com)
113 private class DocumentHandler implements DocumentListener
116 * Receives notification that some text attributes have changed. No action
117 * is taken here.
119 * @param event the document event
121 public void changedUpdate(DocumentEvent event)
123 // Nothing to do here.
127 * Receives notification that some text has been inserted from the text
128 * component. The caret is moved forward accordingly.
130 * @param event the document event
132 public void insertUpdate(DocumentEvent event)
134 if (policy == ALWAYS_UPDATE ||
135 (SwingUtilities.isEventDispatchThread() &&
136 policy == UPDATE_WHEN_ON_EDT))
138 int dot = getDot();
139 setDot(dot + event.getLength());
144 * Receives notification that some text has been removed into the text
145 * component. The caret is moved backwards accordingly.
147 * @param event the document event
149 public void removeUpdate(DocumentEvent event)
151 if (policy == ALWAYS_UPDATE ||
152 (SwingUtilities.isEventDispatchThread() &&
153 policy == UPDATE_WHEN_ON_EDT))
155 int dot = getDot();
156 setDot(dot - event.getLength());
158 else if (policy == NEVER_UPDATE)
160 int docLength = event.getDocument().getLength();
161 if (getDot() > docLength)
162 setDot(docLength);
168 * Listens for property changes on the text document. This is used to add and
169 * remove our document listener, if the document of the text component has
170 * changed.
172 * @author Roman Kennke (kennke@aicas.com)
174 private class PropertyChangeHandler implements PropertyChangeListener
178 * Receives notification when a property has changed on the text component.
179 * This adds/removes our document listener from the text component's
180 * document when the document changes.
182 * @param e the property change event
184 public void propertyChange(PropertyChangeEvent e)
186 if (e.getPropertyName().equals("document"))
188 Document oldDoc = (Document) e.getOldValue();
189 oldDoc.removeDocumentListener(documentListener);
190 Document newDoc = (Document) e.getNewValue();
191 newDoc.addDocumentListener(documentListener);
197 /** The serialization UID (compatible with JDK1.5). */
198 private static final long serialVersionUID = 4325555698756477346L;
201 * Indicates the Caret position should always be updated after Document
202 * changes even if the updates are not performed on the Event Dispatching
203 * thread.
205 * @since 1.5
207 public static final int ALWAYS_UPDATE = 2;
210 * Indicates the Caret position should not be changed unless the Document
211 * length becomes less than the Caret position, in which case the Caret
212 * is moved to the end of the Document.
214 * @since 1.5
216 public static final int NEVER_UPDATE = 1;
218 /**
219 * Indicates the Caret position should be updated only if Document changes
220 * are made on the Event Dispatcher thread.
222 * @since 1.5
224 public static final int UPDATE_WHEN_ON_EDT = 0;
226 /** Keeps track of the current update policy **/
227 int policy = UPDATE_WHEN_ON_EDT;
230 * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}.
232 protected ChangeEvent changeEvent = new ChangeEvent(this);
235 * Stores all registered event listeners.
237 protected EventListenerList listenerList = new EventListenerList();
240 * Our document listener.
242 DocumentListener documentListener;
245 * Our property listener.
247 PropertyChangeListener propertyChangeListener;
250 * The text component in which this caret is installed.
252 private JTextComponent textComponent;
255 * Indicates if the selection should be visible or not.
257 private boolean selectionVisible = true;
260 * The blink rate of this <code>Caret</code>.
262 private int blinkRate = 500;
265 * The current dot position.
267 private int dot = 0;
270 * The current mark position.
272 private int mark = 0;
275 * The current visual caret position.
277 private Point magicCaretPosition = null;
280 * Indicates if this <code>Caret</code> is currently visible or not. This is
281 * package private to avoid an accessor method.
283 boolean visible = false;
286 * The current highlight entry.
288 private Object highlightEntry;
290 private Timer blinkTimer;
292 private BlinkTimerListener blinkListener;
295 * Creates a new <code>DefaultCaret</code> instance.
297 public DefaultCaret()
299 // Nothing to do here.
303 * Sets the Caret update policy.
305 * @param policy the new policy. Valid values are:
306 * ALWAYS_UPDATE: always update the Caret position, even when Document
307 * updates don't occur on the Event Dispatcher thread.
308 * NEVER_UPDATE: don't update the Caret position unless the Document
309 * length becomes less than the Caret position (then update the
310 * Caret to the end of the Document).
311 * UPDATE_WHEN_ON_EDT: update the Caret position when the
312 * Document updates occur on the Event Dispatcher thread. This is the
313 * default.
315 * @since 1.5
316 * @throws IllegalArgumentException if policy is not one of the above.
318 public void setUpdatePolicy (int policy)
320 if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE
321 && policy != UPDATE_WHEN_ON_EDT)
322 throw new
323 IllegalArgumentException
324 ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT");
325 this.policy = policy;
329 * Gets the caret update policy.
331 * @return the caret update policy.
332 * @since 1.5
334 public int getUpdatePolicy ()
336 return policy;
340 * Moves the caret position when the mouse is dragged over the text
341 * component, modifying the selection accordingly.
343 * @param event the <code>MouseEvent</code> describing the drag operation
345 public void mouseDragged(MouseEvent event)
347 moveCaret(event);
351 * Indicates a mouse movement over the text component. Does nothing here.
353 * @param event the <code>MouseEvent</code> describing the mouse operation
355 public void mouseMoved(MouseEvent event)
357 // Nothing to do here.
361 * When the click is received from Button 1 then the following actions
362 * are performed here:
364 * <ul>
365 * <li>If we receive a double click, the caret position (dot) is set
366 * to the position associated to the mouse click and the word at
367 * this location is selected.</li>
368 * <li>If we receive a triple click, the caret position (dot) is set
369 * to the position associated to the mouse click and the line at
370 * this location is selected.</li>
371 * </ul>
373 * @param event the <code>MouseEvent</code> describing the click operation
375 public void mouseClicked(MouseEvent event)
377 // TODO: Implement double- and triple-click behaviour here.
381 * Indicates that the mouse has entered the text component. Nothing is done
382 * here.
384 * @param event the <code>MouseEvent</code> describing the mouse operation
386 public void mouseEntered(MouseEvent event)
388 // Nothing to do here.
392 * Indicates that the mouse has exited the text component. Nothing is done
393 * here.
395 * @param event the <code>MouseEvent</code> describing the mouse operation
397 public void mouseExited(MouseEvent event)
399 // Nothing to do here.
403 * If the button 1 is pressed, the caret position is updated to the
404 * position of the mouse click and the text component requests the input
405 * focus if it is enabled. If the SHIFT key is held down, the caret will
406 * be moved, which might select the text between the old and new location.
408 * @param event the <code>MouseEvent</code> describing the press operation
410 public void mousePressed(MouseEvent event)
412 positionCaret(event);
416 * Indicates that a mouse button has been released on the text component.
417 * Nothing is done here.
419 * @param event the <code>MouseEvent</code> describing the mouse operation
421 public void mouseReleased(MouseEvent event)
423 // Nothing to do here.
427 * Sets the caret to <code>visible</code> if the text component is editable.
429 * @param event the <code>FocusEvent</code>
431 public void focusGained(FocusEvent event)
433 setVisible(true);
434 updateTimerStatus();
438 * Sets the caret to <code>invisible</code>.
440 * @param event the <code>FocusEvent</code>
442 public void focusLost(FocusEvent event)
444 if (event.isTemporary() == false)
446 setVisible(false);
447 // Stop the blinker, if running.
448 if (blinkTimer != null && blinkTimer.isRunning())
449 blinkTimer.stop();
454 * Install (if not present) and start the timer, if the caret must blink. The
455 * caret does not blink if it is invisible, or the component is disabled or
456 * not editable.
458 private void updateTimerStatus()
460 if (textComponent.isEnabled() && textComponent.isEditable())
462 if (blinkTimer == null)
463 initBlinkTimer();
464 if (!blinkTimer.isRunning())
465 blinkTimer.start();
467 else
469 if (blinkTimer != null)
470 blinkTimer.stop();
475 * Moves the caret to the position specified in the <code>MouseEvent</code>.
476 * This will cause a selection if the dot and mark are different.
478 * @param event the <code>MouseEvent</code> from which to fetch the position
480 protected void moveCaret(MouseEvent event)
482 int newDot = getComponent().viewToModel(event.getPoint());
483 moveDot(newDot);
487 * Repositions the caret to the position specified in the
488 * <code>MouseEvent</code>.
490 * @param event the <code>MouseEvent</code> from which to fetch the position
492 protected void positionCaret(MouseEvent event)
494 int newDot = getComponent().viewToModel(event.getPoint());
495 setDot(newDot);
499 * Deinstalls this <code>Caret</code> from the specified
500 * <code>JTextComponent</code>. This removes any listeners that have been
501 * registered by this <code>Caret</code>.
503 * @param c the text component from which to install this caret
505 public void deinstall(JTextComponent c)
507 textComponent.removeFocusListener(this);
508 textComponent.removeMouseListener(this);
509 textComponent.removeMouseMotionListener(this);
510 textComponent.getDocument().removeDocumentListener(documentListener);
511 documentListener = null;
512 textComponent.removePropertyChangeListener(propertyChangeListener);
513 propertyChangeListener = null;
514 textComponent = null;
516 // Deinstall blink timer if present.
517 if (blinkTimer != null)
518 blinkTimer.stop();
519 blinkTimer = null;
523 * Installs this <code>Caret</code> on the specified
524 * <code>JTextComponent</code>. This registers a couple of listeners
525 * on the text component.
527 * @param c the text component on which to install this caret
529 public void install(JTextComponent c)
531 textComponent = c;
532 textComponent.addFocusListener(this);
533 textComponent.addMouseListener(this);
534 textComponent.addMouseMotionListener(this);
535 propertyChangeListener = new PropertyChangeHandler();
536 textComponent.addPropertyChangeListener(propertyChangeListener);
537 documentListener = new DocumentHandler();
538 textComponent.getDocument().addDocumentListener(documentListener);
540 repaint();
544 * Sets the current visual position of this <code>Caret</code>.
546 * @param p the Point to use for the saved location. May be <code>null</code>
547 * to indicate that there is no visual location
549 public void setMagicCaretPosition(Point p)
551 magicCaretPosition = p;
555 * Returns the current visual position of this <code>Caret</code>.
557 * @return the current visual position of this <code>Caret</code>
559 * @see #setMagicCaretPosition
561 public Point getMagicCaretPosition()
563 return magicCaretPosition;
567 * Returns the current position of the <code>mark</code>. The
568 * <code>mark</code> marks the location in the <code>Document</code> that
569 * is the end of a selection. If there is no selection, the <code>mark</code>
570 * is the same as the <code>dot</code>.
572 * @return the current position of the mark
574 public int getMark()
576 return mark;
579 private void handleHighlight()
581 Highlighter highlighter = textComponent.getHighlighter();
583 if (highlighter == null)
584 return;
586 int p0 = Math.min(dot, mark);
587 int p1 = Math.max(dot, mark);
589 if (selectionVisible && p0 != p1)
593 if (highlightEntry == null)
594 highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter());
595 else
596 highlighter.changeHighlight(highlightEntry, p0, p1);
598 catch (BadLocationException e)
600 // This should never happen.
601 throw new InternalError();
604 else
606 if (highlightEntry != null)
608 highlighter.removeHighlight(highlightEntry);
609 highlightEntry = null;
615 * Sets the visiblity state of the selection.
617 * @param v <code>true</code> if the selection should be visible,
618 * <code>false</code> otherwise
620 public void setSelectionVisible(boolean v)
622 if (selectionVisible == v)
623 return;
625 selectionVisible = v;
626 handleHighlight();
627 repaint();
631 * Returns <code>true</code> if the selection is currently visible,
632 * <code>false</code> otherwise.
634 * @return <code>true</code> if the selection is currently visible,
635 * <code>false</code> otherwise
637 public boolean isSelectionVisible()
639 return selectionVisible;
643 * Causes the <code>Caret</code> to repaint itself.
645 protected final void repaint()
647 getComponent().repaint(x, y, width, height);
651 * Paints this <code>Caret</code> using the specified <code>Graphics</code>
652 * context.
654 * @param g the graphics context to use
656 public void paint(Graphics g)
658 JTextComponent comp = getComponent();
659 if (comp == null)
660 return;
662 int dot = getDot();
663 Rectangle rect = null;
667 rect = textComponent.modelToView(dot);
669 catch (BadLocationException e)
671 AssertionError ae;
672 ae = new AssertionError("Unexpected bad caret location: " + dot);
673 ae.initCause(e);
674 throw ae;
677 if (rect == null)
678 return;
680 // Check if paint has possibly been called directly, without a previous
681 // call to damage(). In this case we need to do some cleanup first.
682 if ((x != rect.x) || (y != rect.y))
684 repaint(); // Erase previous location of caret.
685 x = rect.x;
686 y = rect.y;
687 width = 1;
688 height = rect.height;
691 // Now draw the caret on the new position if visible.
692 if (visible)
694 g.setColor(textComponent.getCaretColor());
695 g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height);
700 * Returns all registered event listeners of the specified type.
702 * @param listenerType the type of listener to return
704 * @return all registered event listeners of the specified type
706 public EventListener[] getListeners(Class listenerType)
708 return listenerList.getListeners(listenerType);
712 * Registers a {@link ChangeListener} that is notified whenever that state
713 * of this <code>Caret</code> changes.
715 * @param listener the listener to register to this caret
717 public void addChangeListener(ChangeListener listener)
719 listenerList.add(ChangeListener.class, listener);
723 * Removes a {@link ChangeListener} from the list of registered listeners.
725 * @param listener the listener to remove
727 public void removeChangeListener(ChangeListener listener)
729 listenerList.remove(ChangeListener.class, listener);
733 * Returns all registered {@link ChangeListener}s of this <code>Caret</code>.
735 * @return all registered {@link ChangeListener}s of this <code>Caret</code>
737 public ChangeListener[] getChangeListeners()
739 return (ChangeListener[]) getListeners(ChangeListener.class);
743 * Notifies all registered {@link ChangeListener}s that the state
744 * of this <code>Caret</code> has changed.
746 protected void fireStateChanged()
748 ChangeListener[] listeners = getChangeListeners();
750 for (int index = 0; index < listeners.length; ++index)
751 listeners[index].stateChanged(changeEvent);
755 * Returns the <code>JTextComponent</code> on which this <code>Caret</code>
756 * is installed.
758 * @return the <code>JTextComponent</code> on which this <code>Caret</code>
759 * is installed
761 protected final JTextComponent getComponent()
763 return textComponent;
767 * Returns the blink rate of this <code>Caret</code> in milliseconds.
768 * A value of <code>0</code> means that the caret does not blink.
770 * @return the blink rate of this <code>Caret</code> or <code>0</code> if
771 * this caret does not blink
773 public int getBlinkRate()
775 return blinkRate;
779 * Sets the blink rate of this <code>Caret</code> in milliseconds.
780 * A value of <code>0</code> means that the caret does not blink.
782 * @param rate the new blink rate to set
784 public void setBlinkRate(int rate)
786 if (blinkTimer != null)
787 blinkTimer.setDelay(rate);
788 blinkRate = rate;
792 * Returns the current position of this <code>Caret</code> within the
793 * <code>Document</code>.
795 * @return the current position of this <code>Caret</code> within the
796 * <code>Document</code>
798 public int getDot()
800 return dot;
804 * Moves the <code>dot</code> location without touching the
805 * <code>mark</code>. This is used when making a selection.
807 * @param dot the location where to move the dot
809 * @see #setDot(int)
811 public void moveDot(int dot)
813 if (dot >= 0)
815 this.dot = dot;
816 handleHighlight();
817 adjustVisibility(this);
818 appear();
823 * Sets the current position of this <code>Caret</code> within the
824 * <code>Document</code>. This also sets the <code>mark</code> to the new
825 * location.
827 * @param dot
828 * the new position to be set
829 * @see #moveDot(int)
831 public void setDot(int dot)
833 if (dot >= 0)
835 Document doc = textComponent.getDocument();
836 if (doc != null)
837 this.dot = Math.min(dot, doc.getLength());
838 this.dot = Math.max(this.dot, 0);
839 this.mark = dot;
840 handleHighlight();
841 adjustVisibility(this);
842 appear();
847 * Show the caret (may be hidden due blinking) and adjust the timer not to
848 * hide it (possibly immediately).
850 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
852 void appear()
854 // All machinery is only required if the carret is blinking.
855 if (blinkListener != null)
857 blinkListener.ignoreNextEvent = true;
859 // If the caret is visible, erase the current position by repainting
860 // over.
861 if (visible)
862 repaint();
864 // Draw the caret in the new position.
865 visible = true;
867 Rectangle area = null;
868 int dot = getDot();
871 area = getComponent().modelToView(dot);
873 catch (BadLocationException e)
875 AssertionError ae;
876 ae = new AssertionError("Unexpected bad caret location: " + dot);
877 ae.initCause(e);
878 throw ae;
880 if (area != null)
881 damage(area);
883 repaint();
887 * Returns <code>true</code> if this <code>Caret</code> is currently visible,
888 * and <code>false</code> if it is not.
890 * @return <code>true</code> if this <code>Caret</code> is currently visible,
891 * and <code>false</code> if it is not
893 public boolean isVisible()
895 return visible;
899 * Sets the visibility state of the caret. <code>true</code> shows the
900 * <code>Caret</code>, <code>false</code> hides it.
902 * @param v the visibility to set
904 public void setVisible(boolean v)
906 if (v != visible)
908 visible = v;
909 updateTimerStatus();
910 Rectangle area = null;
911 int dot = getDot();
914 area = getComponent().modelToView(dot);
916 catch (BadLocationException e)
918 AssertionError ae;
919 ae = new AssertionError("Unexpected bad caret location: " + dot);
920 ae.initCause(e);
921 throw ae;
923 if (area != null)
924 damage(area);
929 * Returns the {@link Highlighter.HighlightPainter} that should be used
930 * to paint the selection.
932 * @return the {@link Highlighter.HighlightPainter} that should be used
933 * to paint the selection
935 protected Highlighter.HighlightPainter getSelectionPainter()
937 return DefaultHighlighter.DefaultPainter;
941 * Updates the carets rectangle properties to the specified rectangle and
942 * repaints the caret.
944 * @param r the rectangle to set as the caret rectangle
946 protected void damage(Rectangle r)
948 if (r == null)
949 return;
950 x = r.x;
951 y = r.y;
952 width = 1;
953 // height is normally set in paint and we leave it untouched. However, we
954 // must set a valid value here, since otherwise the painting mechanism
955 // sets a zero clip and never calls paint.
956 if (height <= 0)
957 height = getComponent().getHeight();
958 repaint();
962 * Adjusts the text component so that the caret is visible. This default
963 * implementation simply calls
964 * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component.
965 * Subclasses may wish to change this.
967 protected void adjustVisibility(Rectangle rect)
969 getComponent().scrollRectToVisible(rect);
973 * Initializes the blink timer.
975 private void initBlinkTimer()
977 // Setup the blink timer.
978 blinkListener = new BlinkTimerListener();
979 blinkTimer = new Timer(getBlinkRate(), blinkListener);
980 blinkTimer.setRepeats(true);