1 /* 2 * Copyright (c) 1997, 2012, 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<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 */ 108 public 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://download.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 241 * in Swing</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 if (getComponent() == null) { 407 return; 408 } 409 410 int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e); 411 412 if (! e.isConsumed()) { 413 if (SwingUtilities.isLeftMouseButton(e)) { 414 // mouse 1 behavior 415 if(nclicks == 1) { 416 selectedWordEvent = null; 417 } else if(nclicks == 2 418 && SwingUtilities2.canEventAccessSystemClipboard(e)) { 419 selectWord(e); 420 selectedWordEvent = null; 421 } else if(nclicks == 3 422 && SwingUtilities2.canEventAccessSystemClipboard(e)) { 423 Action a = null; 424 ActionMap map = getComponent().getActionMap(); 425 if (map != null) { 426 a = map.get(DefaultEditorKit.selectLineAction); 427 } 428 if (a == null) { 429 if (selectLine == null) { 430 selectLine = new DefaultEditorKit.SelectLineAction(); 431 } 432 a = selectLine; 433 } 434 a.actionPerformed(new ActionEvent(getComponent(), 435 ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers())); 436 } 437 } else if (SwingUtilities.isMiddleMouseButton(e)) { 438 // mouse 2 behavior 439 if (nclicks == 1 && component.isEditable() && component.isEnabled() 440 && SwingUtilities2.canEventAccessSystemClipboard(e)) { 441 // paste system selection, if it exists 442 JTextComponent c = (JTextComponent) e.getSource(); 443 if (c != null) { 444 try { 445 Toolkit tk = c.getToolkit(); 446 Clipboard buffer = tk.getSystemSelection(); 447 if (buffer != null) { 448 // platform supports system selections, update it. 449 adjustCaret(e); 450 TransferHandler th = c.getTransferHandler(); 451 if (th != null) { 452 Transferable trans = null; 453 454 try { 455 trans = buffer.getContents(null); 456 } catch (IllegalStateException ise) { 457 // clipboard was unavailable 458 UIManager.getLookAndFeel().provideErrorFeedback(c); 459 } 460 461 if (trans != null) { 462 th.importData(c, trans); 463 } 464 } 465 adjustFocus(true); 466 } 467 } catch (HeadlessException he) { 468 // do nothing... there is no system clipboard 469 } 470 } 471 } 472 } 473 } 474 } 475 476 /** 477 * If button 1 is pressed, this is implemented to 478 * request focus on the associated text component, 479 * and to set the caret position. If the shift key is held down, 480 * the caret will be moved, potentially resulting in a selection, 481 * otherwise the 482 * caret position will be set to the new location. If the component 483 * is not enabled, there will be no request for focus. 484 * 485 * @param e the mouse event 486 * @see MouseListener#mousePressed 487 */ 488 public void mousePressed(MouseEvent e) { 489 int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e); 490 491 if (SwingUtilities.isLeftMouseButton(e)) { 492 if (e.isConsumed()) { 493 shouldHandleRelease = true; 494 } else { 495 shouldHandleRelease = false; 496 adjustCaretAndFocus(e); 497 if (nclicks == 2 498 && SwingUtilities2.canEventAccessSystemClipboard(e)) { 499 selectWord(e); 500 } 501 } 502 } 503 } 504 505 void adjustCaretAndFocus(MouseEvent e) { 506 adjustCaret(e); 507 adjustFocus(false); 508 } 509 510 /** 511 * Adjusts the caret location based on the MouseEvent. 512 */ 513 private void adjustCaret(MouseEvent e) { 514 if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 && 515 getDot() != -1) { 516 moveCaret(e); 517 } else if (!e.isPopupTrigger()) { 518 positionCaret(e); 519 } 520 } 521 522 /** 523 * Adjusts the focus, if necessary. 524 * 525 * @param inWindow if true indicates requestFocusInWindow should be used 526 */ 527 private void adjustFocus(boolean inWindow) { 528 if ((component != null) && component.isEnabled() && 529 component.isRequestFocusEnabled()) { 530 if (inWindow) { 531 component.requestFocusInWindow(); 532 } 533 else { 534 component.requestFocus(); 535 } 536 } 537 } 538 539 /** 540 * Called when the mouse is released. 541 * 542 * @param e the mouse event 543 * @see MouseListener#mouseReleased 544 */ 545 public void mouseReleased(MouseEvent e) { 546 if (!e.isConsumed() 547 && shouldHandleRelease 548 && SwingUtilities.isLeftMouseButton(e)) { 549 550 adjustCaretAndFocus(e); 551 } 552 } 553 554 /** 555 * Called when the mouse enters a region. 556 * 557 * @param e the mouse event 558 * @see MouseListener#mouseEntered 559 */ 560 public void mouseEntered(MouseEvent e) { 561 } 562 563 /** 564 * Called when the mouse exits a region. 565 * 566 * @param e the mouse event 567 * @see MouseListener#mouseExited 568 */ 569 public void mouseExited(MouseEvent e) { 570 } 571 572 // --- MouseMotionListener methods ------------------------- 573 574 /** 575 * Moves the caret position 576 * according to the mouse pointer's current 577 * location. This effectively extends the 578 * selection. By default, this is only done 579 * for mouse button 1. 580 * 581 * @param e the mouse event 582 * @see MouseMotionListener#mouseDragged 583 */ 584 public void mouseDragged(MouseEvent e) { 585 if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) { 586 moveCaret(e); 587 } 588 } 589 590 /** 591 * Called when the mouse is moved. 592 * 593 * @param e the mouse event 594 * @see MouseMotionListener#mouseMoved 595 */ 596 public void mouseMoved(MouseEvent e) { 597 } 598 599 // ---- Caret methods --------------------------------- 600 601 /** 602 * Renders the caret as a vertical line. If this is reimplemented 603 * the damage method should also be reimplemented as it assumes the 604 * shape of the caret is a vertical line. Sets the caret color to 605 * the value returned by getCaretColor(). 606 * <p> 607 * If there are multiple text directions present in the associated 608 * document, a flag indicating the caret bias will be rendered. 609 * This will occur only if the associated document is a subclass 610 * of AbstractDocument and there are multiple bidi levels present 611 * in the bidi element structure (i.e. the text has multiple 612 * directions associated with it). 613 * 614 * @param g the graphics context 615 * @see #damage 616 */ 617 public void paint(Graphics g) { 618 if(isVisible()) { 619 try { 620 TextUI mapper = component.getUI(); 621 Rectangle r = mapper.modelToView(component, dot, dotBias); 622 623 if ((r == null) || ((r.width == 0) && (r.height == 0))) { 624 return; 625 } 626 if (width > 0 && height > 0 && 627 !this._contains(r.x, r.y, r.width, r.height)) { 628 // We seem to have gotten out of sync and no longer 629 // contain the right location, adjust accordingly. 630 Rectangle clip = g.getClipBounds(); 631 632 if (clip != null && !clip.contains(this)) { 633 // Clip doesn't contain the old location, force it 634 // to be repainted lest we leave a caret around. 635 repaint(); 636 } 637 // This will potentially cause a repaint of something 638 // we're already repainting, but without changing the 639 // semantics of damage we can't really get around this. 640 damage(r); 641 } 642 g.setColor(component.getCaretColor()); 643 int paintWidth = getCaretWidth(r.height); 644 r.x -= paintWidth >> 1; 645 g.fillRect(r.x, r.y, paintWidth, r.height); 646 647 // see if we should paint a flag to indicate the bias 648 // of the caret. 649 // PENDING(prinz) this should be done through 650 // protected methods so that alternative LAF 651 // will show bidi information. 652 Document doc = component.getDocument(); 653 if (doc instanceof AbstractDocument) { 654 Element bidi = ((AbstractDocument)doc).getBidiRootElement(); 655 if ((bidi != null) && (bidi.getElementCount() > 1)) { 656 // there are multiple directions present. 657 flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0); 658 flagYPoints[0] = r.y; 659 flagXPoints[1] = flagXPoints[0]; 660 flagYPoints[1] = flagYPoints[0] + 4; 661 flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4); 662 flagYPoints[2] = flagYPoints[0]; 663 g.fillPolygon(flagXPoints, flagYPoints, 3); 664 } 665 } 666 } catch (BadLocationException e) { 667 // can't render I guess 668 //System.err.println("Can't render cursor"); 669 } 670 } 671 } 672 673 /** 674 * Called when the UI is being installed into the 675 * interface of a JTextComponent. This can be used 676 * to gain access to the model that is being navigated 677 * by the implementation of this interface. Sets the dot 678 * and mark to 0, and establishes document, property change, 679 * focus, mouse, and mouse motion listeners. 680 * 681 * @param c the component 682 * @see Caret#install 683 */ 684 public void install(JTextComponent c) { 685 component = c; 686 Document doc = c.getDocument(); 687 dot = mark = 0; 688 dotLTR = markLTR = true; 689 dotBias = markBias = Position.Bias.Forward; 690 if (doc != null) { 691 doc.addDocumentListener(handler); 692 } 693 c.addPropertyChangeListener(handler); 694 c.addFocusListener(this); 695 c.addMouseListener(this); 696 c.addMouseMotionListener(this); 697 698 // if the component already has focus, it won't 699 // be notified. 700 if (component.hasFocus()) { 701 focusGained(null); 702 } 703 704 Number ratio = (Number) c.getClientProperty("caretAspectRatio"); 705 if (ratio != null) { 706 aspectRatio = ratio.floatValue(); 707 } else { 708 aspectRatio = -1; 709 } 710 711 Integer width = (Integer) c.getClientProperty("caretWidth"); 712 if (width != null) { 713 caretWidth = width.intValue(); 714 } else { 715 caretWidth = -1; 716 } 717 } 718 719 /** 720 * Called when the UI is being removed from the 721 * interface of a JTextComponent. This is used to 722 * unregister any listeners that were attached. 723 * 724 * @param c the component 725 * @see Caret#deinstall 726 */ 727 public void deinstall(JTextComponent c) { 728 c.removeMouseListener(this); 729 c.removeMouseMotionListener(this); 730 c.removeFocusListener(this); 731 c.removePropertyChangeListener(handler); 732 Document doc = c.getDocument(); 733 if (doc != null) { 734 doc.removeDocumentListener(handler); 735 } 736 synchronized(this) { 737 component = null; 738 } 739 if (flasher != null) { 740 flasher.stop(); 741 } 742 743 744 } 745 746 /** 747 * Adds a listener to track whenever the caret position has 748 * been changed. 749 * 750 * @param l the listener 751 * @see Caret#addChangeListener 752 */ 753 public void addChangeListener(ChangeListener l) { 754 listenerList.add(ChangeListener.class, l); 755 } 756 757 /** 758 * Removes a listener that was tracking caret position changes. 759 * 760 * @param l the listener 761 * @see Caret#removeChangeListener 762 */ 763 public void removeChangeListener(ChangeListener l) { 764 listenerList.remove(ChangeListener.class, l); 765 } 766 767 /** 768 * Returns an array of all the change listeners 769 * registered on this caret. 770 * 771 * @return all of this caret's <code>ChangeListener</code>s 772 * or an empty 773 * array if no change listeners are currently registered 774 * 775 * @see #addChangeListener 776 * @see #removeChangeListener 777 * 778 * @since 1.4 779 */ 780 public ChangeListener[] getChangeListeners() { 781 return listenerList.getListeners(ChangeListener.class); 782 } 783 784 /** 785 * Notifies all listeners that have registered interest for 786 * notification on this event type. The event instance 787 * is lazily created using the parameters passed into 788 * the fire method. The listener list is processed last to first. 789 * 790 * @see EventListenerList 791 */ 792 protected void fireStateChanged() { 793 // Guaranteed to return a non-null array 794 Object[] listeners = listenerList.getListenerList(); 795 // Process the listeners last to first, notifying 796 // those that are interested in this event 797 for (int i = listeners.length-2; i>=0; i-=2) { 798 if (listeners[i]==ChangeListener.class) { 799 // Lazily create the event: 800 if (changeEvent == null) 801 changeEvent = new ChangeEvent(this); 802 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 803 } 804 } 805 } 806 807 /** 808 * Returns an array of all the objects currently registered 809 * as <code><em>Foo</em>Listener</code>s 810 * upon this caret. 811 * <code><em>Foo</em>Listener</code>s are registered using the 812 * <code>add<em>Foo</em>Listener</code> method. 813 * 814 * <p> 815 * 816 * You can specify the <code>listenerType</code> argument 817 * with a class literal, 818 * such as 819 * <code><em>Foo</em>Listener.class</code>. 820 * For example, you can query a 821 * <code>DefaultCaret</code> <code>c</code> 822 * for its change listeners with the following code: 823 * 824 * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre> 825 * 826 * If no such listeners exist, this method returns an empty array. 827 * 828 * @param listenerType the type of listeners requested; this parameter 829 * should specify an interface that descends from 830 * <code>java.util.EventListener</code> 831 * @return an array of all objects registered as 832 * <code><em>Foo</em>Listener</code>s on this component, 833 * or an empty array if no such 834 * listeners have been added 835 * @exception ClassCastException if <code>listenerType</code> 836 * doesn't specify a class or interface that implements 837 * <code>java.util.EventListener</code> 838 * 839 * @see #getChangeListeners 840 * 841 * @since 1.3 842 */ 843 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 844 return listenerList.getListeners(listenerType); 845 } 846 847 /** 848 * Changes the selection visibility. 849 * 850 * @param vis the new visibility 851 */ 852 public void setSelectionVisible(boolean vis) { 853 if (vis != selectionVisible) { 854 selectionVisible = vis; 855 if (selectionVisible) { 856 // show 857 Highlighter h = component.getHighlighter(); 858 if ((dot != mark) && (h != null) && (selectionTag == null)) { 859 int p0 = Math.min(dot, mark); 860 int p1 = Math.max(dot, mark); 861 Highlighter.HighlightPainter p = getSelectionPainter(); 862 try { 863 selectionTag = h.addHighlight(p0, p1, p); 864 } catch (BadLocationException bl) { 865 selectionTag = null; 866 } 867 } 868 } else { 869 // hide 870 if (selectionTag != null) { 871 Highlighter h = component.getHighlighter(); 872 h.removeHighlight(selectionTag); 873 selectionTag = null; 874 } 875 } 876 } 877 } 878 879 /** 880 * Checks whether the current selection is visible. 881 * 882 * @return true if the selection is visible 883 */ 884 public boolean isSelectionVisible() { 885 return selectionVisible; 886 } 887 888 /** 889 * Determines if the caret is currently active. 890 * <p> 891 * This method returns whether or not the <code>Caret</code> 892 * is currently in a blinking state. It does not provide 893 * information as to whether it is currently blinked on or off. 894 * To determine if the caret is currently painted use the 895 * <code>isVisible</code> method. 896 * 897 * @return <code>true</code> if active else <code>false</code> 898 * @see #isVisible 899 * 900 * @since 1.5 901 */ 902 public boolean isActive() { 903 return active; 904 } 905 906 /** 907 * Indicates whether or not the caret is currently visible. As the 908 * caret flashes on and off the return value of this will change 909 * between true, when the caret is painted, and false, when the 910 * caret is not painted. <code>isActive</code> indicates whether 911 * or not the caret is in a blinking state, such that it <b>can</b> 912 * be visible, and <code>isVisible</code> indicates whether or not 913 * the caret <b>is</b> actually visible. 914 * <p> 915 * Subclasses that wish to render a different flashing caret 916 * should override paint and only paint the caret if this method 917 * returns true. 918 * 919 * @return true if visible else false 920 * @see Caret#isVisible 921 * @see #isActive 922 */ 923 public boolean isVisible() { 924 return visible; 925 } 926 927 /** 928 * Sets the caret visibility, and repaints the caret. 929 * It is important to understand the relationship between this method, 930 * <code>isVisible</code> and <code>isActive</code>. 931 * Calling this method with a value of <code>true</code> activates the 932 * caret blinking. Setting it to <code>false</code> turns it completely off. 933 * To determine whether the blinking is active, you should call 934 * <code>isActive</code>. In effect, <code>isActive</code> is an 935 * appropriate corresponding "getter" method for this one. 936 * <code>isVisible</code> can be used to fetch the current 937 * visibility status of the caret, meaning whether or not it is currently 938 * painted. This status will change as the caret blinks on and off. 939 * <p> 940 * Here's a list showing the potential return values of both 941 * <code>isActive</code> and <code>isVisible</code> 942 * after calling this method: 943 * <p> 944 * <b><code>setVisible(true)</code></b>: 945 * <ul> 946 * <li>isActive(): true</li> 947 * <li>isVisible(): true or false depending on whether 948 * or not the caret is blinked on or off</li> 949 * </ul> 950 * <p> 951 * <b><code>setVisible(false)</code></b>: 952 * <ul> 953 * <li>isActive(): false</li> 954 * <li>isVisible(): false</li> 955 * </ul> 956 * 957 * @param e the visibility specifier 958 * @see #isActive 959 * @see Caret#setVisible 960 */ 961 public void setVisible(boolean e) { 962 // focus lost notification can come in later after the 963 // caret has been deinstalled, in which case the component 964 // will be null. 965 active = e; 966 if (component != null) { 967 TextUI mapper = component.getUI(); 968 if (visible != e) { 969 visible = e; 970 // repaint the caret 971 try { 972 Rectangle loc = mapper.modelToView(component, dot,dotBias); 973 damage(loc); 974 } catch (BadLocationException badloc) { 975 // hmm... not legally positioned 976 } 977 } 978 } 979 if (flasher != null) { 980 if (visible) { 981 flasher.start(); 982 } else { 983 flasher.stop(); 984 } 985 } 986 } 987 988 /** 989 * Sets the caret blink rate. 990 * 991 * @param rate the rate in milliseconds, 0 to stop blinking 992 * @see Caret#setBlinkRate 993 */ 994 public void setBlinkRate(int rate) { 995 if (rate != 0) { 996 if (flasher == null) { 997 flasher = new Timer(rate, handler); 998 } 999 flasher.setDelay(rate); 1000 } else { 1001 if (flasher != null) { 1002 flasher.stop(); 1003 flasher.removeActionListener(handler); 1004 flasher = null; 1005 } 1006 } 1007 } 1008 1009 /** 1010 * Gets the caret blink rate. 1011 * 1012 * @return the delay in milliseconds. If this is 1013 * zero the caret will not blink. 1014 * @see Caret#getBlinkRate 1015 */ 1016 public int getBlinkRate() { 1017 return (flasher == null) ? 0 : flasher.getDelay(); 1018 } 1019 1020 /** 1021 * Fetches the current position of the caret. 1022 * 1023 * @return the position >= 0 1024 * @see Caret#getDot 1025 */ 1026 public int getDot() { 1027 return dot; 1028 } 1029 1030 /** 1031 * Fetches the current position of the mark. If there is a selection, 1032 * the dot and mark will not be the same. 1033 * 1034 * @return the position >= 0 1035 * @see Caret#getMark 1036 */ 1037 public int getMark() { 1038 return mark; 1039 } 1040 1041 /** 1042 * Sets the caret position and mark to the specified position, 1043 * with a forward bias. This implicitly sets the 1044 * selection range to zero. 1045 * 1046 * @param dot the position >= 0 1047 * @see #setDot(int, Position.Bias) 1048 * @see Caret#setDot 1049 */ 1050 public void setDot(int dot) { 1051 setDot(dot, Position.Bias.Forward); 1052 } 1053 1054 /** 1055 * Moves the caret position to the specified position, 1056 * with a forward bias. 1057 * 1058 * @param dot the position >= 0 1059 * @see #moveDot(int, javax.swing.text.Position.Bias) 1060 * @see Caret#moveDot 1061 */ 1062 public void moveDot(int dot) { 1063 moveDot(dot, Position.Bias.Forward); 1064 } 1065 1066 // ---- Bidi methods (we could put these in a subclass) 1067 1068 /** 1069 * Moves the caret position to the specified position, with the 1070 * specified bias. 1071 * 1072 * @param dot the position >= 0 1073 * @param dotBias the bias for this position, not <code>null</code> 1074 * @throws IllegalArgumentException if the bias is <code>null</code> 1075 * @see Caret#moveDot 1076 * @since 1.6 1077 */ 1078 public void moveDot(int dot, Position.Bias dotBias) { 1079 if (dotBias == null) { 1080 throw new IllegalArgumentException("null bias"); 1081 } 1082 1083 if (! component.isEnabled()) { 1084 // don't allow selection on disabled components. 1085 setDot(dot, dotBias); 1086 return; 1087 } 1088 if (dot != this.dot) { 1089 NavigationFilter filter = component.getNavigationFilter(); 1090 1091 if (filter != null) { 1092 filter.moveDot(getFilterBypass(), dot, dotBias); 1093 } 1094 else { 1095 handleMoveDot(dot, dotBias); 1096 } 1097 } 1098 } 1099 1100 void handleMoveDot(int dot, Position.Bias dotBias) { 1101 changeCaretPosition(dot, dotBias); 1102 1103 if (selectionVisible) { 1104 Highlighter h = component.getHighlighter(); 1105 if (h != null) { 1106 int p0 = Math.min(dot, mark); 1107 int p1 = Math.max(dot, mark); 1108 1109 // if p0 == p1 then there should be no highlight, remove it if necessary 1110 if (p0 == p1) { 1111 if (selectionTag != null) { 1112 h.removeHighlight(selectionTag); 1113 selectionTag = null; 1114 } 1115 // otherwise, change or add the highlight 1116 } else { 1117 try { 1118 if (selectionTag != null) { 1119 h.changeHighlight(selectionTag, p0, p1); 1120 } else { 1121 Highlighter.HighlightPainter p = getSelectionPainter(); 1122 selectionTag = h.addHighlight(p0, p1, p); 1123 } 1124 } catch (BadLocationException e) { 1125 throw new StateInvariantError("Bad caret position"); 1126 } 1127 } 1128 } 1129 } 1130 } 1131 1132 /** 1133 * Sets the caret position and mark to the specified position, with the 1134 * specified bias. This implicitly sets the selection range 1135 * to zero. 1136 * 1137 * @param dot the position >= 0 1138 * @param dotBias the bias for this position, not <code>null</code> 1139 * @throws IllegalArgumentException if the bias is <code>null</code> 1140 * @see Caret#setDot 1141 * @since 1.6 1142 */ 1143 public void setDot(int dot, Position.Bias dotBias) { 1144 if (dotBias == null) { 1145 throw new IllegalArgumentException("null bias"); 1146 } 1147 1148 NavigationFilter filter = component.getNavigationFilter(); 1149 1150 if (filter != null) { 1151 filter.setDot(getFilterBypass(), dot, dotBias); 1152 } 1153 else { 1154 handleSetDot(dot, dotBias); 1155 } 1156 } 1157 1158 void handleSetDot(int dot, Position.Bias dotBias) { 1159 // move dot, if it changed 1160 Document doc = component.getDocument(); 1161 if (doc != null) { 1162 dot = Math.min(dot, doc.getLength()); 1163 } 1164 dot = Math.max(dot, 0); 1165 1166 // The position (0,Backward) is out of range so disallow it. 1167 if( dot == 0 ) 1168 dotBias = Position.Bias.Forward; 1169 1170 mark = dot; 1171 if (this.dot != dot || this.dotBias != dotBias || 1172 selectionTag != null || forceCaretPositionChange) { 1173 changeCaretPosition(dot, dotBias); 1174 } 1175 this.markBias = this.dotBias; 1176 this.markLTR = dotLTR; 1177 Highlighter h = component.getHighlighter(); 1178 if ((h != null) && (selectionTag != null)) { 1179 h.removeHighlight(selectionTag); 1180 selectionTag = null; 1181 } 1182 } 1183 1184 /** 1185 * Returns the bias of the caret position. 1186 * 1187 * @return the bias of the caret position 1188 * @since 1.6 1189 */ 1190 public Position.Bias getDotBias() { 1191 return dotBias; 1192 } 1193 1194 /** 1195 * Returns the bias of the mark. 1196 * 1197 * @return the bias of the mark 1198 * @since 1.6 1199 */ 1200 public Position.Bias getMarkBias() { 1201 return markBias; 1202 } 1203 1204 boolean isDotLeftToRight() { 1205 return dotLTR; 1206 } 1207 1208 boolean isMarkLeftToRight() { 1209 return markLTR; 1210 } 1211 1212 boolean isPositionLTR(int position, Position.Bias bias) { 1213 Document doc = component.getDocument(); 1214 if(bias == Position.Bias.Backward && --position < 0) 1215 position = 0; 1216 return AbstractDocument.isLeftToRight(doc, position, position); 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 && component.hasFocus()) { 1331 Clipboard clip = getSystemSelection(); 1332 if (clip != null) { 1333 String selectedText; 1334 if (component instanceof JPasswordField 1335 && component.getClientProperty("JPasswordField.cutCopyAllowed") != 1336 Boolean.TRUE) { 1337 //fix for 4793761 1338 StringBuilder 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 StringBuilder(); 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 } else { 1504 Object property = UIManager.get("Caret.width"); 1505 if (property instanceof Integer) { 1506 return ((Integer) property).intValue(); 1507 } else { 1508 return 1; 1509 } 1510 } 1511 } 1512 1513 // --- serialization --------------------------------------------- 1514 1515 private void readObject(ObjectInputStream s) 1516 throws ClassNotFoundException, IOException 1517 { 1518 s.defaultReadObject(); 1519 handler = new Handler(); 1520 if (!s.readBoolean()) { 1521 dotBias = Position.Bias.Forward; 1522 } 1523 else { 1524 dotBias = Position.Bias.Backward; 1525 } 1526 if (!s.readBoolean()) { 1527 markBias = Position.Bias.Forward; 1528 } 1529 else { 1530 markBias = Position.Bias.Backward; 1531 } 1532 } 1533 1534 private void writeObject(ObjectOutputStream s) throws IOException { 1535 s.defaultWriteObject(); 1536 s.writeBoolean((dotBias == Position.Bias.Backward)); 1537 s.writeBoolean((markBias == Position.Bias.Backward)); 1538 } 1539 1540 // ---- member variables ------------------------------------------ 1541 1542 /** 1543 * The event listener list. 1544 */ 1545 protected EventListenerList listenerList = new EventListenerList(); 1546 1547 /** 1548 * The change event for the model. 1549 * Only one ChangeEvent is needed per model instance since the 1550 * event's only (read-only) state is the source property. The source 1551 * of events generated here is always "this". 1552 */ 1553 protected transient ChangeEvent changeEvent = null; 1554 1555 // package-private to avoid inner classes private member 1556 // access bug 1557 JTextComponent component; 1558 1559 int updatePolicy = UPDATE_WHEN_ON_EDT; 1560 boolean visible; 1561 boolean active; 1562 int dot; 1563 int mark; 1564 Object selectionTag; 1565 boolean selectionVisible; 1566 Timer flasher; 1567 Point magicCaretPosition; 1568 transient Position.Bias dotBias; 1569 transient Position.Bias markBias; 1570 boolean dotLTR; 1571 boolean markLTR; 1572 transient Handler handler = new Handler(); 1573 transient private int[] flagXPoints = new int[3]; 1574 transient private int[] flagYPoints = new int[3]; 1575 private transient NavigationFilter.FilterBypass filterBypass; 1576 static private transient Action selectWord = null; 1577 static private transient Action selectLine = null; 1578 /** 1579 * This is used to indicate if the caret currently owns the selection. 1580 * This is always false if the system does not support the system 1581 * clipboard. 1582 */ 1583 private boolean ownsSelection; 1584 1585 /** 1586 * If this is true, the location of the dot is updated regardless of 1587 * the current location. This is set in the DocumentListener 1588 * such that even if the model location of dot hasn't changed (perhaps do 1589 * to a forward delete) the visual location is updated. 1590 */ 1591 private boolean forceCaretPositionChange; 1592 1593 /** 1594 * Whether or not mouseReleased should adjust the caret and focus. 1595 * This flag is set by mousePressed if it wanted to adjust the caret 1596 * and focus but couldn't because of a possible DnD operation. 1597 */ 1598 private transient boolean shouldHandleRelease; 1599 1600 1601 /** 1602 * holds last MouseEvent which caused the word selection 1603 */ 1604 private transient MouseEvent selectedWordEvent = null; 1605 1606 /** 1607 * The width of the caret in pixels. 1608 */ 1609 private int caretWidth = -1; 1610 private float aspectRatio = -1; 1611 1612 class SafeScroller implements Runnable { 1613 1614 SafeScroller(Rectangle r) { 1615 this.r = r; 1616 } 1617 1618 public void run() { 1619 if (component != null) { 1620 component.scrollRectToVisible(r); 1621 } 1622 } 1623 1624 Rectangle r; 1625 } 1626 1627 1628 class Handler implements PropertyChangeListener, DocumentListener, ActionListener, ClipboardOwner { 1629 1630 // --- ActionListener methods ---------------------------------- 1631 1632 /** 1633 * Invoked when the blink timer fires. This is called 1634 * asynchronously. The simply changes the visibility 1635 * and repaints the rectangle that last bounded the caret. 1636 * 1637 * @param e the action event 1638 */ 1639 public void actionPerformed(ActionEvent e) { 1640 if (width == 0 || height == 0) { 1641 // setVisible(true) will cause a scroll, only do this if the 1642 // new location is really valid. 1643 if (component != null) { 1644 TextUI mapper = component.getUI(); 1645 try { 1646 Rectangle r = mapper.modelToView(component, dot, 1647 dotBias); 1648 if (r != null && r.width != 0 && r.height != 0) { 1649 damage(r); 1650 } 1651 } catch (BadLocationException ble) { 1652 } 1653 } 1654 } 1655 visible = !visible; 1656 repaint(); 1657 } 1658 1659 // --- DocumentListener methods -------------------------------- 1660 1661 /** 1662 * Updates the dot and mark if they were changed by 1663 * the insertion. 1664 * 1665 * @param e the document event 1666 * @see DocumentListener#insertUpdate 1667 */ 1668 public void insertUpdate(DocumentEvent e) { 1669 if (getUpdatePolicy() == NEVER_UPDATE || 1670 (getUpdatePolicy() == UPDATE_WHEN_ON_EDT && 1671 !SwingUtilities.isEventDispatchThread())) { 1672 1673 if ((e.getOffset() <= dot || e.getOffset() <= mark) 1674 && selectionTag != null) { 1675 try { 1676 component.getHighlighter().changeHighlight(selectionTag, 1677 Math.min(dot, mark), Math.max(dot, mark)); 1678 } catch (BadLocationException e1) { 1679 e1.printStackTrace(); 1680 } 1681 } 1682 return; 1683 } 1684 int offset = e.getOffset(); 1685 int length = e.getLength(); 1686 int newDot = dot; 1687 short changed = 0; 1688 1689 if (e instanceof AbstractDocument.UndoRedoDocumentEvent) { 1690 setDot(offset + length); 1691 return; 1692 } 1693 if (newDot >= offset) { 1694 newDot += length; 1695 changed |= 1; 1696 } 1697 int newMark = mark; 1698 if (newMark >= offset) { 1699 newMark += length; 1700 changed |= 2; 1701 } 1702 1703 if (changed != 0) { 1704 Position.Bias dotBias = DefaultCaret.this.dotBias; 1705 if (dot == offset) { 1706 Document doc = component.getDocument(); 1707 boolean isNewline; 1708 try { 1709 Segment s = new Segment(); 1710 doc.getText(newDot - 1, 1, s); 1711 isNewline = (s.count > 0 && 1712 s.array[s.offset] == '\n'); 1713 } catch (BadLocationException ble) { 1714 isNewline = false; 1715 } 1716 if (isNewline) { 1717 dotBias = Position.Bias.Forward; 1718 } else { 1719 dotBias = Position.Bias.Backward; 1720 } 1721 } 1722 if (newMark == newDot) { 1723 setDot(newDot, dotBias); 1724 ensureValidPosition(); 1725 } 1726 else { 1727 setDot(newMark, markBias); 1728 if (getDot() == newMark) { 1729 // Due this test in case the filter vetoed the 1730 // change in which case this probably won't be 1731 // valid either. 1732 moveDot(newDot, dotBias); 1733 } 1734 ensureValidPosition(); 1735 } 1736 } 1737 } 1738 1739 /** 1740 * Updates the dot and mark if they were changed 1741 * by the removal. 1742 * 1743 * @param e the document event 1744 * @see DocumentListener#removeUpdate 1745 */ 1746 public void removeUpdate(DocumentEvent e) { 1747 if (getUpdatePolicy() == NEVER_UPDATE || 1748 (getUpdatePolicy() == UPDATE_WHEN_ON_EDT && 1749 !SwingUtilities.isEventDispatchThread())) { 1750 1751 int length = component.getDocument().getLength(); 1752 dot = Math.min(dot, length); 1753 mark = Math.min(mark, length); 1754 if ((e.getOffset() < dot || e.getOffset() < mark) 1755 && selectionTag != null) { 1756 try { 1757 component.getHighlighter().changeHighlight(selectionTag, 1758 Math.min(dot, mark), Math.max(dot, mark)); 1759 } catch (BadLocationException e1) { 1760 e1.printStackTrace(); 1761 } 1762 } 1763 return; 1764 } 1765 int offs0 = e.getOffset(); 1766 int offs1 = offs0 + e.getLength(); 1767 int newDot = dot; 1768 boolean adjustDotBias = false; 1769 int newMark = mark; 1770 boolean adjustMarkBias = false; 1771 1772 if(e instanceof AbstractDocument.UndoRedoDocumentEvent) { 1773 setDot(offs0); 1774 return; 1775 } 1776 if (newDot >= offs1) { 1777 newDot -= (offs1 - offs0); 1778 if(newDot == offs1) { 1779 adjustDotBias = true; 1780 } 1781 } else if (newDot >= offs0) { 1782 newDot = offs0; 1783 adjustDotBias = true; 1784 } 1785 if (newMark >= offs1) { 1786 newMark -= (offs1 - offs0); 1787 if(newMark == offs1) { 1788 adjustMarkBias = true; 1789 } 1790 } else if (newMark >= offs0) { 1791 newMark = offs0; 1792 adjustMarkBias = true; 1793 } 1794 if (newMark == newDot) { 1795 forceCaretPositionChange = true; 1796 try { 1797 setDot(newDot, guessBiasForOffset(newDot, dotBias, 1798 dotLTR)); 1799 } finally { 1800 forceCaretPositionChange = false; 1801 } 1802 ensureValidPosition(); 1803 } else { 1804 Position.Bias dotBias = DefaultCaret.this.dotBias; 1805 Position.Bias markBias = DefaultCaret.this.markBias; 1806 if(adjustDotBias) { 1807 dotBias = guessBiasForOffset(newDot, dotBias, dotLTR); 1808 } 1809 if(adjustMarkBias) { 1810 markBias = guessBiasForOffset(mark, markBias, markLTR); 1811 } 1812 setDot(newMark, markBias); 1813 if (getDot() == newMark) { 1814 // Due this test in case the filter vetoed the change 1815 // in which case this probably won't be valid either. 1816 moveDot(newDot, dotBias); 1817 } 1818 ensureValidPosition(); 1819 } 1820 } 1821 1822 /** 1823 * Gives notification that an attribute or set of attributes changed. 1824 * 1825 * @param e the document event 1826 * @see DocumentListener#changedUpdate 1827 */ 1828 public void changedUpdate(DocumentEvent e) { 1829 if (getUpdatePolicy() == NEVER_UPDATE || 1830 (getUpdatePolicy() == UPDATE_WHEN_ON_EDT && 1831 !SwingUtilities.isEventDispatchThread())) { 1832 return; 1833 } 1834 if(e instanceof AbstractDocument.UndoRedoDocumentEvent) { 1835 setDot(e.getOffset() + e.getLength()); 1836 } 1837 } 1838 1839 // --- PropertyChangeListener methods ----------------------- 1840 1841 /** 1842 * This method gets called when a bound property is changed. 1843 * We are looking for document changes on the editor. 1844 */ 1845 public void propertyChange(PropertyChangeEvent evt) { 1846 Object oldValue = evt.getOldValue(); 1847 Object newValue = evt.getNewValue(); 1848 if ((oldValue instanceof Document) || (newValue instanceof Document)) { 1849 setDot(0); 1850 if (oldValue != null) { 1851 ((Document)oldValue).removeDocumentListener(this); 1852 } 1853 if (newValue != null) { 1854 ((Document)newValue).addDocumentListener(this); 1855 } 1856 } else if("enabled".equals(evt.getPropertyName())) { 1857 Boolean enabled = (Boolean) evt.getNewValue(); 1858 if(component.isFocusOwner()) { 1859 if(enabled == Boolean.TRUE) { 1860 if(component.isEditable()) { 1861 setVisible(true); 1862 } 1863 setSelectionVisible(true); 1864 } else { 1865 setVisible(false); 1866 setSelectionVisible(false); 1867 } 1868 } 1869 } else if("caretWidth".equals(evt.getPropertyName())) { 1870 Integer newWidth = (Integer) evt.getNewValue(); 1871 if (newWidth != null) { 1872 caretWidth = newWidth.intValue(); 1873 } else { 1874 caretWidth = -1; 1875 } 1876 repaint(); 1877 } else if("caretAspectRatio".equals(evt.getPropertyName())) { 1878 Number newRatio = (Number) evt.getNewValue(); 1879 if (newRatio != null) { 1880 aspectRatio = newRatio.floatValue(); 1881 } else { 1882 aspectRatio = -1; 1883 } 1884 repaint(); 1885 } 1886 } 1887 1888 1889 // 1890 // ClipboardOwner 1891 // 1892 /** 1893 * Toggles the visibility of the selection when ownership is lost. 1894 */ 1895 public void lostOwnership(Clipboard clipboard, 1896 Transferable contents) { 1897 if (ownsSelection) { 1898 ownsSelection = false; 1899 if (component != null && !component.hasFocus()) { 1900 setSelectionVisible(false); 1901 } 1902 } 1903 } 1904 } 1905 1906 1907 private class DefaultFilterBypass extends NavigationFilter.FilterBypass { 1908 public Caret getCaret() { 1909 return DefaultCaret.this; 1910 } 1911 1912 public void setDot(int dot, Position.Bias bias) { 1913 handleSetDot(dot, bias); 1914 } 1915 1916 public void moveDot(int dot, Position.Bias bias) { 1917 handleMoveDot(dot, bias); 1918 } 1919 } 1920 }