1 /*
2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package javax.swing.text;
26 
27 import java.awt.*;
28 import java.awt.event.*;
29 import java.awt.datatransfer.*;
30 import java.beans.*;
31 import java.awt.event.ActionEvent;
32 import java.awt.event.ActionListener;
33 import java.io.*;
34 import javax.swing.*;
35 import javax.swing.event.*;
36 import javax.swing.plaf.*;
37 import java.util.EventListener;
38 import sun.swing.SwingUtilities2;
39 
40 /**
41  * A default implementation of Caret.  The caret is rendered as
42  * a vertical line in the color specified by the CaretColor property
43  * of the associated JTextComponent.  It can blink at the rate specified
44  * by the BlinkRate property.
45  * <p>
46  * This implementation expects two sources of asynchronous notification.
47  * The timer thread fires asynchronously, and causes the caret to simply
48  * repaint the most recent bounding box.  The caret also tracks change
49  * as the document is modified.  Typically this will happen on the
50  * event dispatch thread as a result of some mouse or keyboard event.
51  * The caret behavior on both synchronous and asynchronous documents updates
52  * is controlled by <code>UpdatePolicy</code> property. The repaint of the
53  * new caret location will occur on the event thread in any case, as calls to
54  * <code>modelToView</code> are only safe on the event thread.
55  * <p>
56  * The caret acts as a mouse and focus listener on the text component
57  * it has been installed in, and defines the caret semantics based upon
58  * those events.  The listener methods can be reimplemented to change the
59  * semantics.
60  * By default, the first mouse button will be used to set focus and caret
61  * position.  Dragging the mouse pointer with the first mouse button will
62  * sweep out a selection that is contiguous in the model.  If the associated
63  * text component is editable, the caret will become visible when focus
64  * is gained, and invisible when focus is lost.
65  * <p>
66  * The Highlighter bound to the associated text component is used to
67  * render the selection by default.
68  * Selection appearance can be customized by supplying a
69  * painter to use for the highlights.  By default a painter is used that
70  * will render a solid color as specified in the associated text component
71  * in the <code>SelectionColor</code> property.  This can easily be changed
72  * by reimplementing the
73  * {@link #getSelectionPainter getSelectionPainter}
74  * method.
75  * <p>
76  * A customized caret appearance can be achieved by reimplementing
77  * the paint method.  If the paint method is changed, the damage method
78  * should also be reimplemented to cause a repaint for the area needed
79  * to render the caret.  The caret extends the Rectangle class which
80  * is used to hold the bounding box for where the caret was last rendered.
81  * This enables the caret to repaint in a thread-safe manner when the
82  * caret moves without making a call to modelToView which is unstable
83  * between model updates and view repair (i.e. the order of delivery
84  * to DocumentListeners is not guaranteed).
85  * <p>
86  * The magic caret position is set to null when the caret position changes.
87  * A timer is used to determine the new location (after the caret change).
88  * When the timer fires, if the magic caret position is still null it is
89  * reset to the current caret position. Any actions that change
90  * the caret position and want the magic caret position to remain the
91  * same, must remember the magic caret position, change the cursor, and
92  * then set the magic caret position to its original value. This has the
93  * benefit that only actions that want the magic caret position to persist
94  * (such as open/down) need to know about it.
95  * <p>
96  * <strong>Warning:</strong>
97  * Serialized objects of this class will not be compatible with
98  * future Swing releases. The current serialization support is
99  * appropriate for short term storage or RMI between applications running
100  * the same version of Swing.  As of 1.4, support for long term storage
101  * of all JavaBeans&trade;
102  * has been added to the <code>java.beans</code> package.
103  * Please see {@link java.beans.XMLEncoder}.
104  *
105  * @author  Timothy Prinzing
106  * @see     Caret
107  */
108 @SuppressWarnings("serial") // Same-version serialization only
109 public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener {
110 
111     /**
112      * Indicates that the caret position is to be updated only when
113      * document changes are performed on the Event Dispatching Thread.
114      * @see #setUpdatePolicy
115      * @see #getUpdatePolicy
116      * @since 1.5
117      */
118     public static final int UPDATE_WHEN_ON_EDT = 0;
119 
120     /**
121      * Indicates that the caret should remain at the same
122      * absolute position in the document regardless of any document
123      * updates, except when the document length becomes less than
124      * the current caret position due to removal. In that case the caret
125      * position is adjusted to the end of the document.
126      *
127      * @see #setUpdatePolicy
128      * @see #getUpdatePolicy
129      * @since 1.5
130      */
131     public static final int NEVER_UPDATE = 1;
132 
133     /**
134      * Indicates that the caret position is to be <b>always</b>
135      * updated accordingly to the document changes regardless whether
136      * the document updates are performed on the Event Dispatching Thread
137      * or not.
138      *
139      * @see #setUpdatePolicy
140      * @see #getUpdatePolicy
141      * @since 1.5
142      */
143     public static final int ALWAYS_UPDATE = 2;
144 
145     /**
146      * Constructs a default caret.
147      */
DefaultCaret()148     public DefaultCaret() {
149     }
150 
151     /**
152      * Sets the caret movement policy on the document updates. Normally
153      * the caret updates its absolute position within the document on
154      * insertions occurred before or at the caret position and
155      * on removals before the caret position. 'Absolute position'
156      * means here the position relative to the start of the document.
157      * For example if
158      * a character is typed within editable text component it is inserted
159      * at the caret position and the caret moves to the next absolute
160      * position within the document due to insertion and if
161      * <code>BACKSPACE</code> is typed then caret decreases its absolute
162      * position due to removal of a character before it. Sometimes
163      * it may be useful to turn off the caret position updates so that
164      * the caret stays at the same absolute position within the
165      * document position regardless of any document updates.
166      * <p>
167      * The following update policies are allowed:
168      * <ul>
169      *   <li><code>NEVER_UPDATE</code>: the caret stays at the same
170      *       absolute position in the document regardless of any document
171      *       updates, except when document length becomes less than
172      *       the current caret position due to removal. In that case caret
173      *       position is adjusted to the end of the document.
174      *       The caret doesn't try to keep itself visible by scrolling
175      *       the associated view when using this policy. </li>
176      *   <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
177      *       changes. For regular changes it increases its position
178      *       if an insertion occurs before or at its current position,
179      *       and decreases position if a removal occurs before
180      *       its current position. For undo/redo updates it is always
181      *       moved to the position where update occurred. The caret
182      *       also tries to keep itself visible by calling
183      *       <code>adjustVisibility</code> method.</li>
184      *   <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
185      *       if the document updates are performed on the Event Dispatching Thread
186      *       and like <code>NEVER_UPDATE</code> if updates are performed on
187      *       other thread. </li>
188      * </ul> <p>
189      * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
190      *
191      * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
192      * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
193      * @throws IllegalArgumentException if invalid value is passed
194      *
195      * @see #getUpdatePolicy
196      * @see #adjustVisibility
197      * @see #UPDATE_WHEN_ON_EDT
198      * @see #NEVER_UPDATE
199      * @see #ALWAYS_UPDATE
200      *
201      * @since 1.5
202      */
setUpdatePolicy(int policy)203     public void setUpdatePolicy(int policy) {
204         updatePolicy = policy;
205     }
206 
207     /**
208      * Gets the caret movement policy on document updates.
209      *
210      * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
211      * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
212      *
213      * @see #setUpdatePolicy
214      * @see #UPDATE_WHEN_ON_EDT
215      * @see #NEVER_UPDATE
216      * @see #ALWAYS_UPDATE
217      *
218      * @since 1.5
219      */
getUpdatePolicy()220     public int getUpdatePolicy() {
221         return updatePolicy;
222     }
223 
224     /**
225      * Gets the text editor component that this caret is
226      * is bound to.
227      *
228      * @return the component
229      */
getComponent()230     protected final JTextComponent getComponent() {
231         return component;
232     }
233 
234     /**
235      * Cause the caret to be painted.  The repaint
236      * area is the bounding box of the caret (i.e.
237      * the caret rectangle or <em>this</em>).
238      * <p>
239      * This method is thread safe, although most Swing methods
240      * are not. Please see
241      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
242      * in Swing</A> for more information.
243      */
repaint()244     protected final synchronized void repaint() {
245         if (component != null) {
246             component.repaint(x, y, width, height);
247         }
248     }
249 
250     /**
251      * Damages the area surrounding the caret to cause
252      * it to be repainted in a new location.  If paint()
253      * is reimplemented, this method should also be
254      * reimplemented.  This method should update the
255      * caret bounds (x, y, width, and height).
256      *
257      * @param r  the current location of the caret
258      * @see #paint
259      */
damage(Rectangle r)260     protected synchronized void damage(Rectangle r) {
261         if (r != null) {
262             int damageWidth = getCaretWidth(r.height);
263             x = r.x - 4 - (damageWidth >> 1);
264             y = r.y;
265             width = 9 + damageWidth;
266             height = r.height;
267             repaint();
268         }
269     }
270 
271     /**
272      * Scrolls the associated view (if necessary) to make
273      * the caret visible.  Since how this should be done
274      * is somewhat of a policy, this method can be
275      * reimplemented to change the behavior.  By default
276      * the scrollRectToVisible method is called on the
277      * associated component.
278      *
279      * @param nloc the new position to scroll to
280      */
adjustVisibility(Rectangle nloc)281     protected void adjustVisibility(Rectangle nloc) {
282         if(component == null) {
283             return;
284         }
285         if (SwingUtilities.isEventDispatchThread()) {
286                 component.scrollRectToVisible(nloc);
287         } else {
288             SwingUtilities.invokeLater(new SafeScroller(nloc));
289         }
290     }
291 
292     /**
293      * Gets the painter for the Highlighter.
294      *
295      * @return the painter
296      */
getSelectionPainter()297     protected Highlighter.HighlightPainter getSelectionPainter() {
298         return DefaultHighlighter.DefaultPainter;
299     }
300 
301     /**
302      * Tries to set the position of the caret from
303      * the coordinates of a mouse event, using viewToModel().
304      *
305      * @param e the mouse event
306      */
307     @SuppressWarnings("deprecation")
positionCaret(MouseEvent e)308     protected void positionCaret(MouseEvent e) {
309         Point pt = new Point(e.getX(), e.getY());
310         Position.Bias[] biasRet = new Position.Bias[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             setDot(pos, biasRet[0]);
316         }
317     }
318 
319     /**
320      * Tries to move the position of the caret from
321      * the coordinates of a mouse event, using viewToModel().
322      * This will cause a selection if the dot and mark
323      * are different.
324      *
325      * @param e the mouse event
326      */
327     @SuppressWarnings("deprecation")
moveCaret(MouseEvent e)328     protected void moveCaret(MouseEvent e) {
329         Point pt = new Point(e.getX(), e.getY());
330         Position.Bias[] biasRet = new Position.Bias[1];
331         int pos = component.getUI().viewToModel(component, pt, biasRet);
332         if(biasRet[0] == null)
333             biasRet[0] = Position.Bias.Forward;
334         if (pos >= 0) {
335             moveDot(pos, biasRet[0]);
336         }
337     }
338 
339     // --- FocusListener methods --------------------------
340 
341     /**
342      * Called when the component containing the caret gains
343      * focus.  This is implemented to set the caret to visible
344      * if the component is editable.
345      *
346      * @param e the focus event
347      * @see FocusListener#focusGained
348      */
focusGained(FocusEvent e)349     public void focusGained(FocusEvent e) {
350         if (component.isEnabled()) {
351             if (component.isEditable()) {
352                 setVisible(true);
353             }
354             setSelectionVisible(true);
355             updateSystemSelection();
356         }
357     }
358 
359     /**
360      * Called when the component containing the caret loses
361      * focus.  This is implemented to set the caret to visibility
362      * to false.
363      *
364      * @param e the focus event
365      * @see FocusListener#focusLost
366      */
focusLost(FocusEvent e)367     public void focusLost(FocusEvent e) {
368         setVisible(false);
369         setSelectionVisible((e.getCause() == FocusEvent.Cause.ACTIVATION ||
370                 e.getOppositeComponent() instanceof JRootPane) &&
371                 (ownsSelection || e.isTemporary()));
372     }
373 
374 
375     /**
376      * Selects word based on the MouseEvent
377      */
378     @SuppressWarnings("deprecation")
selectWord(MouseEvent e)379     private void selectWord(MouseEvent e) {
380         if (selectedWordEvent != null
381             && selectedWordEvent.getX() == e.getX()
382             && selectedWordEvent.getY() == e.getY()) {
383             //we already done selection for this
384             return;
385         }
386                     Action a = null;
387                     ActionMap map = getComponent().getActionMap();
388                     if (map != null) {
389                         a = map.get(DefaultEditorKit.selectWordAction);
390                     }
391                     if (a == null) {
392                         if (selectWord == null) {
393                             selectWord = new DefaultEditorKit.SelectWordAction();
394                         }
395                         a = selectWord;
396                     }
397                     a.actionPerformed(new ActionEvent(getComponent(),
398                                                       ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
399         selectedWordEvent = e;
400     }
401 
402     // --- MouseListener methods -----------------------------------
403 
404     /**
405      * Called when the mouse is clicked.  If the click was generated
406      * from button1, a double click selects a word,
407      * and a triple click the current line.
408      *
409      * @param e the mouse event
410      * @see MouseListener#mouseClicked
411      */
412     @SuppressWarnings("deprecation")
mouseClicked(MouseEvent e)413     public void mouseClicked(MouseEvent e) {
414         if (getComponent() == null) {
415             return;
416         }
417 
418         int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
419 
420         if (! e.isConsumed()) {
421             if (SwingUtilities.isLeftMouseButton(e)) {
422                 // mouse 1 behavior
423                 if(nclicks == 1) {
424                     selectedWordEvent = null;
425                 } else if(nclicks == 2
426                           && SwingUtilities2.canEventAccessSystemClipboard(e)) {
427                     selectWord(e);
428                     selectedWordEvent = null;
429                 } else if(nclicks == 3
430                           && SwingUtilities2.canEventAccessSystemClipboard(e)) {
431                     Action a = null;
432                     ActionMap map = getComponent().getActionMap();
433                     if (map != null) {
434                         a = map.get(DefaultEditorKit.selectLineAction);
435                     }
436                     if (a == null) {
437                         if (selectLine == null) {
438                             selectLine = new DefaultEditorKit.SelectLineAction();
439                         }
440                         a = selectLine;
441                     }
442                     a.actionPerformed(new ActionEvent(getComponent(),
443                                                       ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
444                 }
445             } else if (SwingUtilities.isMiddleMouseButton(e)) {
446                 // mouse 2 behavior
447                 if (nclicks == 1 && component.isEditable() && component.isEnabled()
448                     && SwingUtilities2.canEventAccessSystemClipboard(e)) {
449                     // paste system selection, if it exists
450                     JTextComponent c = (JTextComponent) e.getSource();
451                     if (c != null) {
452                         try {
453                             Toolkit tk = c.getToolkit();
454                             Clipboard buffer = tk.getSystemSelection();
455                             if (buffer != null) {
456                                 // platform supports system selections, update it.
457                                 adjustCaret(e);
458                                 TransferHandler th = c.getTransferHandler();
459                                 if (th != null) {
460                                     Transferable trans = null;
461 
462                                     try {
463                                         trans = buffer.getContents(null);
464                                     } catch (IllegalStateException ise) {
465                                         // clipboard was unavailable
466                                         UIManager.getLookAndFeel().provideErrorFeedback(c);
467                                     }
468 
469                                     if (trans != null) {
470                                         th.importData(c, trans);
471                                     }
472                                 }
473                                 adjustFocus(true);
474                             }
475                         } catch (HeadlessException he) {
476                             // do nothing... there is no system clipboard
477                         }
478                     }
479                 }
480             }
481         }
482     }
483 
484     /**
485      * If button 1 is pressed, this is implemented to
486      * request focus on the associated text component,
487      * and to set the caret position. If the shift key is held down,
488      * the caret will be moved, potentially resulting in a selection,
489      * otherwise the
490      * caret position will be set to the new location.  If the component
491      * is not enabled, there will be no request for focus.
492      *
493      * @param e the mouse event
494      * @see MouseListener#mousePressed
495      */
mousePressed(MouseEvent e)496     public void mousePressed(MouseEvent e) {
497         int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
498 
499         if (SwingUtilities.isLeftMouseButton(e)) {
500             if (e.isConsumed()) {
501                 shouldHandleRelease = true;
502             } else {
503                 shouldHandleRelease = false;
504                 adjustCaretAndFocus(e);
505                 if (nclicks == 2
506                     && SwingUtilities2.canEventAccessSystemClipboard(e)) {
507                     selectWord(e);
508                 }
509             }
510         }
511     }
512 
adjustCaretAndFocus(MouseEvent e)513     void adjustCaretAndFocus(MouseEvent e) {
514         adjustCaret(e);
515         adjustFocus(false);
516     }
517 
518     /**
519      * Adjusts the caret location based on the MouseEvent.
520      */
521     @SuppressWarnings("deprecation")
adjustCaret(MouseEvent e)522     private void adjustCaret(MouseEvent e) {
523         if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
524             getDot() != -1) {
525             moveCaret(e);
526         } else if (!e.isPopupTrigger()) {
527             positionCaret(e);
528         }
529     }
530 
531     /**
532      * Adjusts the focus, if necessary.
533      *
534      * @param inWindow if true indicates requestFocusInWindow should be used
535      */
adjustFocus(boolean inWindow)536     private void adjustFocus(boolean inWindow) {
537         if ((component != null) && component.isEnabled() &&
538                                    component.isRequestFocusEnabled()) {
539             if (inWindow) {
540                 component.requestFocusInWindow();
541             }
542             else {
543                 component.requestFocus();
544             }
545         }
546     }
547 
548     /**
549      * Called when the mouse is released.
550      *
551      * @param e the mouse event
552      * @see MouseListener#mouseReleased
553      */
mouseReleased(MouseEvent e)554     public void mouseReleased(MouseEvent e) {
555         if (!e.isConsumed()
556                 && shouldHandleRelease
557                 && SwingUtilities.isLeftMouseButton(e)) {
558 
559             adjustCaretAndFocus(e);
560         }
561     }
562 
563     /**
564      * Called when the mouse enters a region.
565      *
566      * @param e the mouse event
567      * @see MouseListener#mouseEntered
568      */
mouseEntered(MouseEvent e)569     public void mouseEntered(MouseEvent e) {
570     }
571 
572     /**
573      * Called when the mouse exits a region.
574      *
575      * @param e the mouse event
576      * @see MouseListener#mouseExited
577      */
mouseExited(MouseEvent e)578     public void mouseExited(MouseEvent e) {
579     }
580 
581     // --- MouseMotionListener methods -------------------------
582 
583     /**
584      * Moves the caret position
585      * according to the mouse pointer's current
586      * location.  This effectively extends the
587      * selection.  By default, this is only done
588      * for mouse button 1.
589      *
590      * @param e the mouse event
591      * @see MouseMotionListener#mouseDragged
592      */
mouseDragged(MouseEvent e)593     public void mouseDragged(MouseEvent e) {
594         if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
595             moveCaret(e);
596         }
597     }
598 
599     /**
600      * Called when the mouse is moved.
601      *
602      * @param e the mouse event
603      * @see MouseMotionListener#mouseMoved
604      */
mouseMoved(MouseEvent e)605     public void mouseMoved(MouseEvent e) {
606     }
607 
608     // ---- Caret methods ---------------------------------
609 
610     /**
611      * Renders the caret as a vertical line.  If this is reimplemented
612      * the damage method should also be reimplemented as it assumes the
613      * shape of the caret is a vertical line.  Sets the caret color to
614      * the value returned by getCaretColor().
615      * <p>
616      * If there are multiple text directions present in the associated
617      * document, a flag indicating the caret bias will be rendered.
618      * This will occur only if the associated document is a subclass
619      * of AbstractDocument and there are multiple bidi levels present
620      * in the bidi element structure (i.e. the text has multiple
621      * directions associated with it).
622      *
623      * @param g the graphics context
624      * @see #damage
625      */
626     @SuppressWarnings("deprecation")
paint(Graphics g)627     public void paint(Graphics g) {
628         if(isVisible()) {
629             try {
630                 TextUI mapper = component.getUI();
631                 Rectangle r = mapper.modelToView(component, dot, dotBias);
632 
633                 if ((r == null) || ((r.width == 0) && (r.height == 0))) {
634                     return;
635                 }
636                 if (width > 0 && height > 0 &&
637                                 !this._contains(r.x, r.y, r.width, r.height)) {
638                     // We seem to have gotten out of sync and no longer
639                     // contain the right location, adjust accordingly.
640                     Rectangle clip = g.getClipBounds();
641 
642                     if (clip != null && !clip.contains(this)) {
643                         // Clip doesn't contain the old location, force it
644                         // to be repainted lest we leave a caret around.
645                         repaint();
646                     }
647                     // This will potentially cause a repaint of something
648                     // we're already repainting, but without changing the
649                     // semantics of damage we can't really get around this.
650                     damage(r);
651                 }
652                 g.setColor(component.getCaretColor());
653                 int paintWidth = getCaretWidth(r.height);
654                 r.x -= paintWidth  >> 1;
655                 g.fillRect(r.x, r.y, paintWidth, r.height);
656 
657                 // see if we should paint a flag to indicate the bias
658                 // of the caret.
659                 // PENDING(prinz) this should be done through
660                 // protected methods so that alternative LAF
661                 // will show bidi information.
662                 Document doc = component.getDocument();
663                 if (doc instanceof AbstractDocument) {
664                     Element bidi = ((AbstractDocument)doc).getBidiRootElement();
665                     if ((bidi != null) && (bidi.getElementCount() > 1)) {
666                         // there are multiple directions present.
667                         flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
668                         flagYPoints[0] = r.y;
669                         flagXPoints[1] = flagXPoints[0];
670                         flagYPoints[1] = flagYPoints[0] + 4;
671                         flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
672                         flagYPoints[2] = flagYPoints[0];
673                         g.fillPolygon(flagXPoints, flagYPoints, 3);
674                     }
675                 }
676             } catch (BadLocationException e) {
677                 // can't render I guess
678                 //System.err.println("Can't render cursor");
679             }
680         }
681     }
682 
683     /**
684      * Called when the UI is being installed into the
685      * interface of a JTextComponent.  This can be used
686      * to gain access to the model that is being navigated
687      * by the implementation of this interface.  Sets the dot
688      * and mark to 0, and establishes document, property change,
689      * focus, mouse, and mouse motion listeners.
690      *
691      * @param c the component
692      * @see Caret#install
693      */
install(JTextComponent c)694     public void install(JTextComponent c) {
695         component = c;
696         Document doc = c.getDocument();
697         dot = mark = 0;
698         dotLTR = markLTR = true;
699         dotBias = markBias = Position.Bias.Forward;
700         if (doc != null) {
701             doc.addDocumentListener(handler);
702         }
703         c.addPropertyChangeListener(handler);
704         c.addFocusListener(this);
705         c.addMouseListener(this);
706         c.addMouseMotionListener(this);
707 
708         // if the component already has focus, it won't
709         // be notified.
710         if (component.hasFocus()) {
711             focusGained(null);
712         }
713 
714         Number ratio = (Number) c.getClientProperty("caretAspectRatio");
715         if (ratio != null) {
716             aspectRatio = ratio.floatValue();
717         } else {
718             aspectRatio = -1;
719         }
720 
721         Integer width = (Integer) c.getClientProperty("caretWidth");
722         if (width != null) {
723             caretWidth = width.intValue();
724         } else {
725             caretWidth = -1;
726         }
727     }
728 
729     /**
730      * Called when the UI is being removed from the
731      * interface of a JTextComponent.  This is used to
732      * unregister any listeners that were attached.
733      *
734      * @param c the component
735      * @see Caret#deinstall
736      */
deinstall(JTextComponent c)737     public void deinstall(JTextComponent c) {
738         c.removeMouseListener(this);
739         c.removeMouseMotionListener(this);
740         c.removeFocusListener(this);
741         c.removePropertyChangeListener(handler);
742         Document doc = c.getDocument();
743         if (doc != null) {
744             doc.removeDocumentListener(handler);
745         }
746         synchronized(this) {
747             component = null;
748         }
749         if (flasher != null) {
750             flasher.stop();
751         }
752 
753 
754     }
755 
756     /**
757      * Adds a listener to track whenever the caret position has
758      * been changed.
759      *
760      * @param l the listener
761      * @see Caret#addChangeListener
762      */
addChangeListener(ChangeListener l)763     public void addChangeListener(ChangeListener l) {
764         listenerList.add(ChangeListener.class, l);
765     }
766 
767     /**
768      * Removes a listener that was tracking caret position changes.
769      *
770      * @param l the listener
771      * @see Caret#removeChangeListener
772      */
removeChangeListener(ChangeListener l)773     public void removeChangeListener(ChangeListener l) {
774         listenerList.remove(ChangeListener.class, l);
775     }
776 
777     /**
778      * Returns an array of all the change listeners
779      * registered on this caret.
780      *
781      * @return all of this caret's <code>ChangeListener</code>s
782      *         or an empty
783      *         array if no change listeners are currently registered
784      *
785      * @see #addChangeListener
786      * @see #removeChangeListener
787      *
788      * @since 1.4
789      */
getChangeListeners()790     public ChangeListener[] getChangeListeners() {
791         return listenerList.getListeners(ChangeListener.class);
792     }
793 
794     /**
795      * Notifies all listeners that have registered interest for
796      * notification on this event type.  The event instance
797      * is lazily created using the parameters passed into
798      * the fire method.  The listener list is processed last to first.
799      *
800      * @see EventListenerList
801      */
fireStateChanged()802     protected void fireStateChanged() {
803         // Guaranteed to return a non-null array
804         Object[] listeners = listenerList.getListenerList();
805         // Process the listeners last to first, notifying
806         // those that are interested in this event
807         for (int i = listeners.length-2; i>=0; i-=2) {
808             if (listeners[i]==ChangeListener.class) {
809                 // Lazily create the event:
810                 if (changeEvent == null)
811                     changeEvent = new ChangeEvent(this);
812                 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
813             }
814         }
815     }
816 
817     /**
818      * Returns an array of all the objects currently registered
819      * as <code><em>Foo</em>Listener</code>s
820      * upon this caret.
821      * <code><em>Foo</em>Listener</code>s are registered using the
822      * <code>add<em>Foo</em>Listener</code> method.
823      *
824      * <p>
825      *
826      * You can specify the <code>listenerType</code> argument
827      * with a class literal,
828      * such as
829      * <code><em>Foo</em>Listener.class</code>.
830      * For example, you can query a
831      * <code>DefaultCaret</code> <code>c</code>
832      * for its change listeners with the following code:
833      *
834      * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
835      *
836      * If no such listeners exist, this method returns an empty array.
837      * @param <T> the listener type
838      * @param listenerType the type of listeners requested
839      * @return an array of all objects registered as
840      *          <code><em>Foo</em>Listener</code>s on this component,
841      *          or an empty array if no such
842      *          listeners have been added
843      * @exception ClassCastException if <code>listenerType</code>
844      *          doesn't specify a class or interface that implements
845      *          <code>java.util.EventListener</code>
846      *
847      * @see #getChangeListeners
848      *
849      * @since 1.3
850      */
getListeners(Class<T> listenerType)851     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
852         return listenerList.getListeners(listenerType);
853     }
854 
855     /**
856      * Changes the selection visibility.
857      *
858      * @param vis the new visibility
859      */
setSelectionVisible(boolean vis)860     public void setSelectionVisible(boolean vis) {
861         if (vis != selectionVisible) {
862             selectionVisible = vis;
863             if (selectionVisible) {
864                 // show
865                 Highlighter h = component.getHighlighter();
866                 if ((dot != mark) && (h != null) && (selectionTag == null)) {
867                     int p0 = Math.min(dot, mark);
868                     int p1 = Math.max(dot, mark);
869                     Highlighter.HighlightPainter p = getSelectionPainter();
870                     try {
871                         selectionTag = h.addHighlight(p0, p1, p);
872                     } catch (BadLocationException bl) {
873                         selectionTag = null;
874                     }
875                 }
876             } else {
877                 // hide
878                 if (selectionTag != null) {
879                     Highlighter h = component.getHighlighter();
880                     h.removeHighlight(selectionTag);
881                     selectionTag = null;
882                 }
883             }
884         }
885     }
886 
887     /**
888      * Checks whether the current selection is visible.
889      *
890      * @return true if the selection is visible
891      */
isSelectionVisible()892     public boolean isSelectionVisible() {
893         return selectionVisible;
894     }
895 
896     /**
897      * Determines if the caret is currently active.
898      * <p>
899      * This method returns whether or not the <code>Caret</code>
900      * is currently in a blinking state. It does not provide
901      * information as to whether it is currently blinked on or off.
902      * To determine if the caret is currently painted use the
903      * <code>isVisible</code> method.
904      *
905      * @return <code>true</code> if active else <code>false</code>
906      * @see #isVisible
907      *
908      * @since 1.5
909      */
isActive()910     public boolean isActive() {
911         return active;
912     }
913 
914     /**
915      * Indicates whether or not the caret is currently visible. As the
916      * caret flashes on and off the return value of this will change
917      * between true, when the caret is painted, and false, when the
918      * caret is not painted. <code>isActive</code> indicates whether
919      * or not the caret is in a blinking state, such that it <b>can</b>
920      * be visible, and <code>isVisible</code> indicates whether or not
921      * the caret <b>is</b> actually visible.
922      * <p>
923      * Subclasses that wish to render a different flashing caret
924      * should override paint and only paint the caret if this method
925      * returns true.
926      *
927      * @return true if visible else false
928      * @see Caret#isVisible
929      * @see #isActive
930      */
isVisible()931     public boolean isVisible() {
932         return visible;
933     }
934 
935     /**
936      * Sets the caret visibility, and repaints the caret.
937      * It is important to understand the relationship between this method,
938      * <code>isVisible</code> and <code>isActive</code>.
939      * Calling this method with a value of <code>true</code> activates the
940      * caret blinking. Setting it to <code>false</code> turns it completely off.
941      * To determine whether the blinking is active, you should call
942      * <code>isActive</code>. In effect, <code>isActive</code> is an
943      * appropriate corresponding "getter" method for this one.
944      * <code>isVisible</code> can be used to fetch the current
945      * visibility status of the caret, meaning whether or not it is currently
946      * painted. This status will change as the caret blinks on and off.
947      * <p>
948      * Here's a list showing the potential return values of both
949      * <code>isActive</code> and <code>isVisible</code>
950      * after calling this method:
951      * <p>
952      * <b><code>setVisible(true)</code></b>:
953      * <ul>
954      *     <li>isActive(): true</li>
955      *     <li>isVisible(): true or false depending on whether
956      *         or not the caret is blinked on or off</li>
957      * </ul>
958      * <p>
959      * <b><code>setVisible(false)</code></b>:
960      * <ul>
961      *     <li>isActive(): false</li>
962      *     <li>isVisible(): false</li>
963      * </ul>
964      *
965      * @param e the visibility specifier
966      * @see #isActive
967      * @see Caret#setVisible
968      */
969     @SuppressWarnings("deprecation")
setVisible(boolean e)970     public void setVisible(boolean e) {
971         // focus lost notification can come in later after the
972         // caret has been deinstalled, in which case the component
973         // will be null.
974         active = e;
975         if (component != null) {
976             TextUI mapper = component.getUI();
977             if (visible != e) {
978                 visible = e;
979                 // repaint the caret
980                 try {
981                     Rectangle loc = mapper.modelToView(component, dot,dotBias);
982                     damage(loc);
983                 } catch (BadLocationException badloc) {
984                     // hmm... not legally positioned
985                 }
986             }
987         }
988         if (flasher != null) {
989             if (visible) {
990                 flasher.start();
991             } else {
992                 flasher.stop();
993             }
994         }
995     }
996 
997     /**
998      * Sets the caret blink rate.
999      *
1000      * @param rate the rate in milliseconds, 0 to stop blinking
1001      * @see Caret#setBlinkRate
1002      */
setBlinkRate(int rate)1003     public void setBlinkRate(int rate) {
1004         if (rate != 0) {
1005             if (flasher == null) {
1006                 flasher = new Timer(rate, handler);
1007             }
1008             flasher.setDelay(rate);
1009         } else {
1010             if (flasher != null) {
1011                 flasher.stop();
1012                 flasher.removeActionListener(handler);
1013                 flasher = null;
1014             }
1015         }
1016     }
1017 
1018     /**
1019      * Gets the caret blink rate.
1020      *
1021      * @return the delay in milliseconds.  If this is
1022      *  zero the caret will not blink.
1023      * @see Caret#getBlinkRate
1024      */
getBlinkRate()1025     public int getBlinkRate() {
1026         return (flasher == null) ? 0 : flasher.getDelay();
1027     }
1028 
1029     /**
1030      * Fetches the current position of the caret.
1031      *
1032      * @return the position &gt;= 0
1033      * @see Caret#getDot
1034      */
getDot()1035     public int getDot() {
1036         return dot;
1037     }
1038 
1039     /**
1040      * Fetches the current position of the mark.  If there is a selection,
1041      * the dot and mark will not be the same.
1042      *
1043      * @return the position &gt;= 0
1044      * @see Caret#getMark
1045      */
getMark()1046     public int getMark() {
1047         return mark;
1048     }
1049 
1050     /**
1051      * Sets the caret position and mark to the specified position,
1052      * with a forward bias. This implicitly sets the
1053      * selection range to zero.
1054      *
1055      * @param dot the position &gt;= 0
1056      * @see #setDot(int, Position.Bias)
1057      * @see Caret#setDot
1058      */
setDot(int dot)1059     public void setDot(int dot) {
1060         setDot(dot, Position.Bias.Forward);
1061     }
1062 
1063     /**
1064      * Moves the caret position to the specified position,
1065      * with a forward bias.
1066      *
1067      * @param dot the position &gt;= 0
1068      * @see #moveDot(int, javax.swing.text.Position.Bias)
1069      * @see Caret#moveDot
1070      */
moveDot(int dot)1071     public void moveDot(int dot) {
1072         moveDot(dot, Position.Bias.Forward);
1073     }
1074 
1075     // ---- Bidi methods (we could put these in a subclass)
1076 
1077     /**
1078      * Moves the caret position to the specified position, with the
1079      * specified bias.
1080      *
1081      * @param dot the position &gt;= 0
1082      * @param dotBias the bias for this position, not <code>null</code>
1083      * @throws IllegalArgumentException if the bias is <code>null</code>
1084      * @see Caret#moveDot
1085      * @since 1.6
1086      */
moveDot(int dot, Position.Bias dotBias)1087     public void moveDot(int dot, Position.Bias dotBias) {
1088         if (dotBias == null) {
1089             throw new IllegalArgumentException("null bias");
1090         }
1091 
1092         if (! component.isEnabled()) {
1093             // don't allow selection on disabled components.
1094             setDot(dot, dotBias);
1095             return;
1096         }
1097         if (dot != this.dot) {
1098             NavigationFilter filter = component.getNavigationFilter();
1099 
1100             if (filter != null) {
1101                 filter.moveDot(getFilterBypass(), dot, dotBias);
1102             }
1103             else {
1104                 handleMoveDot(dot, dotBias);
1105             }
1106         }
1107     }
1108 
handleMoveDot(int dot, Position.Bias dotBias)1109     void handleMoveDot(int dot, Position.Bias dotBias) {
1110         changeCaretPosition(dot, dotBias);
1111 
1112         if (selectionVisible) {
1113             Highlighter h = component.getHighlighter();
1114             if (h != null) {
1115                 int p0 = Math.min(dot, mark);
1116                 int p1 = Math.max(dot, mark);
1117 
1118                 // if p0 == p1 then there should be no highlight, remove it if necessary
1119                 if (p0 == p1) {
1120                     if (selectionTag != null) {
1121                         h.removeHighlight(selectionTag);
1122                         selectionTag = null;
1123                     }
1124                 // otherwise, change or add the highlight
1125                 } else {
1126                     try {
1127                         if (selectionTag != null) {
1128                             h.changeHighlight(selectionTag, p0, p1);
1129                         } else {
1130                             Highlighter.HighlightPainter p = getSelectionPainter();
1131                             selectionTag = h.addHighlight(p0, p1, p);
1132                         }
1133                     } catch (BadLocationException e) {
1134                         throw new StateInvariantError("Bad caret position");
1135                     }
1136                 }
1137             }
1138         }
1139     }
1140 
1141     /**
1142      * Sets the caret position and mark to the specified position, with the
1143      * specified bias. This implicitly sets the selection range
1144      * to zero.
1145      *
1146      * @param dot the position &gt;= 0
1147      * @param dotBias the bias for this position, not <code>null</code>
1148      * @throws IllegalArgumentException if the bias is <code>null</code>
1149      * @see Caret#setDot
1150      * @since 1.6
1151      */
setDot(int dot, Position.Bias dotBias)1152     public void setDot(int dot, Position.Bias dotBias) {
1153         if (dotBias == null) {
1154             throw new IllegalArgumentException("null bias");
1155         }
1156 
1157         NavigationFilter filter = component.getNavigationFilter();
1158 
1159         if (filter != null) {
1160             filter.setDot(getFilterBypass(), dot, dotBias);
1161         }
1162         else {
1163             handleSetDot(dot, dotBias);
1164         }
1165     }
1166 
handleSetDot(int dot, Position.Bias dotBias)1167     void handleSetDot(int dot, Position.Bias dotBias) {
1168         // move dot, if it changed
1169         Document doc = component.getDocument();
1170         if (doc != null) {
1171             dot = Math.min(dot, doc.getLength());
1172         }
1173         dot = Math.max(dot, 0);
1174 
1175         // The position (0,Backward) is out of range so disallow it.
1176         if( dot == 0 )
1177             dotBias = Position.Bias.Forward;
1178 
1179         mark = dot;
1180         if (this.dot != dot || this.dotBias != dotBias ||
1181             selectionTag != null || forceCaretPositionChange) {
1182             changeCaretPosition(dot, dotBias);
1183         }
1184         this.markBias = this.dotBias;
1185         this.markLTR = dotLTR;
1186         Highlighter h = component.getHighlighter();
1187         if ((h != null) && (selectionTag != null)) {
1188             h.removeHighlight(selectionTag);
1189             selectionTag = null;
1190         }
1191     }
1192 
1193     /**
1194      * Returns the bias of the caret position.
1195      *
1196      * @return the bias of the caret position
1197      * @since 1.6
1198      */
getDotBias()1199     public Position.Bias getDotBias() {
1200         return dotBias;
1201     }
1202 
1203     /**
1204      * Returns the bias of the mark.
1205      *
1206      * @return the bias of the mark
1207      * @since 1.6
1208      */
getMarkBias()1209     public Position.Bias getMarkBias() {
1210         return markBias;
1211     }
1212 
isDotLeftToRight()1213     boolean isDotLeftToRight() {
1214         return dotLTR;
1215     }
1216 
isMarkLeftToRight()1217     boolean isMarkLeftToRight() {
1218         return markLTR;
1219     }
1220 
isPositionLTR(int position, Position.Bias bias)1221     boolean isPositionLTR(int position, Position.Bias bias) {
1222         Document doc = component.getDocument();
1223         if(bias == Position.Bias.Backward && --position < 0)
1224             position = 0;
1225         return AbstractDocument.isLeftToRight(doc, position, position);
1226     }
1227 
guessBiasForOffset(int offset, Position.Bias lastBias, boolean lastLTR)1228     Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
1229                                      boolean lastLTR) {
1230         // There is an abiguous case here. That if your model looks like:
1231         // abAB with the cursor at abB]A (visual representation of
1232         // 3 forward) deleting could either become abB] or
1233         // ab[B. I'ld actually prefer abB]. But, if I implement that
1234         // a delete at abBA] would result in aBA] vs a[BA which I
1235         // think is totally wrong. To get this right we need to know what
1236         // was deleted. And we could get this from the bidi structure
1237         // in the change event. So:
1238         // PENDING: base this off what was deleted.
1239         if(lastLTR != isPositionLTR(offset, lastBias)) {
1240             lastBias = Position.Bias.Backward;
1241         }
1242         else if(lastBias != Position.Bias.Backward &&
1243                 lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
1244             lastBias = Position.Bias.Backward;
1245         }
1246         if (lastBias == Position.Bias.Backward && offset > 0) {
1247             try {
1248                 Segment s = new Segment();
1249                 component.getDocument().getText(offset - 1, 1, s);
1250                 if (s.count > 0 && s.array[s.offset] == '\n') {
1251                     lastBias = Position.Bias.Forward;
1252                 }
1253             }
1254             catch (BadLocationException ble) {}
1255         }
1256         return lastBias;
1257     }
1258 
1259     // ---- local methods --------------------------------------------
1260 
1261     /**
1262      * Sets the caret position (dot) to a new location.  This
1263      * causes the old and new location to be repainted.  It
1264      * also makes sure that the caret is within the visible
1265      * region of the view, if the view is scrollable.
1266      */
changeCaretPosition(int dot, Position.Bias dotBias)1267     void changeCaretPosition(int dot, Position.Bias dotBias) {
1268         // repaint the old position and set the new value of
1269         // the dot.
1270         repaint();
1271 
1272 
1273         // Make sure the caret is visible if this window has the focus.
1274         if (flasher != null && flasher.isRunning()) {
1275             visible = true;
1276             flasher.restart();
1277         }
1278 
1279         // notify listeners at the caret moved
1280         this.dot = dot;
1281         this.dotBias = dotBias;
1282         dotLTR = isPositionLTR(dot, dotBias);
1283         fireStateChanged();
1284 
1285         updateSystemSelection();
1286 
1287         setMagicCaretPosition(null);
1288 
1289         // We try to repaint the caret later, since things
1290         // may be unstable at the time this is called
1291         // (i.e. we don't want to depend upon notification
1292         // order or the fact that this might happen on
1293         // an unsafe thread).
1294         Runnable callRepaintNewCaret = new Runnable() {
1295             public void run() {
1296                 repaintNewCaret();
1297             }
1298         };
1299         SwingUtilities.invokeLater(callRepaintNewCaret);
1300     }
1301 
1302     /**
1303      * Repaints the new caret position, with the
1304      * assumption that this is happening on the
1305      * event thread so that calling <code>modelToView</code>
1306      * is safe.
1307      */
1308     @SuppressWarnings("deprecation")
repaintNewCaret()1309     void repaintNewCaret() {
1310         if (component != null) {
1311             TextUI mapper = component.getUI();
1312             Document doc = component.getDocument();
1313             if ((mapper != null) && (doc != null)) {
1314                 // determine the new location and scroll if
1315                 // not visible.
1316                 Rectangle newLoc;
1317                 try {
1318                     newLoc = mapper.modelToView(component, this.dot, this.dotBias);
1319                 } catch (BadLocationException e) {
1320                     newLoc = null;
1321                 }
1322                 if (newLoc != null) {
1323                     adjustVisibility(newLoc);
1324                     // If there is no magic caret position, make one
1325                     if (getMagicCaretPosition() == null) {
1326                         setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
1327                     }
1328                 }
1329 
1330                 // repaint the new position
1331                 damage(newLoc);
1332             }
1333         }
1334     }
1335 
updateSystemSelection()1336     private void updateSystemSelection() {
1337         if ( ! SwingUtilities2.canCurrentEventAccessSystemClipboard() ) {
1338             return;
1339         }
1340         if (this.dot != this.mark && component != null && component.hasFocus()) {
1341             Clipboard clip = getSystemSelection();
1342             if (clip != null) {
1343                 String selectedText;
1344                 if (component instanceof JPasswordField
1345                     && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
1346                     Boolean.TRUE) {
1347                     //fix for 4793761
1348                     StringBuilder txt = null;
1349                     char echoChar = ((JPasswordField)component).getEchoChar();
1350                     int p0 = Math.min(getDot(), getMark());
1351                     int p1 = Math.max(getDot(), getMark());
1352                     for (int i = p0; i < p1; i++) {
1353                         if (txt == null) {
1354                             txt = new StringBuilder();
1355                         }
1356                         txt.append(echoChar);
1357                     }
1358                     selectedText = (txt != null) ? txt.toString() : null;
1359                 } else {
1360                     selectedText = component.getSelectedText();
1361                 }
1362                 try {
1363                     clip.setContents(
1364                         new StringSelection(selectedText), getClipboardOwner());
1365 
1366                     ownsSelection = true;
1367                 } catch (IllegalStateException ise) {
1368                     // clipboard was unavailable
1369                     // no need to provide error feedback to user since updating
1370                     // the system selection is not a user invoked action
1371                 }
1372             }
1373         }
1374     }
1375 
getSystemSelection()1376     private Clipboard getSystemSelection() {
1377         try {
1378             return component.getToolkit().getSystemSelection();
1379         } catch (HeadlessException he) {
1380             // do nothing... there is no system clipboard
1381         } catch (SecurityException se) {
1382             // do nothing... there is no allowed system clipboard
1383         }
1384         return null;
1385     }
1386 
getClipboardOwner()1387     private ClipboardOwner getClipboardOwner() {
1388         return handler;
1389     }
1390 
1391     /**
1392      * This is invoked after the document changes to verify the current
1393      * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
1394      * changed where to position the dot, that resulted in the current location
1395      * being bogus.
1396      */
ensureValidPosition()1397     private void ensureValidPosition() {
1398         int length = component.getDocument().getLength();
1399         if (dot > length || mark > length) {
1400             // Current location is bogus and filter likely vetoed the
1401             // change, force the reset without giving the filter a
1402             // chance at changing it.
1403             handleSetDot(length, Position.Bias.Forward);
1404         }
1405     }
1406 
1407 
1408     /**
1409      * Saves the current caret position.  This is used when
1410      * caret up/down actions occur, moving between lines
1411      * that have uneven end positions.
1412      *
1413      * @param p the position
1414      * @see #getMagicCaretPosition
1415      */
setMagicCaretPosition(Point p)1416     public void setMagicCaretPosition(Point p) {
1417         magicCaretPosition = p;
1418     }
1419 
1420     /**
1421      * Gets the saved caret position.
1422      *
1423      * @return the position
1424      * see #setMagicCaretPosition
1425      */
getMagicCaretPosition()1426     public Point getMagicCaretPosition() {
1427         return magicCaretPosition;
1428     }
1429 
1430     /**
1431      * Compares this object to the specified object.
1432      * The superclass behavior of comparing rectangles
1433      * is not desired, so this is changed to the Object
1434      * behavior.
1435      *
1436      * @param     obj   the object to compare this font with
1437      * @return    <code>true</code> if the objects are equal;
1438      *            <code>false</code> otherwise
1439      */
equals(Object obj)1440     public boolean equals(Object obj) {
1441         return (this == obj);
1442     }
1443 
toString()1444     public String toString() {
1445         String s = "Dot=(" + dot + ", " + dotBias + ")";
1446         s += " Mark=(" + mark + ", " + markBias + ")";
1447         return s;
1448     }
1449 
getFilterBypass()1450     private NavigationFilter.FilterBypass getFilterBypass() {
1451         if (filterBypass == null) {
1452             filterBypass = new DefaultFilterBypass();
1453         }
1454         return filterBypass;
1455     }
1456 
1457     // Rectangle.contains returns false if passed a rect with a w or h == 0,
1458     // this won't (assuming X,Y are contained with this rectangle).
_contains(int X, int Y, int W, int H)1459     private boolean _contains(int X, int Y, int W, int H) {
1460         int w = this.width;
1461         int h = this.height;
1462         if ((w | h | W | H) < 0) {
1463             // At least one of the dimensions is negative...
1464             return false;
1465         }
1466         // Note: if any dimension is zero, tests below must return false...
1467         int x = this.x;
1468         int y = this.y;
1469         if (X < x || Y < y) {
1470             return false;
1471         }
1472         if (W > 0) {
1473             w += x;
1474             W += X;
1475             if (W <= X) {
1476                 // X+W overflowed or W was zero, return false if...
1477                 // either original w or W was zero or
1478                 // x+w did not overflow or
1479                 // the overflowed x+w is smaller than the overflowed X+W
1480                 if (w >= x || W > w) return false;
1481             } else {
1482                 // X+W did not overflow and W was not zero, return false if...
1483                 // original w was zero or
1484                 // x+w did not overflow and x+w is smaller than X+W
1485                 if (w >= x && W > w) return false;
1486             }
1487         }
1488         else if ((x + w) < X) {
1489             return false;
1490         }
1491         if (H > 0) {
1492             h += y;
1493             H += Y;
1494             if (H <= Y) {
1495                 if (h >= y || H > h) return false;
1496             } else {
1497                 if (h >= y && H > h) return false;
1498             }
1499         }
1500         else if ((y + h) < Y) {
1501             return false;
1502         }
1503         return true;
1504     }
1505 
getCaretWidth(int height)1506     int getCaretWidth(int height) {
1507         if (aspectRatio > -1) {
1508             return (int) (aspectRatio * height) + 1;
1509         }
1510 
1511         if (caretWidth > -1) {
1512             return caretWidth;
1513         } else {
1514             Object property = UIManager.get("Caret.width");
1515             if (property instanceof Integer) {
1516                 return ((Integer) property).intValue();
1517             } else {
1518                 return 1;
1519             }
1520         }
1521     }
1522 
1523     // --- serialization ---------------------------------------------
1524 
readObject(ObjectInputStream s)1525     private void readObject(ObjectInputStream s)
1526       throws ClassNotFoundException, IOException
1527     {
1528         ObjectInputStream.GetField f = s.readFields();
1529 
1530         EventListenerList newListenerList = (EventListenerList) f.get("listenerList", null);
1531         if (newListenerList == null) {
1532             throw new InvalidObjectException("Null listenerList");
1533         }
1534         listenerList = newListenerList;
1535         component = (JTextComponent) f.get("component", null);
1536         updatePolicy = f.get("updatePolicy", 0);
1537         visible = f.get("visible", false);
1538         active = f.get("active", false);
1539         dot = f.get("dot", 0);
1540         mark = f.get("mark", 0);
1541         selectionTag = f.get("selectionTag", null);
1542         selectionVisible = f.get("selectionVisible", false);
1543         flasher = (Timer) f.get("flasher", null);
1544         magicCaretPosition = (Point) f.get("magicCaretPosition", null);
1545         dotLTR = f.get("dotLTR", false);
1546         markLTR = f.get("markLTR", false);
1547         ownsSelection = f.get("ownsSelection", false);
1548         forceCaretPositionChange = f.get("forceCaretPositionChange", false);
1549         caretWidth = f.get("caretWidth", 0);
1550         aspectRatio = f.get("aspectRatio", 0.0f);
1551 
1552         handler = new Handler();
1553         if (!s.readBoolean()) {
1554             dotBias = Position.Bias.Forward;
1555         }
1556         else {
1557             dotBias = Position.Bias.Backward;
1558         }
1559         if (!s.readBoolean()) {
1560             markBias = Position.Bias.Forward;
1561         }
1562         else {
1563             markBias = Position.Bias.Backward;
1564         }
1565     }
1566 
writeObject(ObjectOutputStream s)1567     private void writeObject(ObjectOutputStream s) throws IOException {
1568         s.defaultWriteObject();
1569         s.writeBoolean((dotBias == Position.Bias.Backward));
1570         s.writeBoolean((markBias == Position.Bias.Backward));
1571     }
1572 
1573     // ---- member variables ------------------------------------------
1574 
1575     /**
1576      * The event listener list.
1577      */
1578     protected EventListenerList listenerList = new EventListenerList();
1579 
1580     /**
1581      * The change event for the model.
1582      * Only one ChangeEvent is needed per model instance since the
1583      * event's only (read-only) state is the source property.  The source
1584      * of events generated here is always "this".
1585      */
1586     protected transient ChangeEvent changeEvent = null;
1587 
1588     // package-private to avoid inner classes private member
1589     // access bug
1590     JTextComponent component;
1591 
1592     int updatePolicy = UPDATE_WHEN_ON_EDT;
1593     boolean visible;
1594     boolean active;
1595     int dot;
1596     int mark;
1597     Object selectionTag;
1598     boolean selectionVisible;
1599     Timer flasher;
1600     Point magicCaretPosition;
1601     transient Position.Bias dotBias;
1602     transient Position.Bias markBias;
1603     boolean dotLTR;
1604     boolean markLTR;
1605     transient Handler handler = new Handler();
1606     private transient int[] flagXPoints = new int[3];
1607     private transient int[] flagYPoints = new int[3];
1608     private transient NavigationFilter.FilterBypass filterBypass;
1609     private static transient Action selectWord = null;
1610     private static transient Action selectLine = null;
1611     /**
1612      * This is used to indicate if the caret currently owns the selection.
1613      * This is always false if the system does not support the system
1614      * clipboard.
1615      */
1616     private boolean ownsSelection;
1617 
1618     /**
1619      * If this is true, the location of the dot is updated regardless of
1620      * the current location. This is set in the DocumentListener
1621      * such that even if the model location of dot hasn't changed (perhaps do
1622      * to a forward delete) the visual location is updated.
1623      */
1624     private boolean forceCaretPositionChange;
1625 
1626     /**
1627      * Whether or not mouseReleased should adjust the caret and focus.
1628      * This flag is set by mousePressed if it wanted to adjust the caret
1629      * and focus but couldn't because of a possible DnD operation.
1630      */
1631     private transient boolean shouldHandleRelease;
1632 
1633 
1634     /**
1635      * holds last MouseEvent which caused the word selection
1636      */
1637     private transient MouseEvent selectedWordEvent = null;
1638 
1639     /**
1640      * The width of the caret in pixels.
1641      */
1642     private int caretWidth = -1;
1643     private float aspectRatio = -1;
1644 
1645     class SafeScroller implements Runnable {
1646 
SafeScroller(Rectangle r)1647         SafeScroller(Rectangle r) {
1648             this.r = r;
1649         }
1650 
run()1651         public void run() {
1652             if (component != null) {
1653                 component.scrollRectToVisible(r);
1654             }
1655         }
1656 
1657         Rectangle r;
1658     }
1659 
1660 
1661     class Handler implements PropertyChangeListener, DocumentListener, ActionListener, ClipboardOwner {
1662 
1663         // --- ActionListener methods ----------------------------------
1664 
1665         /**
1666          * Invoked when the blink timer fires.  This is called
1667          * asynchronously.  The simply changes the visibility
1668          * and repaints the rectangle that last bounded the caret.
1669          *
1670          * @param e the action event
1671          */
1672         @SuppressWarnings("deprecation")
actionPerformed(ActionEvent e)1673         public void actionPerformed(ActionEvent e) {
1674             if (width == 0 || height == 0) {
1675                 // setVisible(true) will cause a scroll, only do this if the
1676                 // new location is really valid.
1677                 if (component != null) {
1678                     TextUI mapper = component.getUI();
1679                     try {
1680                         Rectangle r = mapper.modelToView(component, dot,
1681                                                          dotBias);
1682                         if (r != null && r.width != 0 && r.height != 0) {
1683                             damage(r);
1684                         }
1685                     } catch (BadLocationException ble) {
1686                     }
1687                 }
1688             }
1689             visible = !visible;
1690             repaint();
1691         }
1692 
1693         // --- DocumentListener methods --------------------------------
1694 
1695         /**
1696          * Updates the dot and mark if they were changed by
1697          * the insertion.
1698          *
1699          * @param e the document event
1700          * @see DocumentListener#insertUpdate
1701          */
insertUpdate(DocumentEvent e)1702         public void insertUpdate(DocumentEvent e) {
1703             if (getUpdatePolicy() == NEVER_UPDATE ||
1704                     (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1705                     !SwingUtilities.isEventDispatchThread())) {
1706 
1707                 if ((e.getOffset() <= dot || e.getOffset() <= mark)
1708                         && selectionTag != null) {
1709                     try {
1710                         component.getHighlighter().changeHighlight(selectionTag,
1711                                 Math.min(dot, mark), Math.max(dot, mark));
1712                     } catch (BadLocationException e1) {
1713                         e1.printStackTrace();
1714                     }
1715                 }
1716                 return;
1717             }
1718             int offset = e.getOffset();
1719             int length = e.getLength();
1720             int newDot = dot;
1721             short changed = 0;
1722 
1723             if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
1724                 setDot(offset + length);
1725                 return;
1726             }
1727             if (newDot >= offset) {
1728                 newDot += length;
1729                 changed |= 1;
1730             }
1731             int newMark = mark;
1732             if (newMark >= offset) {
1733                 newMark += length;
1734                 changed |= 2;
1735             }
1736 
1737             if (changed != 0) {
1738                 Position.Bias dotBias = DefaultCaret.this.dotBias;
1739                 if (dot == offset) {
1740                     Document doc = component.getDocument();
1741                     boolean isNewline;
1742                     try {
1743                         Segment s = new Segment();
1744                         doc.getText(newDot - 1, 1, s);
1745                         isNewline = (s.count > 0 &&
1746                                 s.array[s.offset] == '\n');
1747                     } catch (BadLocationException ble) {
1748                         isNewline = false;
1749                     }
1750                     if (isNewline) {
1751                         dotBias = Position.Bias.Forward;
1752                     } else {
1753                         dotBias = Position.Bias.Backward;
1754                     }
1755                 }
1756                 if (newMark == newDot) {
1757                     setDot(newDot, dotBias);
1758                     ensureValidPosition();
1759                 }
1760                 else {
1761                     setDot(newMark, markBias);
1762                     if (getDot() == newMark) {
1763                         // Due this test in case the filter vetoed the
1764                         // change in which case this probably won't be
1765                         // valid either.
1766                         moveDot(newDot, dotBias);
1767                     }
1768                     ensureValidPosition();
1769                 }
1770             }
1771         }
1772 
1773         /**
1774          * Updates the dot and mark if they were changed
1775          * by the removal.
1776          *
1777          * @param e the document event
1778          * @see DocumentListener#removeUpdate
1779          */
removeUpdate(DocumentEvent e)1780         public void removeUpdate(DocumentEvent e) {
1781             if (getUpdatePolicy() == NEVER_UPDATE ||
1782                     (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1783                     !SwingUtilities.isEventDispatchThread())) {
1784 
1785                 int length = component.getDocument().getLength();
1786                 dot = Math.min(dot, length);
1787                 mark = Math.min(mark, length);
1788                 if ((e.getOffset() < dot || e.getOffset() < mark)
1789                         && selectionTag != null) {
1790                     try {
1791                         component.getHighlighter().changeHighlight(selectionTag,
1792                                 Math.min(dot, mark), Math.max(dot, mark));
1793                     } catch (BadLocationException e1) {
1794                         e1.printStackTrace();
1795                     }
1796                 }
1797                 return;
1798             }
1799             int offs0 = e.getOffset();
1800             int offs1 = offs0 + e.getLength();
1801             int newDot = dot;
1802             boolean adjustDotBias = false;
1803             int newMark = mark;
1804             boolean adjustMarkBias = false;
1805 
1806             if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
1807                 setDot(offs0);
1808                 return;
1809             }
1810             if (newDot >= offs1) {
1811                 newDot -= (offs1 - offs0);
1812                 if(newDot == offs1) {
1813                     adjustDotBias = true;
1814                 }
1815             } else if (newDot >= offs0) {
1816                 newDot = offs0;
1817                 adjustDotBias = true;
1818             }
1819             if (newMark >= offs1) {
1820                 newMark -= (offs1 - offs0);
1821                 if(newMark == offs1) {
1822                     adjustMarkBias = true;
1823                 }
1824             } else if (newMark >= offs0) {
1825                 newMark = offs0;
1826                 adjustMarkBias = true;
1827             }
1828             if (newMark == newDot) {
1829                 forceCaretPositionChange = true;
1830                 try {
1831                     setDot(newDot, guessBiasForOffset(newDot, dotBias,
1832                             dotLTR));
1833                 } finally {
1834                     forceCaretPositionChange = false;
1835                 }
1836                 ensureValidPosition();
1837             } else {
1838                 Position.Bias dotBias = DefaultCaret.this.dotBias;
1839                 Position.Bias markBias = DefaultCaret.this.markBias;
1840                 if(adjustDotBias) {
1841                     dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
1842                 }
1843                 if(adjustMarkBias) {
1844                     markBias = guessBiasForOffset(mark, markBias, markLTR);
1845                 }
1846                 setDot(newMark, markBias);
1847                 if (getDot() == newMark) {
1848                     // Due this test in case the filter vetoed the change
1849                     // in which case this probably won't be valid either.
1850                     moveDot(newDot, dotBias);
1851                 }
1852                 ensureValidPosition();
1853             }
1854         }
1855 
1856         /**
1857          * Gives notification that an attribute or set of attributes changed.
1858          *
1859          * @param e the document event
1860          * @see DocumentListener#changedUpdate
1861          */
changedUpdate(DocumentEvent e)1862         public void changedUpdate(DocumentEvent e) {
1863             if (getUpdatePolicy() == NEVER_UPDATE ||
1864                     (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
1865                     !SwingUtilities.isEventDispatchThread())) {
1866                 return;
1867             }
1868             if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
1869                 setDot(e.getOffset() + e.getLength());
1870             }
1871         }
1872 
1873         // --- PropertyChangeListener methods -----------------------
1874 
1875         /**
1876          * This method gets called when a bound property is changed.
1877          * We are looking for document changes on the editor.
1878          */
propertyChange(PropertyChangeEvent evt)1879         public void propertyChange(PropertyChangeEvent evt) {
1880             Object oldValue = evt.getOldValue();
1881             Object newValue = evt.getNewValue();
1882             if ((oldValue instanceof Document) || (newValue instanceof Document)) {
1883                 setDot(0);
1884                 if (oldValue != null) {
1885                     ((Document)oldValue).removeDocumentListener(this);
1886                 }
1887                 if (newValue != null) {
1888                     ((Document)newValue).addDocumentListener(this);
1889                 }
1890             } else if("enabled".equals(evt.getPropertyName())) {
1891                 Boolean enabled = (Boolean) evt.getNewValue();
1892                 if(component.isFocusOwner()) {
1893                     if(enabled == Boolean.TRUE) {
1894                         if(component.isEditable()) {
1895                             setVisible(true);
1896                         }
1897                         setSelectionVisible(true);
1898                     } else {
1899                         setVisible(false);
1900                         setSelectionVisible(false);
1901                     }
1902                 }
1903             } else if("caretWidth".equals(evt.getPropertyName())) {
1904                 Integer newWidth = (Integer) evt.getNewValue();
1905                 if (newWidth != null) {
1906                     caretWidth = newWidth.intValue();
1907                 } else {
1908                     caretWidth = -1;
1909                 }
1910                 repaint();
1911             } else if("caretAspectRatio".equals(evt.getPropertyName())) {
1912                 Number newRatio = (Number) evt.getNewValue();
1913                 if (newRatio != null) {
1914                     aspectRatio = newRatio.floatValue();
1915                 } else {
1916                     aspectRatio = -1;
1917                 }
1918                 repaint();
1919             }
1920         }
1921 
1922 
1923         //
1924         // ClipboardOwner
1925         //
1926         /**
1927          * Toggles the visibility of the selection when ownership is lost.
1928          */
lostOwnership(Clipboard clipboard, Transferable contents)1929         public void lostOwnership(Clipboard clipboard,
1930                                       Transferable contents) {
1931             if (ownsSelection) {
1932                 ownsSelection = false;
1933                 if (component != null && !component.hasFocus()) {
1934                     setSelectionVisible(false);
1935                 }
1936             }
1937         }
1938     }
1939 
1940 
1941     private class DefaultFilterBypass extends NavigationFilter.FilterBypass {
getCaret()1942         public Caret getCaret() {
1943             return DefaultCaret.this;
1944         }
1945 
setDot(int dot, Position.Bias bias)1946         public void setDot(int dot, Position.Bias bias) {
1947             handleSetDot(dot, bias);
1948         }
1949 
moveDot(int dot, Position.Bias bias)1950         public void moveDot(int dot, Position.Bias bias) {
1951             handleMoveDot(dot, bias);
1952         }
1953     }
1954 }
1955