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