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 }