001/* DefaultCaret.java --
002   Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package javax.swing.text;
039
040import java.awt.Graphics;
041import java.awt.Point;
042import java.awt.Rectangle;
043import java.awt.event.ActionEvent;
044import java.awt.event.ActionListener;
045import java.awt.event.FocusEvent;
046import java.awt.event.FocusListener;
047import java.awt.event.MouseEvent;
048import java.awt.event.MouseListener;
049import java.awt.event.MouseMotionListener;
050import java.beans.PropertyChangeEvent;
051import java.beans.PropertyChangeListener;
052import java.util.EventListener;
053
054import javax.swing.JComponent;
055import javax.swing.SwingUtilities;
056import javax.swing.Timer;
057import javax.swing.event.ChangeEvent;
058import javax.swing.event.ChangeListener;
059import javax.swing.event.DocumentEvent;
060import javax.swing.event.DocumentListener;
061import javax.swing.event.EventListenerList;
062import 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 */
070public 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}