KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > DefaultCaret


1 /*
2  * @(#)DefaultCaret.java 1.141 06/04/18
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.awt.*;
10 import java.awt.event.*;
11 import java.awt.datatransfer.*;
12 import java.beans.*;
13 import java.awt.event.ActionEvent JavaDoc;
14 import java.awt.event.ActionListener JavaDoc;
15 import java.io.*;
16 import javax.swing.*;
17 import javax.swing.event.*;
18 import javax.swing.plaf.*;
19 import java.util.EventListener JavaDoc;
20 import com.sun.java.swing.SwingUtilities2;
21
22 /**
23  * A default implementation of Caret. The caret is rendered as
24  * a vertical line in the color specified by the CaretColor property
25  * of the associated JTextComponent. It can blink at the rate specified
26  * by the BlinkRate property.
27  * <p>
28  * This implementation expects two sources of asynchronous notification.
29  * The timer thread fires asynchronously, and causes the caret to simply
30  * repaint the most recent bounding box. The caret also tracks change
31  * as the document is modified. Typically this will happen on the
32  * event dispatch thread as a result of some mouse or keyboard event.
33  * The caret behavior on both synchronous and asynchronous documents updates
34  * is controlled by <code>UpdatePolicy</code> property. The repaint of the
35  * new caret location will occur on the event thread in any case, as calls to
36  * <code>modelToView</code> are only safe on the event thread.
37  * <p>
38  * The caret acts as a mouse and focus listener on the text component
39  * it has been installed in, and defines the caret semantics based upon
40  * those events. The listener methods can be reimplemented to change the
41  * semantics.
42  * By default, the first mouse button will be used to set focus and caret
43  * position. Dragging the mouse pointer with the first mouse button will
44  * sweep out a selection that is contiguous in the model. If the associated
45  * text component is editable, the caret will become visible when focus
46  * is gained, and invisible when focus is lost.
47  * <p>
48  * The Highlighter bound to the associated text component is used to
49  * render the selection by default.
50  * Selection appearance can be customized by supplying a
51  * painter to use for the highlights. By default a painter is used that
52  * will render a solid color as specified in the associated text component
53  * in the <code>SelectionColor</code> property. This can easily be changed
54  * by reimplementing the
55  * <a HREF="#getSelectionHighlighter">getSelectionHighlighter</a>
56  * method.
57  * <p>
58  * A customized caret appearance can be achieved by reimplementing
59  * the paint method. If the paint method is changed, the damage method
60  * should also be reimplemented to cause a repaint for the area needed
61  * to render the caret. The caret extends the Rectangle class which
62  * is used to hold the bounding box for where the caret was last rendered.
63  * This enables the caret to repaint in a thread-safe manner when the
64  * caret moves without making a call to modelToView which is unstable
65  * between model updates and view repair (i.e. the order of delivery
66  * to DocumentListeners is not guaranteed).
67  * <p>
68  * The magic caret position is set to null when the caret position changes.
69  * A timer is used to determine the new location (after the caret change).
70  * When the timer fires, if the magic caret position is still null it is
71  * reset to the current caret position. Any actions that change
72  * the caret position and want the magic caret position to remain the
73  * same, must remember the magic caret position, change the cursor, and
74  * then set the magic caret position to its original value. This has the
75  * benefit that only actions that want the magic caret position to persist
76  * (such as open/down) need to know about it.
77  * <p>
78  * <strong>Warning:</strong>
79  * Serialized objects of this class will not be compatible with
80  * future Swing releases. The current serialization support is
81  * appropriate for short term storage or RMI between applications running
82  * the same version of Swing. As of 1.4, support for long term storage
83  * of all JavaBeans<sup><font size="-2">TM</font></sup>
84  * has been added to the <code>java.beans</code> package.
85  * Please see {@link java.beans.XMLEncoder}.
86  *
87  * @author Timothy Prinzing
88  * @version 1.141 04/18/06
89  * @see Caret
90  */

