001    /* DefaultCaret.java --
002       Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.swing.text;
039    
040    import java.awt.Graphics;
041    import java.awt.Point;
042    import java.awt.Rectangle;
043    import java.awt.event.ActionEvent;
044    import java.awt.event.ActionListener;
045    import java.awt.event.FocusEvent;
046    import java.awt.event.FocusListener;
047    import java.awt.event.MouseEvent;
048    import java.awt.event.MouseListener;
049    import java.awt.event.MouseMotionListener;
050    import java.beans.PropertyChangeEvent;
051    import java.beans.PropertyChangeListener;
052    import java.util.EventListener;
053    
054    import javax.swing.JComponent;
055    import javax.swing.SwingUtilities;
056    import javax.swing.Timer;
057    import javax.swing.event.ChangeEvent;
058    import javax.swing.event.ChangeListener;
059    import javax.swing.event.DocumentEvent;
060    import javax.swing.event.DocumentListener;
061    import javax.swing.event.EventListenerList;
062    import javax.swing.text.Position.Bias;
063    
064    /**
065     * The default implementation of the {@link Caret} interface.
066     *
067     * @author original author unknown
068     * @author Roman Kennke (roman@kennke.org)
069     */
070    public class DefaultCaret extends Rectangle
071      implements Caret, FocusListener, MouseListener, MouseMotionListener
072    {
073    
074      /** A text component in the current VM which currently has a
075       * text selection or <code>null</code>.
076       */
077      static JTextComponent componentWithSelection;
078    
079      /** An implementation of NavigationFilter.FilterBypass which delegates
080       * to the corresponding methods of the <code>DefaultCaret</code>.
081       *
082       * @author Robert Schuster (robertschuster@fsfe.org)
083       */
084      class Bypass extends NavigationFilter.FilterBypass
085      {
086    
087        public Caret getCaret()
088        {
089          return DefaultCaret.this;
090        }
091    
092        public void moveDot(int dot, Bias bias)
093        {
094          DefaultCaret.this.moveDotImpl(dot);
095        }
096    
097        public void setDot(int dot, Bias bias)
098        {
099          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         */
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         */
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         */
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         */
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         */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
768      public int getMark()
769      {
770        return mark;
771      }
772    
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    
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       */
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       */
877      public boolean isSelectionVisible()
878      {
879        return selectionVisible;
880      }
881    
882      /**
883       * Causes the <code>Caret</code> to repaint itself.
884       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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    
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       */
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    
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       */
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       */
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       */
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       */
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       */
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       */
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       */
1271      protected void adjustVisibility(Rectangle rect)
1272      {
1273        getComponent().scrollRectToVisible(rect);
1274      }
1275    
1276      /**
1277       * Initializes the blink timer.
1278       */
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    }