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