91 public class DefaultCaret extends Rectangle implements Caret JavaDoc, FocusListener, MouseListener, MouseMotionListener {
92
93     /**
94      * Indicates that the caret position is to be updated only when
95      * document changes are performed on the Event Dispatching Thread.
96      * @see #setUpdatePolicy
97      * @see #getUpdatePolicy
98      * @since 1.5
99      */

100     public static final int UPDATE_WHEN_ON_EDT = 0;
101
102     /**
103      * Indicates that the caret should remain at the same
104      * absolute position in the document regardless of any document
105      * updates, except when the document length becomes less than
106      * the current caret position due to removal. In that case the caret
107      * position is adjusted to the end of the document.
108      *
109      * @see #setUpdatePolicy
110      * @see #getUpdatePolicy
111      * @since 1.5
112      */

113     public static final int NEVER_UPDATE = 1;
114
115     /**
116      * Indicates that the caret position is to be <b>always</b>
117      * updated accordingly to the document changes regardless whether
118      * the document updates are performed on the Event Dispatching Thread
119      * or not.
120      *
121      * @see #setUpdatePolicy
122      * @see #getUpdatePolicy
123      * @since 1.5
124      */

125     public static final int ALWAYS_UPDATE = 2;
126
127     /**
128      * Constructs a default caret.
129      */

130     public DefaultCaret() {
131     }
132
133     /**
134      * Sets the caret movement policy on the document updates. Normally
135      * the caret updates its absolute position within the document on
136      * insertions occurred before or at the caret position and
137      * on removals before the caret position. 'Absolute position'
138      * means here the position relative to the start of the document.
139      * For example if
140      * a character is typed within editable text component it is inserted
141      * at the caret position and the caret moves to the next absolute
142      * position within the document due to insertion and if
143      * <code>BACKSPACE</code> is typed then caret decreases its absolute
144      * position due to removal of a character before it. Sometimes
145      * it may be useful to turn off the caret position updates so that
146      * the caret stays at the same absolute position within the
147      * document position regardless of any document updates.
148      * <p>
149      * The following update policies are allowed:
150      * <ul>
151      * <li><code>NEVER_UPDATE</code>: the caret stays at the same
152      * absolute position in the document regardless of any document
153      * updates, except when document length becomes less than
154      * the current caret position due to removal. In that case caret
155      * position is adjusted to the end of the document.
156      * The caret doesn't try to keep itself visible by scrolling
157      * the associated view when using this policy. </li>
158      * <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
159      * changes. For regular changes it increases its position
160      * if an insertion occurs before or at its current position,
161      * and decreases position if a removal occurs before
162      * its current position. For undo/redo updates it is always
163      * moved to the position where update occurred. The caret
164      * also tries to keep itself visible by calling
165      * <code>adjustVisibility</code> method.</li>
166      * <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
167      * if the document updates are performed on the Event Dispatching Thread
168      * and like <code>NEVER_UPDATE</code> if updates are performed on
169      * other thread. </li>
170      * </ul> <p>
171      * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
172      *
173      * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
174      * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
175      * @throws IllegalArgumentException if invalid value is passed
176      *
177      * @see #getUpdatePolicy
178      * @see #adjustVisibility
179      * @see #UPDATE_WHEN_ON_EDT
180      * @see #NEVER_UPDATE
181      * @see #ALWAYS_UPDATE
182      *
183      * @since 1.5
184      */

185     public void setUpdatePolicy(int policy) {
186         updatePolicy = policy;
187     }
188
189     /**
190      * Gets the caret movement policy on document updates.
191      *
192      * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
193      * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
194      *
195      * @see #setUpdatePolicy
196      * @see #UPDATE_WHEN_ON_EDT
197      * @see #NEVER_UPDATE
198      * @see #ALWAYS_UPDATE
199      *
200      * @since 1.5
201      */

202     public int getUpdatePolicy() {
203         return updatePolicy;
204     }
205
206     /**
207      * Gets the text editor component that this caret is
208      * is bound to.
209      *
210      * @return the component
211      */

212     protected final JTextComponent JavaDoc getComponent() {
213     return component;
214     }
215
216     /**
217      * Cause the caret to be painted. The repaint
218      * area is the bounding box of the caret (i.e.
219      * the caret rectangle or <em>this</em>).
220      * <p>
221      * This method is thread safe, although most Swing methods
222      * are not. Please see
223      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">
224      * Threads and Swing</A> for more information.
225      */

226     protected final synchronized void repaint() {
227     if (component != null) {
228         component.repaint(x, y, width, height);
229     }
230     }
231
232     /**
233      * Damages the area surrounding the caret to cause
234      * it to be repainted in a new location. If paint()
235      * is reimplemented, this method should also be
236      * reimplemented. This method should update the
237      * caret bounds (x, y, width, and height).
238      *
239      * @param r the current location of the caret
240      * @see #paint
241      */

242     protected synchronized void damage(Rectangle r) {
243         if (r != null) {
244             int damageWidth = getCaretWidth(r.height);
245             x = r.x - 4 - (damageWidth >> 1);
246             y = r.y;
247             width = 9 + damageWidth;
248             height = r.height;
249             repaint();
250         }
251     }
252
253     /**
254      * Scrolls the associated view (if necessary) to make
255      * the caret visible. Since how this should be done
256      * is somewhat of a policy, this method can be
257      * reimplemented to change the behavior. By default
258      * the scrollRectToVisible method is called on the
259      * associated component.
260      *
261      * @param nloc the new position to scroll to
262      */

263     protected void adjustVisibility(Rectangle nloc) {
264         if(component == null) {
265             return;
266         }
267         if (SwingUtilities.isEventDispatchThread()) {
268                 component.scrollRectToVisible(nloc);
269         } else {
270             SwingUtilities.invokeLater(new SafeScroller(nloc));
271         }
272     }
273
274     /**
275      * Gets the painter for the Highlighter.
276      *
277      * @return the painter
278      */

279     protected Highlighter.HighlightPainter JavaDoc getSelectionPainter() {
280     return DefaultHighlighter.DefaultPainter;
281     }
282
283     /**
284      * Tries to set the position of the caret from
285      * the coordinates of a mouse event, using viewToModel().
286      *
287      * @param e the mouse event
288      */

289     protected void positionCaret(MouseEvent e) {
290     Point pt = new Point(e.getX(), e.getY());
291     Position.Bias JavaDoc[] biasRet = new Position.Bias JavaDoc[1];
292     int pos = component.getUI().viewToModel(component, pt, biasRet);
293     if(biasRet[0] == null)
294         biasRet[0] = Position.Bias.Forward;
295     if (pos >= 0) {
296         setDot(pos, biasRet[0]);
297     }
298     }
299
300     /**
301      * Tries to move the position of the caret from
302      * the coordinates of a mouse event, using viewToModel().
303      * This will cause a selection if the dot and mark
304      * are different.
305      *
306      * @param e the mouse event
307      */

308     protected void moveCaret(MouseEvent e) {
309     Point pt = new Point(e.getX(), e.getY());
310     Position.Bias JavaDoc[] biasRet = new Position.Bias JavaDoc[1];
311     int pos = component.getUI().viewToModel(component, pt, biasRet);
312     if(biasRet[0] == null)
313         biasRet[0] = Position.Bias.Forward;
314     if (pos >= 0) {
315         moveDot(pos, biasRet[0]);
316     }
317     }
318
319     // --- FocusListener methods --------------------------
320

321     /**
322      * Called when the component containing the caret gains
323      * focus. This is implemented to set the caret to visible
324      * if the component is editable.
325      *
326      * @param e the focus event
327      * @see FocusListener#focusGained
328      */

329     public void focusGained(FocusEvent e) {
330     if (component.isEnabled()) {
331         if (component.isEditable()) {
332         setVisible(true);
333         }
334         setSelectionVisible(true);
335     }
336     }
337
338     /**
339      * Called when the component containing the caret loses
340      * focus. This is implemented to set the caret to visibility
341      * to false.
342      *
343      * @param e the focus event
344      * @see FocusListener#focusLost
345      */

346     public void focusLost(FocusEvent e) {
347     setVisible(false);
348         setSelectionVisible(ownsSelection || e.isTemporary());
349     }
350
351     
352     /**
353      * Selects word based on the MouseEvent
354      */

355     private void selectWord(MouseEvent e) {
356         if (selectedWordEvent != null
357             && selectedWordEvent.getX() == e.getX()
358             && selectedWordEvent.getY() == e.getY()) {
359             //we already done selection for this
360
return;
361         }
362             Action a = null;
363             ActionMap map = getComponent().getActionMap();
364             if (map != null) {
365             a = map.get(DefaultEditorKit.selectWordAction);
366             }
367             if (a == null) {
368             if (selectWord == null) {
369                 selectWord = new DefaultEditorKit.SelectWordAction JavaDoc();
370             }
371             a = selectWord;
372             }
373             a.actionPerformed(new ActionEvent JavaDoc(getComponent(),
374                               ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
375         selectedWordEvent = e;
376     }
377
378     // --- MouseListener methods -----------------------------------
379

380     /**
381      * Called when the mouse is clicked. If the click was generated
382      * from button1, a double click selects a word,
383      * and a triple click the current line.
384      *
385      * @param e the mouse event
386      * @see MouseListener#mouseClicked
387      */

388     public void mouseClicked(MouseEvent e) {
389         int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
390
391     if (! e.isConsumed()) {
392         if (SwingUtilities.isLeftMouseButton(e)) {
393         // mouse 1 behavior
394
if(nclicks == 1) {
395                     selectedWordEvent = null;
396                 } else if(nclicks == 2
397                           && SwingUtilities2.canEventAccessSystemClipboard(e)) {
398                     selectWord(e);
399                     selectedWordEvent = null;
400         } else if(nclicks == 3
401                           && SwingUtilities2.canEventAccessSystemClipboard(e)) {
402             Action a = null;
403             ActionMap map = getComponent().getActionMap();
404             if (map != null) {
405             a = map.get(DefaultEditorKit.selectLineAction);
406             }
407             if (a == null) {
408             if (selectLine == null) {
409                 selectLine = new DefaultEditorKit.SelectLineAction JavaDoc();
410             }
411             a = selectLine;
412             }
413             a.actionPerformed(new ActionEvent JavaDoc(getComponent(),
414                               ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
415         }
416         } else if (SwingUtilities.isMiddleMouseButton(e)) {
417         // mouse 2 behavior
418
if (nclicks == 1 && component.isEditable() && component.isEnabled()
419                     && SwingUtilities2.canEventAccessSystemClipboard(e)) {
420             // paste system selection, if it exists
421
JTextComponent JavaDoc c = (JTextComponent JavaDoc) e.getSource();
422             if (c != null) {
423             try {
424                 Toolkit tk = c.getToolkit();
425                 Clipboard buffer = tk.getSystemSelection();
426                 if (buffer != null) {
427                 // platform supports system selections, update it.
428
adjustCaret(e);
429                 TransferHandler th = c.getTransferHandler();
430                 if (th != null) {
431                                     Transferable trans = null;
432
433                                     try {
434                                         trans = buffer.getContents(null);
435                                     } catch (IllegalStateException JavaDoc ise) {
436                                         // clipboard was unavailable
437
UIManager.getLookAndFeel().provideErrorFeedback(c);
438                                     }
439
440                                     if (trans != null) {
441                                         th.importData(c, trans);
442                                     }
443                 }
444                                 adjustFocus(true);
445                 }
446             } catch (HeadlessException he) {
447                 // do nothing... there is no system clipboard
448
}
449             }
450         }
451         }
452     }
453     }
454
455     /**
456      * If button 1 is pressed, this is implemented to
457      * request focus on the associated text component,
458      * and to set the caret position. If the shift key is held down,
459      * the caret will be moved, potentially resulting in a selection,
460      * otherwise the
461      * caret position will be set to the new location. If the component
462      * is not enabled, there will be no request for focus.
463      *
464      * @param e the mouse event
465      * @see MouseListener#mousePressed
466      */

467     public void mousePressed(MouseEvent e) {
468         int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
469
470         if (SwingUtilities.isLeftMouseButton(e)) {
471             if (e.isConsumed()) {
472                 shouldHandleRelease = true;
473             } else {
474                 shouldHandleRelease = false;
475                 adjustCaretAndFocus(e);
476                 if (nclicks == 2
477                     && SwingUtilities2.canEventAccessSystemClipboard(e)) {
478                     selectWord(e);
479                 }
480             }
481         }
482     }
483
484     void adjustCaretAndFocus(MouseEvent e) {
485         adjustCaret(e);
486         adjustFocus(false);
487     }
488
489     /**
490      * Adjusts the caret location based on the MouseEvent.
491      */

492     private void adjustCaret(MouseEvent e) {
493     if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
494         getDot() != -1) {
495         moveCaret(e);
496     } else {
497         positionCaret(e);
498     }
499     }
500
501     /**
502      * Adjusts the focus, if necessary.
503      *
504      * @param inWindow if true indicates requestFocusInWindow should be used
505      */

506     private void adjustFocus(boolean inWindow) {
507     if ((component != null) && component.isEnabled() &&
508                                    component.isRequestFocusEnabled()) {
509             if (inWindow) {
510                 component.requestFocusInWindow();
511             }
512             else {
513                 component.requestFocus();
514             }
515     }
516     }
517
518     /**
519      * Called when the mouse is released.
520      *
521      * @param e the mouse event
522      * @see MouseListener#mouseReleased
523      */

524     public void mouseReleased(MouseEvent e) {
525         if (!e.isConsumed()
526                 && shouldHandleRelease
527                 && SwingUtilities.isLeftMouseButton(e)) {
528
529             adjustCaretAndFocus(e);
530         }
531     }
532
533     /**
534      * Called when the mouse enters a region.
535      *
536      * @param e the mouse event
537      * @see MouseListener#mouseEntered
538      */

539     public void mouseEntered(MouseEvent e) {
540     }
541
542     /**
543      * Called when the mouse exits a region.
544      *
545      * @param e the mouse event
546      * @see MouseListener#mouseExited
547      */

548     public void mouseExited(MouseEvent e) {
549     }
550
551     // --- MouseMotionListener methods -------------------------
552

553     /**
554      * Moves the caret position
555      * according to the mouse pointer's current
556      * location. This effectively extends the
557      * selection. By default, this is only done
558      * for mouse button 1.
559      *
560      * @param e the mouse event
561      * @see MouseMotionListener#mouseDragged
562      */

563     public void mouseDragged(MouseEvent e) {
564     if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
565         moveCaret(e);
566     }
567     }
568
569     /**
570      * Called when the mouse is moved.
571      *
572      * @param e the mouse event
573      * @see MouseMotionListener#mouseMoved
574      */

575     public void mouseMoved(MouseEvent e) {
576     }
577
578     // ---- Caret methods ---------------------------------
579

580     /**
581      * Renders the caret as a vertical line. If this is reimplemented
582      * the damage method should also be reimplemented as it assumes the
583      * shape of the caret is a vertical line. Sets the caret color to
584      * the value returned by getCaretColor().
585      * <p>
586      * If there are multiple text directions present in the associated
587      * document, a flag indicating the caret bias will be rendered.
588      * This will occur only if the associated document is a subclass
589      * of AbstractDocument and there are multiple bidi levels present
590      * in the bidi element structure (i.e. the text has multiple
591      * directions associated with it).
592      *
593      * @param g the graphics context
594      * @see #damage
595      */

596     public void paint(Graphics g) {
597     if(isVisible()) {
598         try {
599         TextUI mapper = component.getUI();
600         Rectangle r = mapper.modelToView(component, dot, dotBias);
601
602                 if ((r == null) || ((r.width == 0) && (r.height == 0))) {
603                     return;
604                 }
605                 if (width > 0 && height > 0 &&
606                                 !this._contains(r.x, r.y, r.width, r.height)) {
607                     // We seem to have gotten out of sync and no longer
608
// contain the right location, adjust accordingly.
609
Rectangle clip = g.getClipBounds();
610
611                     if (clip != null && !clip.contains(this)) {
612                         // Clip doesn't contain the old location, force it
613
// to be repainted lest we leave a caret around.
614
repaint();
615                     }
616                     // This will potentially cause a repaint of something
617
// we're already repainting, but without changing the
618
// semantics of damage we can't really get around this.
619
damage(r);
620                 }
621         g.setColor(component.getCaretColor());
622                 int paintWidth = getCaretWidth(r.height);
623                 r.x -= paintWidth >> 1;
624                 g.fillRect(r.x, r.y, paintWidth , r.height - 1);
625
626         // see if we should paint a flag to indicate the bias
627
// of the caret.
628
// PENDING(prinz) this should be done through
629
// protected methods so that alternative LAF
630
// will show bidi information.
631
Document JavaDoc doc = component.getDocument();
632         if (doc instanceof AbstractDocument JavaDoc) {
633             Element JavaDoc bidi = ((AbstractDocument JavaDoc)doc).getBidiRootElement();
634             if ((bidi != null) && (bidi.getElementCount() > 1)) {
635             // there are multiple directions present.
636
flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
637                         flagYPoints[0] = r.y;
638                         flagXPoints[1] = flagXPoints[0];
639                         flagYPoints[1] = flagYPoints[0] + 4;
640                         flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
641                         flagYPoints[2] = flagYPoints[0];
642                         g.fillPolygon(flagXPoints, flagYPoints, 3);
643             }
644         }
645         } catch (BadLocationException JavaDoc e) {
646         // can't render I guess
647
//System.err.println("Can't render cursor");
648
}
649     }
650     }
651     
652     /**
653      * Called when the UI is being installed into the
654      * interface of a JTextComponent. This can be used
655      * to gain access to the model that is being navigated
656      * by the implementation of this interface. Sets the dot
657      * and mark to 0, and establishes document, property change,
658      * focus, mouse, and mouse motion listeners.
659      *
660      * @param c the component
661      * @see Caret#install
662      */

663     public void install(JTextComponent JavaDoc c) {
664     component = c;
665     Document JavaDoc doc = c.getDocument();
666     dot = mark = 0;
667     dotLTR = markLTR = true;
668     dotBias = markBias = Position.Bias.Forward;
669     if (doc != null) {
670         doc.addDocumentListener(handler);
671     }
672     c.addPropertyChangeListener(handler);
673     c.addFocusListener(this);
674     c.addMouseListener(this);
675     c.addMouseMotionListener(this);
676
677     // if the component already has focus, it won't
678
// be notified.
679
if (component.hasFocus()) {
680         focusGained(null);
681     }
682
683         Number JavaDoc ratio = (Number JavaDoc) c.getClientProperty("caretAspectRatio");
684         if (ratio != null) {
685             aspectRatio = ratio.floatValue();
686         } else {
687             aspectRatio = -1;
688         }
689
690         Integer JavaDoc width = (Integer JavaDoc) c.getClientProperty("caretWidth");
691         if (width != null) {
692             caretWidth = width.intValue();
693         } else {
694             caretWidth = -1;
695         }
696     }
697
698     /**
699      * Called when the UI is being removed from the
700      * interface of a JTextComponent. This is used to
701      * unregister any listeners that were attached.
702      *
703      * @param c the component
704      * @see Caret#deinstall
705      */

706     public void deinstall(JTextComponent JavaDoc c) {
707     c.removeMouseListener(this);
708     c.removeMouseMotionListener(this);
709         c.removeFocusListener(this);
710     c.removePropertyChangeListener(handler);
711     Document JavaDoc doc = c.getDocument();
712     if (doc != null) {
713         doc.removeDocumentListener(handler);
714     }
715     synchronized(this) {
716         component = null;
717     }
718     if (flasher != null) {
719         flasher.stop();
720     }
721
722     
723     }
724
725     /**
726      * Adds a listener to track whenever the caret position has
727      * been changed.
728      *
729      * @param l the listener
730      * @see Caret#addChangeListener
731      */

732     public void addChangeListener(ChangeListener l) {
733     listenerList.add(ChangeListener.class, l);
734     }
735     
736     /**
737      * Removes a listener that was tracking caret position changes.
738      *
739      * @param l the listener
740      * @see Caret#removeChangeListener
741      */

742     public void removeChangeListener(ChangeListener l) {
743     listenerList.remove(ChangeListener.class, l);
744     }
745
746     /**
747      * Returns an array of all the change listeners
748      * registered on this caret.
749      *
750      * @return all of this caret's <code>ChangeListener</code>s
751      * or an empty
752      * array if no change listeners are currently registered
753      *
754      * @see #addChangeListener
755      * @see #removeChangeListener
756      *
757      * @since 1.4
758      */

759     public ChangeListener[] getChangeListeners() {
760         return (ChangeListener[])listenerList.getListeners(
761                 ChangeListener.class);
762     }
763
764     /**
765      * Notifies all listeners that have registered interest for
766      * notification on this event type. The event instance
767      * is lazily created using the parameters passed into
768      * the fire method. The listener list is processed last to first.
769      *
770      * @see EventListenerList
771      */

772     protected void fireStateChanged() {
773     // Guaranteed to return a non-null array
774
Object JavaDoc[] listeners = listenerList.getListenerList();
775     // Process the listeners last to first, notifying
776
// those that are interested in this event
777
for (int i = listeners.length-2; i>=0; i-=2) {
778         if (listeners[i]==ChangeListener.class) {
779         // Lazily create the event:
780
if (changeEvent == null)
781             changeEvent = new ChangeEvent(this);
782         ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
783         }
784     }
785     }
786
787     /**
788      * Returns an array of all the objects currently registered
789      * as <code><em>Foo</em>Listener</code>s
790      * upon this caret.
791      * <code><em>Foo</em>Listener</code>s are registered using the
792      * <code>add<em>Foo</em>Listener</code> method.
793      *
794      * <p>
795      *
796      * You can specify the <code>listenerType</code> argument
797      * with a class literal,
798      * such as
799      * <code><em>Foo</em>Listener.class</code>.
800      * For example, you can query a
801      * <code>DefaultCaret</code> <code>c</code>
802      * for its change listeners with the following code:
803      *
804      * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
805      *
806      * If no such listeners exist, this method returns an empty array.
807      *
808      * @param listenerType the type of listeners requested; this parameter
809      * should specify an interface that descends from
810      * <code>java.util.EventListener</code>
811      * @return an array of all objects registered as
812      * <code><em>Foo</em>Listener</code>s on this component,
813      * or an empty array if no such
814      * listeners have been added
815      * @exception ClassCastException if <code>listenerType</code>
816      * doesn't specify a class or interface that implements
817      * <code>java.util.EventListener</code>
818      *
819      * @see #getChangeListeners
820      *
821      * @since 1.3
822      */

823     public <T extends EventListener JavaDoc> T[] getListeners(Class JavaDoc<T> listenerType) {
824     return listenerList.getListeners(listenerType);
825     }
826
827     /**
828      * Changes the selection visibility.
829      *
830      * @param vis the new visibility
831      */

832     public void setSelectionVisible(boolean vis) {
833     if (vis != selectionVisible) {
834         selectionVisible = vis;
835         if (selectionVisible) {
836         // show
837
Highlighter JavaDoc h = component.getHighlighter();
838         if ((dot != mark) && (h != null) && (selectionTag == null)) {
839             int p0 = Math.min(dot, mark);
840             int p1 = Math.max(dot, mark);
841             Highlighter.HighlightPainter JavaDoc p = getSelectionPainter();
842             try {
843             selectionTag = h.addHighlight(p0, p1, p);
844             } catch (BadLocationException JavaDoc bl) {
845             selectionTag = null;
846             }
847         }
848         } else {
849         // hide
850
if (selectionTag != null) {
851             Highlighter JavaDoc h = component.getHighlighter();
852             h.removeHighlight(selectionTag);
853             selectionTag = null;
854         }
855         }
856     }
857     }
858
859     /**
860      * Checks whether the current selection is visible.
861      *
862      * @return true if the selection is visible
863      */

864     public boolean isSelectionVisible() {
865     return selectionVisible;
866     }
867
868     /**
869      * Determines if the caret is currently active.
870      * <p>
871      * This method returns whether or not the <code>Caret</code>
872      * is currently in a blinking state. It does not provide
873      * information as to whether it is currently blinked on or off.
874      * To determine if the caret is currently painted use the
875      * <code>isVisible</code> method.
876      *
877      * @return <code>true</code> if active else <code>false</code>
878      * @see #isVisible
879      *
880      * @since 1.5
881      */

882     public boolean isActive() {
883         return active;
884     }
885
886     /**
887      * Indicates whether or not the caret is currently visible. As the
888      * caret flashes on and off the return value of this will change
889      * between true, when the caret is painted, and false, when the
890      * caret is not painted. <code>isActive</code> indicates whether
891      * or not the caret is in a blinking state, such that it <b>can</b>
892      * be visible, and <code>isVisible</code> indicates whether or not
893      * the caret <b>is</b> actually visible.
894      * <p>
895      * Subclasses that wish to render a different flashing caret
896      * should override paint and only paint the caret if this method
897      * returns true.
898      *
899      * @return true if visible else false
900      * @see Caret#isVisible
901      * @see #isActive
902      */

903     public boolean isVisible() {
904         return visible;
905     }
906
907     /**
908      * Sets the caret visibility, and repaints the caret.
909      * It is important to understand the relationship between this method,
910      * <code>isVisible</code> and <code>isActive</code>.
911      * Calling this method with a value of <code>true</code> activates the
912      * caret blinking. Setting it to <code>false</code> turns it completely off.
913      * To determine whether the blinking is active, you should call
914      * <code>isActive</code>. In effect, <code>isActive</code> is an
915      * appropriate corresponding "getter" method for this one.
916      * <code>isVisible</code> can be used to fetch the current
917      * visibility status of the caret, meaning whether or not it is currently
918      * painted. This status will change as the caret blinks on and off.
919      * <p>
920      * Here's a list showing the potential return values of both
921      * <code>isActive</code> and <code>isVisible</code>
922      * after calling this method:
923      * <p>
924      * <b><code>setVisible(true)</code></b>:
925      * <ul>
926      * <li>isActive(): true</li>
927      * <li>isVisible(): true or false depending on whether
928      * or not the caret is blinked on or off</li>
929      * </ul>
930      * <p>
931      * <b><code>setVisible(false)</code></b>:
932      * <ul>
933      * <li>isActive(): false</li>
934      * <li>isVisible(): false</li>
935      * </ul>
936      *
937      * @param e the visibility specifier
938      * @see #isActive
939      * @see Caret#setVisible
940      */

941     public void setVisible(boolean e) {
942     // focus lost notification can come in later after the
943
// caret has been deinstalled, in which case the component
944
// will be null.
945
if (component != null) {
946             active = e;
947             TextUI mapper = component.getUI();
948         if (visible != e) {
949                 visible = e;
950         // repaint the caret
951
try {
952             Rectangle loc = mapper.modelToView(component, dot,dotBias);
953             damage(loc);
954         } catch (BadLocationException JavaDoc badloc) {
955             // hmm... not legally positioned
956
}
957         }
958     }
959     if (flasher != null) {
960         if (visible) {
961         flasher.start();
962         } else {
963         flasher.stop();
964         }
965     }
966     }
967
968     /**
969      * Sets the caret blink rate.
970      *
971      * @param rate the rate in milliseconds, 0 to stop blinking
972      * @see Caret#setBlinkRate
973      */

974     public void setBlinkRate(int rate) {
975     if (rate != 0) {
976         if (flasher == null) {
977         flasher = new Timer(rate, handler);
978         }
979         flasher.setDelay(rate);
980     } else {
981         if (flasher != null) {
982         flasher.stop();
983         flasher.removeActionListener(handler);
984         flasher = null;
985         }
986     }
987     }
988
989     /**
990      * Gets the caret blink rate.
991      *
992      * @return the delay in milliseconds. If this is
993      * zero the caret will not blink.
994      * @see Caret#getBlinkRate
995      */

996     public int getBlinkRate() {
997     return (flasher == null) ? 0 : flasher.getDelay();
998     }
999
1000    /**
1001     * Fetches the current position of the caret.
1002     *
1003     * @return the position >= 0
1004     * @see Caret#getDot
1005     */

1006    public int getDot() {
1007        return dot;
1008    }
1009
1010    /**
1011     * Fetches the current position of the mark. If there is a selection,
1012     * the dot and mark will not be the same.
1013     *
1014     * @return the position >= 0
1015     * @see Caret#getMark
1016     */

1017    public int getMark() {
1018        return mark;
1019    }
1020
1021    /**
1022     * Sets the caret position and mark to some position. This
1023     * implicitly sets the selection range to zero.
1024     *
1025     * @param dot the position >= 0
1026     * @see Caret#setDot
1027     */

1028    public void setDot(int dot) {
1029    setDot(dot, Position.Bias.Forward);
1030    }
1031
1032    /**
1033     * Moves the caret position to some other position.
1034     *
1035     * @param dot the position >= 0
1036     * @see Caret#moveDot
1037     */

1038    public void moveDot(int dot) {
1039    moveDot(dot, Position.Bias.Forward);
1040    }
1041
1042    // ---- Bidi methods (we could put these in a subclass)
1043

1044    void moveDot(int dot, Position.Bias JavaDoc dotBias) {
1045    if (! component.isEnabled()) {
1046        // don't allow selection on disabled components.
1047
setDot(dot, dotBias);
1048        return;
1049    }
1050    if (dot != this.dot) {
1051            NavigationFilter JavaDoc filter = component.getNavigationFilter();
1052
1053            if (filter != null) {
1054                filter.moveDot(getFilterBypass(), dot, dotBias);
1055            }
1056            else {
1057                handleMoveDot(dot, dotBias);
1058            }
1059        }
1060    }
1061
1062    void handleMoveDot(int dot, Position.Bias JavaDoc dotBias) {
1063        changeCaretPosition(dot, dotBias);
1064        
1065        if (selectionVisible) {
1066            Highlighter JavaDoc h = component.getHighlighter();
1067            if (h != null) {
1068                int p0 = Math.min(dot, mark);
1069                int p1 = Math.max(dot, mark);
1070                
1071                // if p0 == p1 then there should be no highlight, remove it if necessary
1072
if (p0 == p1) {
1073                    if (selectionTag != null) {
1074                        h.removeHighlight(selectionTag);
1075                        selectionTag = null;
1076                    }
1077                // otherwise, change or add the highlight
1078
} else {
1079                    try {
1080                        if (selectionTag != null) {
1081                            h.changeHighlight(selectionTag, p0, p1);
1082                        } else {
1083                            Highlighter.HighlightPainter JavaDoc p = getSelectionPainter();
1084                            selectionTag = h.addHighlight(p0, p1, p);
1085                        }
1086                    } catch (BadLocationException JavaDoc e) {
1087                        throw new StateInvariantError JavaDoc("Bad caret position");
1088                    }
1089                }
1090            }
1091        }
1092    }
1093
1094    void setDot(int dot, Position.Bias JavaDoc dotBias) {
1095        NavigationFilter JavaDoc filter = component.getNavigationFilter();
1096
1097        if (filter != null) {
1098            filter.setDot(getFilterBypass(), dot, dotBias);
1099        }
1100        else {
1101            handleSetDot(dot, dotBias);
1102        }
1103    }
1104
1105    void handleSetDot(int dot, Position.Bias JavaDoc dotBias) {
1106    // move dot, if it changed
1107
Document JavaDoc doc = component.getDocument();
1108    if (doc != null) {
1109        dot = Math.min(dot, doc.getLength());
1110    }
1111    dot = Math.max(dot, 0);
1112
1113        // The position (0,Backward) is out of range so disallow it.
1114
if( dot == 0 )
1115            dotBias = Position.Bias.Forward;
1116        
1117    mark = dot;
1118    if (this.dot != dot || this.dotBias != dotBias ||
1119        selectionTag != null || forceCaretPositionChange) {
1120        changeCaretPosition(dot, dotBias);
1121    }
1122    this.markBias = this.dotBias;
1123    this.markLTR = dotLTR;
1124    Highlighter JavaDoc h = component.getHighlighter();
1125    if ((h != null) && (selectionTag != null)) {
1126        h.removeHighlight(selectionTag);
1127        selectionTag = null;
1128    }
1129    }
1130
1131    Position.Bias JavaDoc getDotBias() {
1132    return dotBias;
1133    }
1134
1135    Position.Bias JavaDoc getMarkBias() {
1136    return markBias;
1137    }
1138
1139    boolean isDotLeftToRight() {
1140    return dotLTR;
1141    }
1142
1143    boolean isMarkLeftToRight() {
1144    return markLTR;
1145    }
1146
1147    boolean isPositionLTR(int position, Position.Bias JavaDoc bias) {
1148    Document JavaDoc doc = component.getDocument();
1149    if(doc instanceof AbstractDocument JavaDoc ) {
1150        if(bias == Position.Bias.Backward && --position < 0)
1151        position = 0;
1152        return ((AbstractDocument JavaDoc)doc).isLeftToRight(position, position);
1153    }
1154    return true;
1155    }
1156
1157    Position.Bias JavaDoc guessBiasForOffset(int offset, Position.Bias JavaDoc lastBias,
1158                     boolean lastLTR) {
1159    // There is an abiguous case here. That if your model looks like:
1160
// abAB with the cursor at abB]A (visual representation of
1161
// 3 forward) deleting could either become abB] or
1162
// ab[B. I'ld actually prefer abB]. But, if I implement that
1163
// a delete at abBA] would result in aBA] vs a[BA which I
1164
// think is totally wrong. To get this right we need to know what
1165
// was deleted. And we could get this from the bidi structure
1166
// in the change event. So:
1167
// PENDING: base this off what was deleted.
1168
if(lastLTR != isPositionLTR(offset, lastBias)) {
1169        lastBias = Position.Bias.Backward;
1170    }
1171    else if(lastBias != Position.Bias.Backward &&
1172        lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
1173        lastBias = Position.Bias.Backward;
1174    }
1175    if (lastBias == Position.Bias.Backward && offset > 0) {
1176        try {
1177        Segment JavaDoc s = new Segment JavaDoc();
1178        component.getDocument().getText(offset - 1, 1, s);
1179        if (s.count > 0 && s.array[s.offset] == '\n') {
1180            lastBias = Position.Bias.Forward;
1181        }
1182        }
1183        catch (BadLocationException JavaDoc ble) {}
1184    }
1185    return lastBias;
1186    }
1187
1188    // ---- local methods --------------------------------------------
1189

1190    /**
1191     * Sets the caret position (dot) to a new location. This
1192     * causes the old and new location to be repainted. It
1193     * also makes sure that the caret is within the visible
1194     * region of the view, if the view is scrollable.
1195     */

1196    void changeCaretPosition(int dot, Position.Bias JavaDoc dotBias) {
1197    // repaint the old position and set the new value of
1198
// the dot.
1199
repaint();
1200
1201
1202        // Make sure the caret is visible if this window has the focus.
1203
if (flasher != null && flasher.isRunning()) {
1204            visible = true;
1205            flasher.restart();
1206        }
1207
1208    // notify listeners at the caret moved
1209
this.dot = dot;
1210        this.dotBias = dotBias;
1211        dotLTR = isPositionLTR(dot, dotBias);
1212        fireStateChanged();
1213
1214        updateSystemSelection();
1215
1216    setMagicCaretPosition(null);
1217
1218    // We try to repaint the caret later, since things
1219
// may be unstable at the time this is called
1220
// (i.e. we don't want to depend upon notification
1221
// order or the fact that this might happen on
1222
// an unsafe thread).
1223
Runnable JavaDoc callRepaintNewCaret = new Runnable JavaDoc() {
1224            public void run() {
1225        repaintNewCaret();
1226        }
1227    };
1228    SwingUtilities.invokeLater(callRepaintNewCaret);
1229    }
1230
1231    /**
1232     * Repaints the new caret position, with the
1233     * assumption that this is happening on the
1234     * event thread so that calling <code>modelToView</code>
1235     * is safe.
1236     */

1237    void repaintNewCaret() {
1238    if (component != null) {
1239        TextUI mapper = component.getUI();
1240        Document JavaDoc doc = component.getDocument();
1241        if ((mapper != null) && (doc != null)) {
1242        // determine the new location and scroll if
1243
// not visible.
1244
Rectangle newLoc;
1245        try {
1246            newLoc = mapper.modelToView(component, this.dot, this.dotBias);
1247        } catch (BadLocationException JavaDoc e) {
1248            newLoc = null;
1249        }
1250        if (newLoc != null) {
1251            adjustVisibility(newLoc);
1252            // If there is no magic caret position, make one
1253
if (getMagicCaretPosition() == null) {
1254            setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
1255            }
1256        }
1257        
1258        // repaint the new position
1259
damage(newLoc);
1260        }
1261    }
1262    }
1263    
1264    private void updateSystemSelection() {
1265        if ( ! SwingUtilities2.canCurrentEventAccessSystemClipboard() ) {
1266            return;
1267        }
1268        if (this.dot != this.mark && component != null) {
1269            Clipboard clip = getSystemSelection();
1270            if (clip != null) {
1271                String JavaDoc selectedText = null;
1272                if (component instanceof JPasswordField
1273                    && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
1274                    Boolean.TRUE) {
1275                    //fix for 4793761
1276
StringBuffer JavaDoc txt = null;
1277                    char echoChar = ((JPasswordField)component).getEchoChar();
1278                    int p0 = Math.min(getDot(), getMark());
1279                    int p1 = Math.max(getDot(), getMark());
1280                    for (int i = p0; i < p1; i++) {
1281                        if (txt == null) {
1282                            txt = new StringBuffer JavaDoc();
1283                        }
1284                        txt.append(echoChar);
1285                    }
1286                    selectedText = (txt != null) ? txt.toString() : null;
1287                } else {
1288                    selectedText = component.getSelectedText();
1289                }
1290                try {
1291                    clip.setContents(
1292                        new StringSelection(selectedText), getClipboardOwner());
1293
1294                    ownsSelection = true;
1295                } catch (IllegalStateException JavaDoc ise) {
1296                    // clipboard was unavailable
1297
// no need to provide error feedback to user since updating
1298
// the system selection is not a user invoked action
1299
}
1300            }
1301        }
1302    }
1303
1304    private Clipboard getSystemSelection() {
1305        try {
1306            return component.getToolkit().getSystemSelection();
1307        } catch (HeadlessException he) {
1308            // do nothing... there is no system clipboard
1309
} catch (SecurityException JavaDoc se) {
1310            // do nothing... there is no allowed system clipboard
1311
}
1312        return null;
1313    }
1314
1315    private ClipboardOwner getClipboardOwner() {
1316        return handler;
1317    }
1318
1319    /**
1320     * This is invoked after the document changes to verify the current
1321     * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
1322     * changed where to position the dot, that resulted in the current location
1323     * being bogus.
1324     */

1325    private void ensureValidPosition() {
1326        int length = component.getDocument().getLength();
1327        if (dot > length || mark > length) {
1328            // Current location is bogus and filter likely vetoed the
1329
// change, force the reset without giving the filter a
1330
// chance at changing it.
1331
handleSetDot(length, Position.Bias.Forward);
1332        }
1333    }
1334
1335
1336    /**
1337     * Saves the current caret position. This is used when
1338     * caret up/down actions occur, moving between lines
1339     * that have uneven end positions.
1340     *
1341     * @param p the position
1342     * @see #getMagicCaretPosition
1343     */

1344    public void setMagicCaretPosition(Point p) {
1345    magicCaretPosition = p;
1346    }
1347    
1348    /**
1349     * Gets the saved caret position.
1350     *
1351     * @return the position
1352     * see #setMagicCaretPosition
1353     */

1354    public Point getMagicCaretPosition() {
1355    return magicCaretPosition;
1356    }
1357
1358    /**
1359     * Compares this object to the specified object.
1360     * The superclass behavior of comparing rectangles
1361     * is not desired, so this is changed to the Object
1362     * behavior.
1363     *
1364     * @param obj the object to compare this font with
1365     * @return <code>true</code> if the objects are equal;
1366     * <code>false</code> otherwise
1367     */

1368    public boolean equals(Object JavaDoc obj) {
1369    return (this == obj);
1370    }
1371
1372    public String JavaDoc toString() {
1373        String JavaDoc s = "Dot=(" + dot + ", " + dotBias + ")";
1374        s += " Mark=(" + mark + ", " + markBias + ")";
1375        return s;
1376    }
1377
1378    private NavigationFilter.FilterBypass JavaDoc getFilterBypass() {
1379        if (filterBypass == null) {
1380            filterBypass = new DefaultFilterBypass();
1381        }
1382        return filterBypass;
1383    }
1384
1385    // Rectangle.contains returns false if passed a rect with a w or h == 0,
1386
// this won't (assuming X,Y are contained with this rectangle).
1387
private boolean _contains(int X, int Y, int W, int H) {
1388        int w = this.width;
1389        int h = this.height;
1390        if ((w | h | W | H) < 0) {
1391            // At least one of the dimensions is negative...
1392
return false;
1393        }
1394        // Note: if any dimension is zero, tests below must return false...
1395
int x = this.x;
1396        int y = this.y;
1397        if (X < x || Y < y) {
1398            return false;
1399        }
1400        if (W > 0) {
1401            w += x;
1402            W += X;
1403            if (W <= X) {
1404                // X+W overflowed or W was zero, return false if...
1405
// either original w or W was zero or
1406
// x+w did not overflow or
1407
// the overflowed x+w is smaller than the overflowed X+W
1408
if (w >= x || W > w) return false;
1409            } else {
1410                // X+W did not overflow and W was not zero, return false if...
1411
// original w was zero or
1412
// x+w did not overflow and x+w is smaller than X+W
1413
if (w >= x && W > w) return false;
1414            }
1415        }
1416        else if ((x + w) < X) {
1417            return false;
1418        }
1419        if (H > 0) {
1420            h += y;
1421            H += Y;
1422            if (H <= Y) {
1423                if (h >= y || H > h) return false;
1424            } else {
1425                if (h >= y && H > h) return false;
1426            }
1427        }
1428        else if ((y + h) < Y) {
1429            return false;
1430        }
1431        return true;
1432    }
1433
1434    int getCaretWidth(int height) {
1435        if (aspectRatio > -1) {
1436            return (int) (aspectRatio * height) + 1;
1437        }
1438
1439        if (caretWidth > -1) {
1440            return caretWidth;
1441        }
1442
1443        return 1;
1444    }
1445
1446    // --- serialization ---------------------------------------------
1447

1448    private void readObject(ObjectInputStream s)
1449      throws ClassNotFoundException JavaDoc, IOException
1450    {
1451    s.defaultReadObject();
1452    handler = new Handler();
1453    if (!s.readBoolean()) {
1454        dotBias = Position.Bias.Forward;
1455    }
1456    else {
1457        dotBias = Position.Bias.Backward;
1458    }
1459    if (!s.readBoolean()) {
1460        markBias = Position.Bias.Forward;
1461    }
1462    else {
1463        markBias = Position.Bias.Backward;
1464    }
1465    }
1466
1467    private void writeObject(ObjectOutputStream s) throws IOException {
1468    s.defaultWriteObject();
1469    s.writeBoolean((dotBias == Position.Bias.Backward));
1470    s.writeBoolean((markBias == Position.Bias.Backward));
1471    }
1472    
1473    // ---- member variables ------------------------------------------
1474

1475    /**
1476     * The event listener list.
1477     */

1478    protected EventListenerList listenerList = new EventListenerList();
1479
1480    /**
1481     * The change event for the model.
1482     * Only one ChangeEvent is needed per model instance since the
1483     * event's only (read-only) state is the source property. The source
1484     * of events generated here is always "this".
1485     */

1486    protected transient ChangeEvent changeEvent = null;
1487
1488    // package-private to avoid inner classes private member
1489
// access bug
1490
JTextComponent JavaDoc component;
1491
1492    int updatePolicy = UPDATE_WHEN_ON_EDT;
1493    boolean visible;
1494    boolean active;
1495    int dot;
1496    int mark;
1497    Object JavaDoc selectionTag;
1498    boolean selectionVisible;
1499    Timer flasher;
1500    Point magicCaretPosition;
1501    transient Position.Bias JavaDoc dotBias;
1502    transient Position.Bias JavaDoc markBias;
1503    boolean dotLTR;
1504    boolean markLTR;
1505    transient Handler handler = new Handler();
1506    transient private int[] flagXPoints = new int[3];
1507    transient private int[] flagYPoints = new int[3];
1508    private transient NavigationFilter.FilterBypass JavaDoc filterBypass;
1509    static private transient Action selectWord = null;
1510    static private transient Action selectLine = null;
1511    /**
1512     * This is used to indicate if the caret currently owns the selection.
1513     * This is always false if the system does not support the system
1514     * clipboard.
1515     */

1516    private boolean ownsSelection;
1517
1518    /**
1519     * If this is true, the location of the dot is updated regardless of
1520     * the current location. This is set in the DocumentListener
1521     * such that even if the model location of dot hasn't changed (perhaps do
1522     * to a forward delete) the visual location is updated.
1523     */

1524    private boolean forceCaretPositionChange;
1525    
1526    /**
1527     * Whether or not mouseReleased should adjust the caret and focus.
1528     * This flag is set by mousePressed if it wanted to adjust the caret
1529     * and focus but couldn't because of a possible DnD operation.
1530     */

1531    private transient boolean shouldHandleRelease;
1532
1533
1534    /**
1535     * holds last MouseEvent which caused the word selection
1536     */

1537    private transient MouseEvent selectedWordEvent = null;
1538
1539    /**
1540     * The width of the caret in pixels.
1541     */

1542    private int caretWidth = -1;
1543    private float aspectRatio = -1;
1544
1545    class SafeScroller implements Runnable JavaDoc {
1546    
1547    SafeScroller(Rectangle r) {
1548        this.r = r;
1549    }
1550
1551    public void run() {
1552        if (component != null) {
1553        component.scrollRectToVisible(r);
1554        }
1555    }
1556
1557    Rectangle r;
1558    }
1559
1560
1561    class Handler implements PropertyChangeListener, DocumentListener, ActionListener JavaDoc, ClipboardOwner {
1562
1563    // --- ActionListener methods ----------------------------------
1564

1565    /**
1566     * Invoked when the blink timer fires. This is called
1567     * asynchronously. The simply changes the visibility
1568     * and repaints the rectangle that last bounded the caret.
1569     *
1570     * @param e the action event
1571     */

1572        public void actionPerformed(ActionEvent JavaDoc e) {
1573        if (width == 0 || height == 0) {
1574                // setVisible(true) will cause a scroll, only do this if the
1575
// new location is really valid.
1576
if (component != null) {
1577                    TextUI mapper = component.getUI();
1578                    try {
1579                        Rectangle r = mapper.modelToView(component, dot,
1580                                                         dotBias);
1581                        if (r != null && r.width != 0 && r.height != 0) {
1582                            damage(r);
1583                        }
1584                    } catch (BadLocationException JavaDoc ble) {
1585                    }
1586                }
1587        }
1588            visible = !visible;
1589            repaint();
1590    }
1591
1592    // --- DocumentListener methods --------------------------------
1593

1594    /**
1595     * Updates the dot and mark if they were changed by
1596     * the insertion.
1597     *
1598     * @param e the document event
1599     * @see DocumentListener#insertUpdate
1600     */

1601        public void insertUpdate(DocumentEvent e) {
1602            if (getUpdatePolicy() == NEVER_UPDATE ||
1603                    (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1604                    !SwingUtilities.isEventDispatchThread())) {
1605
1606                if ((e.getOffset() <= dot || e.getOffset() <= mark)
1607                        && selectionTag != null) {
1608                    try {
1609                        component.getHighlighter().changeHighlight(selectionTag,
1610                                Math.min(dot, mark), Math.max(dot, mark));
1611                    } catch (BadLocationException JavaDoc e1) {
1612                        e1.printStackTrace();
1613                    }
1614                }
1615                return;
1616            }
1617            int adjust = 0;
1618            int offset = e.getOffset();
1619            int length = e.getLength();
1620            int newDot = dot;
1621            short changed = 0;
1622
1623            if (e instanceof AbstractDocument.UndoRedoDocumentEvent JavaDoc) {
1624                setDot(offset + length);
1625                return;
1626            }
1627            if (newDot >= offset) {
1628                newDot += length;
1629                changed |= 1;
1630            }
1631            int newMark = mark;
1632            if (newMark >= offset) {
1633                newMark += length;
1634                changed |= 2;
1635            }
1636
1637            if (changed != 0) {
1638                Position.Bias JavaDoc dotBias = DefaultCaret.this.dotBias;
1639                if (dot == offset) {
1640                    Document JavaDoc doc = component.getDocument();
1641                    boolean isNewline;
1642                    try {
1643                        Segment JavaDoc s = new Segment JavaDoc();
1644                        doc.getText(newDot - 1, 1, s);
1645                        isNewline = (s.count > 0 &&
1646                                s.array[s.offset] == '\n');
1647                    } catch (BadLocationException JavaDoc ble) {
1648                        isNewline = false;
1649                    }
1650                    if (isNewline) {
1651                        dotBias = Position.Bias.Forward;
1652                    } else {
1653                        dotBias = Position.Bias.Backward;
1654                    }
1655                }
1656                if (newMark == newDot) {
1657                    setDot(newDot, dotBias);
1658                    ensureValidPosition();
1659                }
1660                else {
1661                    setDot(newMark, markBias);
1662                    if (getDot() == newMark) {
1663                        // Due this test in case the filter vetoed the
1664
// change in which case this probably won't be
1665
// valid either.
1666
moveDot(newDot, dotBias);
1667                    }
1668                    ensureValidPosition();
1669                }
1670            }
1671    }
1672
1673    /**
1674     * Updates the dot and mark if they were changed
1675     * by the removal.
1676     *
1677     * @param e the document event
1678     * @see DocumentListener#removeUpdate
1679     */

1680        public void removeUpdate(DocumentEvent e) {
1681            if (getUpdatePolicy() == NEVER_UPDATE ||
1682                    (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1683                    !SwingUtilities.isEventDispatchThread())) {
1684
1685                int length = component.getDocument().getLength();
1686                dot = Math.min(dot, length);
1687                mark = Math.min(mark, length);
1688                if ((e.getOffset() < dot || e.getOffset() < mark)
1689                        && selectionTag != null) {
1690                    try {
1691                        component.getHighlighter().changeHighlight(selectionTag,
1692                                Math.min(dot, mark), Math.max(dot, mark));
1693                    } catch (BadLocationException JavaDoc e1) {
1694                        e1.printStackTrace();
1695                    }
1696                }
1697                return;
1698            }
1699            int offs0 = e.getOffset();
1700            int offs1 = offs0 + e.getLength();
1701            int adjust = 0;
1702            int newDot = dot;
1703            boolean adjustDotBias = false;
1704            int newMark = mark;
1705            boolean adjustMarkBias = false;
1706
1707            if(e instanceof AbstractDocument.UndoRedoDocumentEvent JavaDoc) {
1708                setDot(offs0);
1709                return;
1710            }
1711            if (newDot >= offs1) {
1712                newDot -= (offs1 - offs0);
1713                if(newDot == offs1) {
1714                    adjustDotBias = true;
1715                }
1716            } else if (newDot >= offs0) {
1717                newDot = offs0;
1718                adjustDotBias = true;
1719            }
1720            if (newMark >= offs1) {
1721                newMark -= (offs1 - offs0);
1722                if(newMark == offs1) {
1723                    adjustMarkBias = true;
1724                }
1725            } else if (newMark >= offs0) {
1726                newMark = offs0;
1727                adjustMarkBias = true;
1728            }
1729            if (newMark == newDot) {
1730                forceCaretPositionChange = true;
1731                try {
1732                    setDot(newDot, guessBiasForOffset(newDot, dotBias,
1733                            dotLTR));
1734                } finally {
1735                    forceCaretPositionChange = false;
1736                }
1737                ensureValidPosition();
1738            } else {
1739                Position.Bias JavaDoc dotBias = DefaultCaret.this.dotBias;
1740                Position.Bias JavaDoc markBias = DefaultCaret.this.markBias;
1741                if(adjustDotBias) {
1742                    dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
1743                }
1744                if(adjustMarkBias) {
1745                    markBias = guessBiasForOffset(mark, markBias, markLTR);
1746                }
1747                setDot(newMark, markBias);
1748                if (getDot() == newMark) {
1749                    // Due this test in case the filter vetoed the change
1750
// in which case this probably won't be valid either.
1751
moveDot(newDot, dotBias);
1752                }
1753                ensureValidPosition();
1754            }
1755    }
1756
1757    /**
1758     * Gives notification that an attribute or set of attributes changed.
1759     *
1760     * @param e the document event
1761     * @see DocumentListener#changedUpdate
1762     */

1763        public void changedUpdate(DocumentEvent e) {
1764            if (getUpdatePolicy() == NEVER_UPDATE ||
1765                    (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1766                    !SwingUtilities.isEventDispatchThread())) {
1767                return;
1768            }
1769            if(e instanceof AbstractDocument.UndoRedoDocumentEvent JavaDoc) {
1770                setDot(e.getOffset() + e.getLength());
1771            }
1772    }
1773
1774    // --- PropertyChangeListener methods -----------------------
1775

1776    /**
1777     * This method gets called when a bound property is changed.
1778     * We are looking for document changes on the editor.
1779     */

1780    public void propertyChange(PropertyChangeEvent evt) {
1781        Object JavaDoc oldValue = evt.getOldValue();
1782        Object JavaDoc newValue = evt.getNewValue();
1783        if ((oldValue instanceof Document JavaDoc) || (newValue instanceof Document JavaDoc)) {
1784                setDot(0);
1785        if (oldValue != null) {
1786            ((Document JavaDoc)oldValue).removeDocumentListener(this);
1787        }
1788        if (newValue != null) {
1789            ((Document JavaDoc)newValue).addDocumentListener(this);
1790        }
1791            } else if("enabled".equals(evt.getPropertyName())) {
1792                Boolean JavaDoc enabled = (Boolean JavaDoc) evt.getNewValue();
1793                if(component.isFocusOwner()) {
1794                    if(enabled == Boolean.TRUE) {
1795                        if(component.isEditable()) {
1796                            setVisible(true);
1797                        }
1798                        setSelectionVisible(true);
1799                    } else {
1800                        setVisible(false);
1801                        setSelectionVisible(false);
1802                    }
1803                }
1804        } else if("caretWidth".equals(evt.getPropertyName())) {
1805                Integer JavaDoc newWidth = (Integer JavaDoc) evt.getNewValue();
1806                if (newWidth != null) {
1807                    caretWidth = newWidth.intValue();
1808                } else {
1809                    caretWidth = -1;
1810                }
1811                repaint();
1812            } else if("caretAspectRatio".equals(evt.getPropertyName())) {
1813                Number JavaDoc newRatio = (Number JavaDoc) evt.getNewValue();
1814                if (newRatio != null) {
1815                    aspectRatio = newRatio.floatValue();
1816                } else {
1817                    aspectRatio = -1;
1818                }
1819                repaint();
1820            }
1821    }
1822
1823
1824        //
1825
// ClipboardOwner
1826
//
1827
/**
1828         * Toggles the visibility of the selection when ownership is lost.
1829         */

1830        public void lostOwnership(Clipboard clipboard,
1831                                      Transferable contents) {
1832            if (ownsSelection) {
1833                ownsSelection = false;
1834                if (component != null && !component.hasFocus()) {
1835                    setSelectionVisible(false);
1836                }
1837            }
1838        }
1839    }
1840
1841
1842    private class DefaultFilterBypass extends NavigationFilter.FilterBypass JavaDoc {
1843        public Caret JavaDoc getCaret() {
1844            return DefaultCaret.this;
1845        }
1846
1847        public void setDot(int dot, Position.Bias JavaDoc bias) {
1848            handleSetDot(dot, bias);
1849        }
1850
1851        public void moveDot(int dot, Position.Bias JavaDoc bias) {
1852            handleMoveDot(dot, bias);
1853        }
1854    }
1855}
1856
1857
Popular Tags