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