1 /* DefaultCaret.java --
2    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
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.
10 
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.
15 
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.
20 
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.
25 
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. */
37 
38 package javax.swing.text;
39 
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;
53 
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;
62 import javax.swing.text.Position.Bias;
63 
64 /**
65  * The default implementation of the {@link Caret} interface.
66  *
67  * @author original author unknown
68  * @author Roman Kennke (roman@kennke.org)
69  */
70 public class DefaultCaret extends Rectangle
71   implements Caret, FocusListener, MouseListener, MouseMotionListener
72 {
73 
74   /** A text component in the current VM which currently has a
75    * text selection or <code>null</code>.
76    */
77   static JTextComponent componentWithSelection;
78 
79   /** An implementation of NavigationFilter.FilterBypass which delegates
80    * to the corresponding methods of the <code>DefaultCaret</code>.
81    *
82    * @author Robert Schuster (robertschuster@fsfe.org)
83    */
84   class Bypass extends NavigationFilter.FilterBypass
85   {
86 
getCaret()87     public Caret getCaret()
88     {
89       return DefaultCaret.this;
90     }
91 
moveDot(int dot, Bias bias)92     public void moveDot(int dot, Bias bias)
93     {
94       DefaultCaret.this.moveDotImpl(dot);
95     }
96 
setDot(int dot, Bias bias)97     public void setDot(int dot, Bias bias)
98     {
99       DefaultCaret.this.setDotImpl(dot);
100     }
101 
102   }
103 
104   /**
105    * Controls the blinking of the caret.
106    *
107    * @author Roman Kennke (kennke@aicas.com)
108    * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
109    */
110   private class BlinkTimerListener implements ActionListener
111   {
112     /**
113      * Forces the next event to be ignored. The next event should be ignored
114      * if we force the caret to appear. We do not know how long will it take
115      * to fire the comming event; this may be near immediately. Better to leave
116      * the caret visible one iteration longer.
117      */
118     boolean ignoreNextEvent;
119 
120     /**
121      * Receives notification when the blink timer fires and updates the visible
122      * state of the caret.
123      *
124      * @param event the action event
125      */
actionPerformed(ActionEvent event)126     public void actionPerformed(ActionEvent event)
127     {
128       if (ignoreNextEvent)
129         ignoreNextEvent = false;
130       else
131         {
132           visible = !visible;
133           repaint();
134         }
135     }
136   }
137 
138   /**
139    * Listens for changes in the text component's document and updates the
140    * caret accordingly.
141    *
142    * @author Roman Kennke (kennke@aicas.com)
143    */
144   private class DocumentHandler implements DocumentListener
145   {
146     /**
147      * Receives notification that some text attributes have changed. No action
148      * is taken here.
149      *
150      * @param event the document event
151      */
changedUpdate(DocumentEvent event)152     public void changedUpdate(DocumentEvent event)
153     {
154       // Nothing to do here.
155     }
156 
157     /**
158      * Receives notification that some text has been inserted from the text
159      * component. The caret is moved forward accordingly.
160      *
161      * @param event the document event
162      */
insertUpdate(DocumentEvent event)163     public void insertUpdate(DocumentEvent event)
164     {
165       if (policy == ALWAYS_UPDATE ||
166           (SwingUtilities.isEventDispatchThread() &&
167            policy == UPDATE_WHEN_ON_EDT))
168         {
169           int dot = getDot();
170           setDot(dot + event.getLength());
171         }
172     }
173 
174     /**
175      * Receives notification that some text has been removed into the text
176      * component. The caret is moved backwards accordingly.
177      *
178      * @param event the document event
179      */
removeUpdate(DocumentEvent event)180     public void removeUpdate(DocumentEvent event)
181     {
182       if (policy == ALWAYS_UPDATE
183           || (SwingUtilities.isEventDispatchThread()
184               && policy == UPDATE_WHEN_ON_EDT))
185         {
186           int dot = getDot();
187           setDot(dot - event.getLength());
188         }
189       else if (policy == NEVER_UPDATE
190                || (! SwingUtilities.isEventDispatchThread()
191                    && policy == UPDATE_WHEN_ON_EDT))
192         {
193           int docLength = event.getDocument().getLength();
194           if (getDot() > docLength)
195             setDot(docLength);
196         }
197     }
198   }
199 
200   /**
201    * Listens for property changes on the text document. This is used to add and
202    * remove our document listener, if the document of the text component has
203    * changed.
204    *
205    * @author Roman Kennke (kennke@aicas.com)
206    */
207   private class PropertyChangeHandler implements PropertyChangeListener
208   {
209 
210     /**
211      * Receives notification when a property has changed on the text component.
212      * This adds/removes our document listener from the text component's
213      * document when the document changes.
214      *
215      * @param e the property change event
216      */
propertyChange(PropertyChangeEvent e)217     public void propertyChange(PropertyChangeEvent e)
218     {
219       String name = e.getPropertyName();
220 
221       if (name.equals("document"))
222         {
223           Document oldDoc = (Document) e.getOldValue();
224           if (oldDoc != null)
225             oldDoc.removeDocumentListener(documentListener);
226 
227           Document newDoc = (Document) e.getNewValue();
228           if (newDoc != null)
229             newDoc.addDocumentListener(documentListener);
230         }
231       else if (name.equals("editable"))
232         {
233           active = (((Boolean) e.getNewValue()).booleanValue()
234                    && textComponent.isEnabled());
235         }
236       else if (name.equals("enabled"))
237         {
238           active = (((Boolean) e.getNewValue()).booleanValue()
239                    && textComponent.isEditable());
240         }
241 
242     }
243 
244   }
245 
246   /** The serialization UID (compatible with JDK1.5). */
247   private static final long serialVersionUID = 4325555698756477346L;
248 
249   /**
250    * Indicates the Caret position should always be updated after Document
251    * changes even if the updates are not performed on the Event Dispatching
252    * thread.
253    *
254    * @since 1.5
255    */
256   public static final int ALWAYS_UPDATE = 2;
257 
258   /**
259    * Indicates the Caret position should not be changed unless the Document
260    * length becomes less than the Caret position, in which case the Caret
261    * is moved to the end of the Document.
262    *
263    * @since 1.5
264    */
265   public static final int NEVER_UPDATE = 1;
266 
267   /**
268    * Indicates the Caret position should be updated only if Document changes
269    * are made on the Event Dispatcher thread.
270    *
271    * @since 1.5
272    */
273   public static final int UPDATE_WHEN_ON_EDT = 0;
274 
275   /** Keeps track of the current update policy **/
276   int policy = UPDATE_WHEN_ON_EDT;
277 
278   /**
279    * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}.
280    */
281   protected ChangeEvent changeEvent = new ChangeEvent(this);
282 
283   /**
284    * Stores all registered event listeners.
285    */
286   protected EventListenerList listenerList = new EventListenerList();
287 
288   /**
289    * Our document listener.
290    */
291   DocumentListener documentListener;
292 
293   /**
294    * Our property listener.
295    */
296   PropertyChangeListener propertyChangeListener;
297 
298   /**
299    * The text component in which this caret is installed.
300    *
301    * (Package private to avoid synthetic accessor method.)
302    */
303   JTextComponent textComponent;
304 
305   /**
306    * Indicates if the selection should be visible or not.
307    */
308   private boolean selectionVisible = true;
309 
310   /**
311    * The blink rate of this <code>Caret</code>.
312    */
313   private int blinkRate = 500;
314 
315   /**
316    * The current dot position.
317    */
318   private int dot = 0;
319 
320   /**
321    * The current mark position.
322    */
323   private int mark = 0;
324 
325   /**
326    * The current visual caret position.
327    */
328   private Point magicCaretPosition = null;
329 
330   /**
331    * Indicates if this <code>Caret</code> is currently visible or not. This is
332    * package private to avoid an accessor method.
333    */
334   boolean visible = false;
335 
336   /** Indicates whether the text component where the caret is installed is
337    * editable and enabled. If either of these properties is <code>false</code>
338    * the caret is not drawn.
339    */
340   boolean active = true;
341 
342   /**
343    * The current highlight entry.
344    */
345   private Object highlightEntry;
346 
347   private Timer blinkTimer;
348 
349   private BlinkTimerListener blinkListener;
350 
351   /**
352    * A <code>NavigationFilter.FilterBypass</code> instance which
353    * is provided to the a <code>NavigationFilter</code> to
354    * unconditionally set or move the caret.
355    */
356   NavigationFilter.FilterBypass bypass;
357 
358   /**
359    * Creates a new <code>DefaultCaret</code> instance.
360    */
DefaultCaret()361   public DefaultCaret()
362   {
363     // Nothing to do here.
364   }
365 
366   /** Returns the caret's <code>NavigationFilter.FilterBypass</code> instance
367    * and creates it if it does not yet exist.
368    *
369    * @return The caret's <code>NavigationFilter.FilterBypass</code> instance.
370    */
getBypass()371   private NavigationFilter.FilterBypass getBypass()
372   {
373     return (bypass == null) ? bypass = new Bypass() : bypass;
374   }
375 
376   /**
377    * Sets the Caret update policy.
378    *
379    * @param policy the new policy.  Valid values are:
380    * ALWAYS_UPDATE: always update the Caret position, even when Document
381    * updates don't occur on the Event Dispatcher thread.
382    * NEVER_UPDATE: don't update the Caret position unless the Document
383    * length becomes less than the Caret position (then update the
384    * Caret to the end of the Document).
385    * UPDATE_WHEN_ON_EDT: update the Caret position when the
386    * Document updates occur on the Event Dispatcher thread.  This is the
387    * default.
388    *
389    * @since 1.5
390    * @throws IllegalArgumentException if policy is not one of the above.
391    */
setUpdatePolicy(int policy)392   public void setUpdatePolicy (int policy)
393   {
394     if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE
395         && policy != UPDATE_WHEN_ON_EDT)
396       throw new
397         IllegalArgumentException
398         ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT");
399     this.policy = policy;
400   }
401 
402   /**
403    * Gets the caret update policy.
404    *
405    * @return the caret update policy.
406    * @since 1.5
407    */
getUpdatePolicy()408   public int getUpdatePolicy ()
409   {
410     return policy;
411   }
412 
413   /**
414    * Moves the caret position when the mouse is dragged over the text
415    * component, modifying the selectiony.
416    *
417    * <p>When the text component where the caret is installed is disabled,
418    * the selection is not change but you can still scroll the text and
419    * update the caret's location.</p>
420    *
421    * @param event the <code>MouseEvent</code> describing the drag operation
422    */
mouseDragged(MouseEvent event)423   public void mouseDragged(MouseEvent event)
424   {
425     if (event.getButton() == MouseEvent.BUTTON1)
426       {
427         if (textComponent.isEnabled())
428           moveCaret(event);
429         else
430           positionCaret(event);
431       }
432   }
433 
434   /**
435    * Indicates a mouse movement over the text component. Does nothing here.
436    *
437    * @param event the <code>MouseEvent</code> describing the mouse operation
438    */
mouseMoved(MouseEvent event)439   public void mouseMoved(MouseEvent event)
440   {
441     // Nothing to do here.
442   }
443 
444   /**
445    * When the click is received from Button 1 then the following actions
446    * are performed here:
447    *
448    * <ul>
449    * <li>If we receive a double click, the caret position (dot) is set
450    *   to the position associated to the mouse click and the word at
451    *   this location is selected. If there is no word at the pointer
452    *   the gap is selected instead.</li>
453    * <li>If we receive a triple click, the caret position (dot) is set
454    *   to the position associated to the mouse click and the line at
455    *   this location is selected.</li>
456    * </ul>
457    *
458    * @param event the <code>MouseEvent</code> describing the click operation
459    */
mouseClicked(MouseEvent event)460   public void mouseClicked(MouseEvent event)
461   {
462     // Do not modify selection if component is disabled.
463     if (!textComponent.isEnabled())
464       return;
465 
466     int count = event.getClickCount();
467 
468     if (event.getButton() == MouseEvent.BUTTON1 && count >= 2)
469       {
470         int newDot = getComponent().viewToModel(event.getPoint());
471         JTextComponent t = getComponent();
472 
473         try
474           {
475             if (count == 3)
476               {
477                 setDot(Utilities.getRowStart(t, newDot));
478                 moveDot( Utilities.getRowEnd(t, newDot));
479               }
480             else
481               {
482                 int wordStart = Utilities.getWordStart(t, newDot);
483 
484                 // When the mouse points at the offset of the first character
485                 // in a word Utilities().getPreviousWord will not return that
486                 // word but we want to select that. We have to use
487                 // Utilities.getWordStart() to get it.
488                 if (newDot == wordStart)
489                   {
490                     setDot(wordStart);
491                     moveDot(Utilities.getWordEnd(t, wordStart));
492                   }
493                 else
494                   {
495                     int nextWord = Utilities.getNextWord(t, newDot);
496                     int previousWord = Utilities.getPreviousWord(t, newDot);
497                     int previousWordEnd = Utilities.getWordEnd(t, previousWord);
498 
499                     // If the user clicked in the space between two words,
500                     // then select the space.
501                     if (newDot >= previousWordEnd && newDot <= nextWord)
502                       {
503                         setDot(previousWordEnd);
504                         moveDot(nextWord);
505                       }
506                     // Otherwise select the word under the mouse pointer.
507                     else
508                       {
509                         setDot(previousWord);
510                         moveDot(previousWordEnd);
511                       }
512                   }
513               }
514           }
515         catch(BadLocationException ble)
516           {
517             // TODO: Swallowing ok here?
518           }
519       }
520 
521   }
522 
523   /**
524    * Indicates that the mouse has entered the text component. Nothing is done
525    * here.
526    *
527    * @param event the <code>MouseEvent</code> describing the mouse operation
528    */
mouseEntered(MouseEvent event)529   public void mouseEntered(MouseEvent event)
530   {
531     // Nothing to do here.
532   }
533 
534   /**
535    * Indicates that the mouse has exited the text component. Nothing is done
536    * here.
537    *
538    * @param event the <code>MouseEvent</code> describing the mouse operation
539    */
mouseExited(MouseEvent event)540   public void mouseExited(MouseEvent event)
541   {
542     // Nothing to do here.
543   }
544 
545   /**
546    * If the button 1 is pressed, the caret position is updated to the
547    * position of the mouse click and the text component requests the input
548    * focus if it is enabled. If the SHIFT key is held down, the caret will
549    * be moved, which might select the text between the old and new location.
550    *
551    * @param event the <code>MouseEvent</code> describing the press operation
552    */
mousePressed(MouseEvent event)553   public void mousePressed(MouseEvent event)
554   {
555 
556     // The implementation assumes that consuming the event makes the AWT event
557     // mechanism forget about this event instance and not transfer focus.
558     // By observing how the RI reacts the following behavior has been
559     // implemented (in regard to text components):
560     // - a left-click moves the caret
561     // - a left-click when shift is held down expands the selection
562     // - a right-click or click with any additional mouse button
563     //   on a text component is ignored
564     // - a middle-click positions the caret and pastes the clipboard
565     //   contents.
566     // - a middle-click when shift is held down is ignored
567 
568     if (SwingUtilities.isLeftMouseButton(event))
569       {
570         // Handle the caret.
571         if (event.isShiftDown() && getDot() != -1)
572           {
573             moveCaret(event);
574           }
575         else
576           {
577             positionCaret(event);
578           }
579 
580         // Handle the focus.
581         if (textComponent != null && textComponent.isEnabled()
582             && textComponent.isRequestFocusEnabled())
583           {
584             textComponent.requestFocus();
585           }
586 
587         // TODO: Handle double click for selecting words.
588       }
589     else if(event.getButton() == MouseEvent.BUTTON2)
590       {
591         // Special handling for X11-style pasting.
592         if (! event.isShiftDown())
593           {
594             positionCaret(event);
595             textComponent.paste();
596           }
597       }
598   }
599 
600   /**
601    * Indicates that a mouse button has been released on the text component.
602    * Nothing is done here.
603    *
604    * @param event the <code>MouseEvent</code> describing the mouse operation
605    */
mouseReleased(MouseEvent event)606   public void mouseReleased(MouseEvent event)
607   {
608     // Nothing to do here.
609   }
610 
611   /**
612    * Sets the caret to <code>visible</code> if the text component is editable.
613    *
614    * @param event the <code>FocusEvent</code>
615    */
focusGained(FocusEvent event)616   public void focusGained(FocusEvent event)
617   {
618     if (textComponent.isEditable())
619       {
620         setVisible(true);
621         updateTimerStatus();
622       }
623   }
624 
625   /**
626    * Sets the caret to <code>invisible</code>.
627    *
628    * @param event the <code>FocusEvent</code>
629    */
focusLost(FocusEvent event)630   public void focusLost(FocusEvent event)
631   {
632     if (textComponent.isEditable() && event.isTemporary() == false)
633       {
634         setVisible(false);
635 
636         // Stop the blinker, if running.
637         if (blinkTimer != null && blinkTimer.isRunning())
638           blinkTimer.stop();
639       }
640   }
641 
642   /**
643    * Install (if not present) and start the timer, if the caret must blink. The
644    * caret does not blink if it is invisible, or the component is disabled or
645    * not editable.
646    */
updateTimerStatus()647   private void updateTimerStatus()
648   {
649     if (textComponent.isEnabled() && textComponent.isEditable())
650       {
651         if (blinkTimer == null)
652           initBlinkTimer();
653         if (!blinkTimer.isRunning())
654           blinkTimer.start();
655       }
656     else
657       {
658         if (blinkTimer != null)
659           blinkTimer.stop();
660       }
661   }
662 
663   /**
664    * Moves the caret to the position specified in the <code>MouseEvent</code>.
665    * This will cause a selection if the dot and mark are different.
666    *
667    * @param event the <code>MouseEvent</code> from which to fetch the position
668    */
moveCaret(MouseEvent event)669   protected void moveCaret(MouseEvent event)
670   {
671     int newDot = getComponent().viewToModel(event.getPoint());
672     moveDot(newDot);
673   }
674 
675   /**
676    * Repositions the caret to the position specified in the
677    * <code>MouseEvent</code>.
678    *
679    * @param event the <code>MouseEvent</code> from which to fetch the position
680    */
positionCaret(MouseEvent event)681   protected void positionCaret(MouseEvent event)
682   {
683     int newDot = getComponent().viewToModel(event.getPoint());
684     setDot(newDot);
685   }
686 
687   /**
688    * Deinstalls this <code>Caret</code> from the specified
689    * <code>JTextComponent</code>. This removes any listeners that have been
690    * registered by this <code>Caret</code>.
691    *
692    * @param c the text component from which to install this caret
693    */
deinstall(JTextComponent c)694   public void deinstall(JTextComponent c)
695   {
696     textComponent.removeFocusListener(this);
697     textComponent.removeMouseListener(this);
698     textComponent.removeMouseMotionListener(this);
699     textComponent.getDocument().removeDocumentListener(documentListener);
700     documentListener = null;
701     textComponent.removePropertyChangeListener(propertyChangeListener);
702     propertyChangeListener = null;
703     textComponent = null;
704 
705     // Deinstall blink timer if present.
706     if (blinkTimer != null)
707       blinkTimer.stop();
708     blinkTimer = null;
709   }
710 
711   /**
712    * Installs this <code>Caret</code> on the specified
713    * <code>JTextComponent</code>. This registers a couple of listeners
714    * on the text component.
715    *
716    * @param c the text component on which to install this caret
717    */
install(JTextComponent c)718   public void install(JTextComponent c)
719   {
720     textComponent = c;
721     textComponent.addFocusListener(this);
722     textComponent.addMouseListener(this);
723     textComponent.addMouseMotionListener(this);
724     propertyChangeListener = new PropertyChangeHandler();
725     textComponent.addPropertyChangeListener(propertyChangeListener);
726     documentListener = new DocumentHandler();
727 
728     Document doc = textComponent.getDocument();
729     if (doc != null)
730       doc.addDocumentListener(documentListener);
731 
732     active = textComponent.isEditable() && textComponent.isEnabled();
733 
734     repaint();
735   }
736 
737   /**
738    * Sets the current visual position of this <code>Caret</code>.
739    *
740    * @param p the Point to use for the saved location. May be <code>null</code>
741    *        to indicate that there is no visual location
742    */
setMagicCaretPosition(Point p)743   public void setMagicCaretPosition(Point p)
744   {
745     magicCaretPosition = p;
746   }
747 
748   /**
749    * Returns the current visual position of this <code>Caret</code>.
750    *
751    * @return the current visual position of this <code>Caret</code>
752    *
753    * @see #setMagicCaretPosition
754    */
getMagicCaretPosition()755   public Point getMagicCaretPosition()
756   {
757     return magicCaretPosition;
758   }
759 
760   /**
761    * Returns the current position of the <code>mark</code>. The
762    * <code>mark</code> marks the location in the <code>Document</code> that
763    * is the end of a selection. If there is no selection, the <code>mark</code>
764    * is the same as the <code>dot</code>.
765    *
766    * @return the current position of the mark
767    */
getMark()768   public int getMark()
769   {
770     return mark;
771   }
772 
clearHighlight()773   private void clearHighlight()
774   {
775     Highlighter highlighter = textComponent.getHighlighter();
776 
777     if (highlighter == null)
778       return;
779 
780     if (selectionVisible)
781       {
782     try
783       {
784         if (highlightEntry != null)
785           highlighter.changeHighlight(highlightEntry, 0, 0);
786 
787         // Free the global variable which stores the text component with an active
788         // selection.
789         if (componentWithSelection == textComponent)
790           componentWithSelection = null;
791       }
792     catch (BadLocationException e)
793       {
794         // This should never happen.
795         throw new InternalError();
796       }
797       }
798     else
799       {
800     if (highlightEntry != null)
801       {
802         highlighter.removeHighlight(highlightEntry);
803         highlightEntry = null;
804       }
805       }
806   }
807 
handleHighlight()808   private void handleHighlight()
809   {
810     Highlighter highlighter = textComponent.getHighlighter();
811 
812     if (highlighter == null)
813       return;
814 
815     int p0 = Math.min(dot, mark);
816     int p1 = Math.max(dot, mark);
817 
818     if (selectionVisible)
819       {
820         try
821           {
822             if (highlightEntry == null)
823               highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter());
824             else
825               highlighter.changeHighlight(highlightEntry, p0, p1);
826 
827             // If another component currently has a text selection clear that selection
828             // first.
829             if (componentWithSelection != null)
830               if (componentWithSelection != textComponent)
831                 {
832                   Caret c = componentWithSelection.getCaret();
833                   c.setDot(c.getDot());
834                 }
835             componentWithSelection = textComponent;
836 
837           }
838         catch (BadLocationException e)
839           {
840             // This should never happen.
841             throw new InternalError();
842           }
843       }
844     else
845       {
846         if (highlightEntry != null)
847           {
848             highlighter.removeHighlight(highlightEntry);
849             highlightEntry = null;
850           }
851       }
852   }
853 
854   /**
855    * Sets the visiblity state of the selection.
856    *
857    * @param v <code>true</code> if the selection should be visible,
858    *        <code>false</code> otherwise
859    */
setSelectionVisible(boolean v)860   public void setSelectionVisible(boolean v)
861   {
862     if (selectionVisible == v)
863       return;
864 
865     selectionVisible = v;
866     handleHighlight();
867     repaint();
868   }
869 
870   /**
871    * Returns <code>true</code> if the selection is currently visible,
872    * <code>false</code> otherwise.
873    *
874    * @return <code>true</code> if the selection is currently visible,
875    *         <code>false</code> otherwise
876    */
isSelectionVisible()877   public boolean isSelectionVisible()
878   {
879     return selectionVisible;
880   }
881 
882   /**
883    * Causes the <code>Caret</code> to repaint itself.
884    */
repaint()885   protected final void repaint()
886   {
887     getComponent().repaint(x, y, width, height);
888   }
889 
890   /**
891    * Paints this <code>Caret</code> using the specified <code>Graphics</code>
892    * context.
893    *
894    * @param g the graphics context to use
895    */
paint(Graphics g)896   public void paint(Graphics g)
897   {
898     JTextComponent comp = getComponent();
899     if (comp == null)
900       return;
901 
902     // Make sure the dot has a sane position.
903     dot = Math.min(dot, textComponent.getDocument().getLength());
904     dot = Math.max(dot, 0);
905 
906     Rectangle rect = null;
907 
908     try
909       {
910         rect = textComponent.modelToView(dot);
911       }
912     catch (BadLocationException e)
913       {
914         // Let's ignore that. This shouldn't really occur. But if it
915         // does (it seems that this happens when the model is mutating),
916         // it causes no real damage. Uncomment this for debugging.
917         // e.printStackTrace();
918       }
919 
920     if (rect == null)
921       return;
922 
923     // Check if paint has possibly been called directly, without a previous
924     // call to damage(). In this case we need to do some cleanup first.
925     if ((x != rect.x) || (y != rect.y))
926       {
927         repaint(); // Erase previous location of caret.
928         x = rect.x;
929         y = rect.y;
930         width = 1;
931         height = rect.height;
932       }
933 
934     // Now draw the caret on the new position if visible.
935     if (visible && active)
936       {
937         g.setColor(textComponent.getCaretColor());
938         g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1);
939       }
940   }
941 
942   /**
943    * Returns all registered event listeners of the specified type.
944    *
945    * @param listenerType the type of listener to return
946    *
947    * @return all registered event listeners of the specified type
948    */
getListeners(Class<T> listenerType)949   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
950   {
951     return listenerList.getListeners(listenerType);
952   }
953 
954   /**
955    * Registers a {@link ChangeListener} that is notified whenever that state
956    * of this <code>Caret</code> changes.
957    *
958    * @param listener the listener to register to this caret
959    */
addChangeListener(ChangeListener listener)960   public void addChangeListener(ChangeListener listener)
961   {
962     listenerList.add(ChangeListener.class, listener);
963   }
964 
965   /**
966    * Removes a {@link ChangeListener} from the list of registered listeners.
967    *
968    * @param listener the listener to remove
969    */
removeChangeListener(ChangeListener listener)970   public void removeChangeListener(ChangeListener listener)
971   {
972     listenerList.remove(ChangeListener.class, listener);
973   }
974 
975   /**
976    * Returns all registered {@link ChangeListener}s of this <code>Caret</code>.
977    *
978    * @return all registered {@link ChangeListener}s of this <code>Caret</code>
979    */
getChangeListeners()980   public ChangeListener[] getChangeListeners()
981   {
982     return (ChangeListener[]) getListeners(ChangeListener.class);
983   }
984 
985   /**
986    * Notifies all registered {@link ChangeListener}s that the state
987    * of this <code>Caret</code> has changed.
988    */
fireStateChanged()989   protected void fireStateChanged()
990   {
991     ChangeListener[] listeners = getChangeListeners();
992 
993     for (int index = 0; index < listeners.length; ++index)
994       listeners[index].stateChanged(changeEvent);
995   }
996 
997   /**
998    * Returns the <code>JTextComponent</code> on which this <code>Caret</code>
999    * is installed.
1000    *
1001    * @return the <code>JTextComponent</code> on which this <code>Caret</code>
1002    *         is installed
1003    */
getComponent()1004   protected final JTextComponent getComponent()
1005   {
1006     return textComponent;
1007   }
1008 
1009   /**
1010    * Returns the blink rate of this <code>Caret</code> in milliseconds.
1011    * A value of <code>0</code> means that the caret does not blink.
1012    *
1013    * @return the blink rate of this <code>Caret</code> or <code>0</code> if
1014    *         this caret does not blink
1015    */
getBlinkRate()1016   public int getBlinkRate()
1017   {
1018     return blinkRate;
1019   }
1020 
1021   /**
1022    * Sets the blink rate of this <code>Caret</code> in milliseconds.
1023    * A value of <code>0</code> means that the caret does not blink.
1024    *
1025    * @param rate the new blink rate to set
1026    */
setBlinkRate(int rate)1027   public void setBlinkRate(int rate)
1028   {
1029     if (blinkTimer != null)
1030       blinkTimer.setDelay(rate);
1031     blinkRate = rate;
1032   }
1033 
1034   /**
1035    * Returns the current position of this <code>Caret</code> within the
1036    * <code>Document</code>.
1037    *
1038    * @return the current position of this <code>Caret</code> within the
1039    *         <code>Document</code>
1040    */
getDot()1041   public int getDot()
1042   {
1043     return dot;
1044   }
1045 
1046   /**
1047    * Moves the <code>dot</code> location without touching the
1048    * <code>mark</code>. This is used when making a selection.
1049    *
1050    * <p>If the underlying text component has a {@link NavigationFilter}
1051    * installed the caret will call the corresponding method of that object.</p>
1052    *
1053    * @param dot the location where to move the dot
1054    *
1055    * @see #setDot(int)
1056    */
moveDot(int dot)1057   public void moveDot(int dot)
1058   {
1059     NavigationFilter filter = textComponent.getNavigationFilter();
1060     if (filter != null)
1061       filter.moveDot(getBypass(), dot, Bias.Forward);
1062     else
1063       moveDotImpl(dot);
1064   }
1065 
moveDotImpl(int dot)1066   void moveDotImpl(int dot)
1067   {
1068     if (dot >= 0)
1069       {
1070         Document doc = textComponent.getDocument();
1071         if (doc != null)
1072           this.dot = Math.min(dot, doc.getLength());
1073         this.dot = Math.max(this.dot, 0);
1074 
1075         handleHighlight();
1076 
1077         appear();
1078       }
1079   }
1080 
1081   /**
1082    * Sets the current position of this <code>Caret</code> within the
1083    * <code>Document</code>. This also sets the <code>mark</code> to the new
1084    * location.
1085    *
1086    * <p>If the underlying text component has a {@link NavigationFilter}
1087    * installed the caret will call the corresponding method of that object.</p>
1088    *
1089    * @param dot
1090    *          the new position to be set
1091    * @see #moveDot(int)
1092    */
setDot(int dot)1093   public void setDot(int dot)
1094   {
1095     NavigationFilter filter = textComponent.getNavigationFilter();
1096     if (filter != null)
1097       filter.setDot(getBypass(), dot, Bias.Forward);
1098     else
1099       setDotImpl(dot);
1100   }
1101 
setDotImpl(int dot)1102   void setDotImpl(int dot)
1103   {
1104     if (dot >= 0)
1105       {
1106         Document doc = textComponent.getDocument();
1107         if (doc != null)
1108           this.dot = Math.min(dot, doc.getLength());
1109         this.dot = Math.max(this.dot, 0);
1110         this.mark = this.dot;
1111 
1112         clearHighlight();
1113 
1114         appear();
1115       }
1116   }
1117 
1118   /**
1119    * Show the caret (may be hidden due blinking) and adjust the timer not to
1120    * hide it (possibly immediately).
1121    *
1122    * @author Audrius Meskauskas (AudriusA@Bioinformatics.org)
1123    */
appear()1124   void appear()
1125   {
1126     // All machinery is only required if the carret is blinking.
1127     if (blinkListener != null)
1128       {
1129         blinkListener.ignoreNextEvent = true;
1130 
1131         // If the caret is visible, erase the current position by repainting
1132         // over.
1133         if (visible)
1134           repaint();
1135 
1136         // Draw the caret in the new position.
1137         visible = true;
1138 
1139         Rectangle area = null;
1140         int dot = getDot();
1141         try
1142           {
1143             area = getComponent().modelToView(dot);
1144           }
1145         catch (BadLocationException e)
1146           {
1147             // Let's ignore that. This shouldn't really occur. But if it
1148             // does (it seems that this happens when the model is mutating),
1149             // it causes no real damage. Uncomment this for debugging.
1150             // e.printStackTrace();
1151           }
1152         if (area != null)
1153           {
1154             adjustVisibility(area);
1155             if (getMagicCaretPosition() == null)
1156               setMagicCaretPosition(new Point(area.x, area.y));
1157             damage(area);
1158           }
1159       }
1160     repaint();
1161   }
1162 
1163   /**
1164    * Returns <code>true</code> if this <code>Caret</code> is blinking,
1165    * and <code>false</code> if not. The returned value is independent of
1166    * the visiblity of this <code>Caret</code> as returned by {@link #isVisible()}.
1167    *
1168    * @return <code>true</code> if this <code>Caret</code> is blinking,
1169    *         and <code>false</code> if not.
1170    * @see #isVisible()
1171    * @since 1.5
1172    */
isActive()1173   public boolean isActive()
1174   {
1175     if (blinkTimer != null)
1176       return blinkTimer.isRunning();
1177 
1178     return false;
1179   }
1180 
1181   /**
1182    * Returns <code>true</code> if this <code>Caret</code> is currently visible,
1183    * and <code>false</code> if it is not.
1184    *
1185    * @return <code>true</code> if this <code>Caret</code> is currently visible,
1186    *         and <code>false</code> if it is not
1187    */
isVisible()1188   public boolean isVisible()
1189   {
1190     return visible && active;
1191   }
1192 
1193   /**
1194    * Sets the visibility state of the caret. <code>true</code> shows the
1195    * <code>Caret</code>, <code>false</code> hides it.
1196    *
1197    * @param v the visibility to set
1198    */
setVisible(boolean v)1199   public void setVisible(boolean v)
1200   {
1201     if (v != visible)
1202       {
1203         visible = v;
1204         updateTimerStatus();
1205         Rectangle area = null;
1206         int dot = getDot();
1207         try
1208           {
1209             area = getComponent().modelToView(dot);
1210           }
1211         catch (BadLocationException e)
1212           {
1213             AssertionError ae;
1214             ae = new AssertionError("Unexpected bad caret location: " + dot);
1215             ae.initCause(e);
1216             throw ae;
1217           }
1218         if (area != null)
1219           damage(area);
1220       }
1221   }
1222 
1223   /**
1224    * Returns the {@link Highlighter.HighlightPainter} that should be used
1225    * to paint the selection.
1226    *
1227    * @return the {@link Highlighter.HighlightPainter} that should be used
1228    *         to paint the selection
1229    */
getSelectionPainter()1230   protected Highlighter.HighlightPainter getSelectionPainter()
1231   {
1232     return DefaultHighlighter.DefaultPainter;
1233   }
1234 
1235   /**
1236    * Updates the carets rectangle properties to the specified rectangle and
1237    * repaints the caret.
1238    *
1239    * @param r the rectangle to set as the caret rectangle
1240    */
damage(Rectangle r)1241   protected void damage(Rectangle r)
1242   {
1243     if (r == null)
1244       return;
1245     x = r.x;
1246     y = r.y;
1247     width = 1;
1248     // height is normally set in paint and we leave it untouched. However, we
1249     // must set a valid value here, since otherwise the painting mechanism
1250     // sets a zero clip and never calls paint.
1251     if (height <= 0)
1252       try
1253         {
1254           height = textComponent.modelToView(dot).height;
1255         }
1256       catch (BadLocationException ble)
1257         {
1258           // Should not happen.
1259           throw new InternalError("Caret location not within document range.");
1260         }
1261 
1262     repaint();
1263   }
1264 
1265   /**
1266    * Adjusts the text component so that the caret is visible. This default
1267    * implementation simply calls
1268    * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component.
1269    * Subclasses may wish to change this.
1270    */
adjustVisibility(Rectangle rect)1271   protected void adjustVisibility(Rectangle rect)
1272   {
1273     getComponent().scrollRectToVisible(rect);
1274   }
1275 
1276   /**
1277    * Initializes the blink timer.
1278    */
initBlinkTimer()1279   private void initBlinkTimer()
1280   {
1281     // Setup the blink timer.
1282     blinkListener = new BlinkTimerListener();
1283     blinkTimer = new Timer(getBlinkRate(), blinkListener);
1284     blinkTimer.setRepeats(true);
1285   }
1286 
1287 }
1288