1 /* DefaultCaret.java -- 2 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 package javax.swing.text; 39 40 import java.awt.Graphics; 41 import java.awt.Point; 42 import java.awt.Rectangle; 43 import java.awt.event.ActionEvent; 44 import java.awt.event.ActionListener; 45 import java.awt.event.FocusEvent; 46 import java.awt.event.FocusListener; 47 import java.awt.event.MouseEvent; 48 import java.awt.event.MouseListener; 49 import java.awt.event.MouseMotionListener; 50 import java.beans.PropertyChangeEvent; 51 import java.beans.PropertyChangeListener; 52 import java.util.EventListener; 53 54 import javax.swing.JComponent; 55 import javax.swing.SwingUtilities; 56 import javax.swing.Timer; 57 import javax.swing.event.ChangeEvent; 58 import javax.swing.event.ChangeListener; 59 import javax.swing.event.DocumentEvent; 60 import javax.swing.event.DocumentListener; 61 import javax.swing.event.EventListenerList; 62 import javax.swing.text.Position.Bias; 63 64 /** 65 * The default implementation of the {@link Caret} interface. 66 * 67 * @author original author unknown 68 * @author Roman Kennke (roman@kennke.org) 69 */ 70 public class DefaultCaret extends Rectangle 71 implements Caret, FocusListener, MouseListener, MouseMotionListener 72 { 73 74 /** A text component in the current VM which currently has a 75 * text selection or <code>null</code>. 76 */ 77 static JTextComponent componentWithSelection; 78 79 /** An implementation of NavigationFilter.FilterBypass which delegates 80 * to the corresponding methods of the <code>DefaultCaret</code>. 81 * 82 * @author Robert Schuster (robertschuster@fsfe.org) 83 */ 84 class Bypass extends NavigationFilter.FilterBypass 85 { 86 getCaret()87 public Caret getCaret() 88 { 89 return DefaultCaret.this; 90 } 91 moveDot(int dot, Bias bias)92 public void moveDot(int dot, Bias bias) 93 { 94 DefaultCaret.this.moveDotImpl(dot); 95 } 96 setDot(int dot, Bias bias)97 public void setDot(int dot, Bias bias) 98 { 99 DefaultCaret.this.setDotImpl(dot); 100 } 101 102 } 103 104 /** 105 * Controls the blinking of the caret. 106 * 107 * @author Roman Kennke (kennke@aicas.com) 108 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) 109 */ 110 private class BlinkTimerListener implements ActionListener 111 { 112 /** 113 * Forces the next event to be ignored. The next event should be ignored 114 * if we force the caret to appear. We do not know how long will it take 115 * to fire the comming event; this may be near immediately. Better to leave 116 * the caret visible one iteration longer. 117 */ 118 boolean ignoreNextEvent; 119 120 /** 121 * Receives notification when the blink timer fires and updates the visible 122 * state of the caret. 123 * 124 * @param event the action event 125 */ actionPerformed(ActionEvent event)126 public void actionPerformed(ActionEvent event) 127 { 128 if (ignoreNextEvent) 129 ignoreNextEvent = false; 130 else 131 { 132 visible = !visible; 133 repaint(); 134 } 135 } 136 } 137 138 /** 139 * Listens for changes in the text component's document and updates the 140 * caret accordingly. 141 * 142 * @author Roman Kennke (kennke@aicas.com) 143 */ 144 private class DocumentHandler implements DocumentListener 145 { 146 /** 147 * Receives notification that some text attributes have changed. No action 148 * is taken here. 149 * 150 * @param event the document event 151 */ changedUpdate(DocumentEvent event)152 public void changedUpdate(DocumentEvent event) 153 { 154 // Nothing to do here. 155 } 156 157 /** 158 * Receives notification that some text has been inserted from the text 159 * component. The caret is moved forward accordingly. 160 * 161 * @param event the document event 162 */ insertUpdate(DocumentEvent event)163 public void insertUpdate(DocumentEvent event) 164 { 165 if (policy == ALWAYS_UPDATE || 166 (SwingUtilities.isEventDispatchThread() && 167 policy == UPDATE_WHEN_ON_EDT)) 168 { 169 int dot = getDot(); 170 setDot(dot + event.getLength()); 171 } 172 } 173 174 /** 175 * Receives notification that some text has been removed into the text 176 * component. The caret is moved backwards accordingly. 177 * 178 * @param event the document event 179 */ removeUpdate(DocumentEvent event)180 public void removeUpdate(DocumentEvent event) 181 { 182 if (policy == ALWAYS_UPDATE 183 || (SwingUtilities.isEventDispatchThread() 184 && policy == UPDATE_WHEN_ON_EDT)) 185 { 186 int dot = getDot(); 187 setDot(dot - event.getLength()); 188 } 189 else if (policy == NEVER_UPDATE 190 || (! SwingUtilities.isEventDispatchThread() 191 && policy == UPDATE_WHEN_ON_EDT)) 192 { 193 int docLength = event.getDocument().getLength(); 194 if (getDot() > docLength) 195 setDot(docLength); 196 } 197 } 198 } 199 200 /** 201 * Listens for property changes on the text document. This is used to add and 202 * remove our document listener, if the document of the text component has 203 * changed. 204 * 205 * @author Roman Kennke (kennke@aicas.com) 206 */ 207 private class PropertyChangeHandler implements PropertyChangeListener 208 { 209 210 /** 211 * Receives notification when a property has changed on the text component. 212 * This adds/removes our document listener from the text component's 213 * document when the document changes. 214 * 215 * @param e the property change event 216 */ propertyChange(PropertyChangeEvent e)217 public void propertyChange(PropertyChangeEvent e) 218 { 219 String name = e.getPropertyName(); 220 221 if (name.equals("document")) 222 { 223 Document oldDoc = (Document) e.getOldValue(); 224 if (oldDoc != null) 225 oldDoc.removeDocumentListener(documentListener); 226 227 Document newDoc = (Document) e.getNewValue(); 228 if (newDoc != null) 229 newDoc.addDocumentListener(documentListener); 230 } 231 else if (name.equals("editable")) 232 { 233 active = (((Boolean) e.getNewValue()).booleanValue() 234 && textComponent.isEnabled()); 235 } 236 else if (name.equals("enabled")) 237 { 238 active = (((Boolean) e.getNewValue()).booleanValue() 239 && textComponent.isEditable()); 240 } 241 242 } 243 244 } 245 246 /** The serialization UID (compatible with JDK1.5). */ 247 private static final long serialVersionUID = 4325555698756477346L; 248 249 /** 250 * Indicates the Caret position should always be updated after Document 251 * changes even if the updates are not performed on the Event Dispatching 252 * thread. 253 * 254 * @since 1.5 255 */ 256 public static final int ALWAYS_UPDATE = 2; 257 258 /** 259 * Indicates the Caret position should not be changed unless the Document 260 * length becomes less than the Caret position, in which case the Caret 261 * is moved to the end of the Document. 262 * 263 * @since 1.5 264 */ 265 public static final int NEVER_UPDATE = 1; 266 267 /** 268 * Indicates the Caret position should be updated only if Document changes 269 * are made on the Event Dispatcher thread. 270 * 271 * @since 1.5 272 */ 273 public static final int UPDATE_WHEN_ON_EDT = 0; 274 275 /** Keeps track of the current update policy **/ 276 int policy = UPDATE_WHEN_ON_EDT; 277 278 /** 279 * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}. 280 */ 281 protected ChangeEvent changeEvent = new ChangeEvent(this); 282 283 /** 284 * Stores all registered event listeners. 285 */ 286 protected EventListenerList listenerList = new EventListenerList(); 287 288 /** 289 * Our document listener. 290 */ 291 DocumentListener documentListener; 292 293 /** 294 * Our property listener. 295 */ 296 PropertyChangeListener propertyChangeListener; 297 298 /** 299 * The text component in which this caret is installed. 300 * 301 * (Package private to avoid synthetic accessor method.) 302 */ 303 JTextComponent textComponent; 304 305 /** 306 * Indicates if the selection should be visible or not. 307 */ 308 private boolean selectionVisible = true; 309 310 /** 311 * The blink rate of this <code>Caret</code>. 312 */ 313 private int blinkRate = 500; 314 315 /** 316 * The current dot position. 317 */ 318 private int dot = 0; 319 320 /** 321 * The current mark position. 322 */ 323 private int mark = 0; 324 325 /** 326 * The current visual caret position. 327 */ 328 private Point magicCaretPosition = null; 329 330 /** 331 * Indicates if this <code>Caret</code> is currently visible or not. This is 332 * package private to avoid an accessor method. 333 */ 334 boolean visible = false; 335 336 /** Indicates whether the text component where the caret is installed is 337 * editable and enabled. If either of these properties is <code>false</code> 338 * the caret is not drawn. 339 */ 340 boolean active = true; 341 342 /** 343 * The current highlight entry. 344 */ 345 private Object highlightEntry; 346 347 private Timer blinkTimer; 348 349 private BlinkTimerListener blinkListener; 350 351 /** 352 * A <code>NavigationFilter.FilterBypass</code> instance which 353 * is provided to the a <code>NavigationFilter</code> to 354 * unconditionally set or move the caret. 355 */ 356 NavigationFilter.FilterBypass bypass; 357 358 /** 359 * Creates a new <code>DefaultCaret</code> instance. 360 */ DefaultCaret()361 public DefaultCaret() 362 { 363 // Nothing to do here. 364 } 365 366 /** Returns the caret's <code>NavigationFilter.FilterBypass</code> instance 367 * and creates it if it does not yet exist. 368 * 369 * @return The caret's <code>NavigationFilter.FilterBypass</code> instance. 370 */ getBypass()371 private NavigationFilter.FilterBypass getBypass() 372 { 373 return (bypass == null) ? bypass = new Bypass() : bypass; 374 } 375 376 /** 377 * Sets the Caret update policy. 378 * 379 * @param policy the new policy. Valid values are: 380 * ALWAYS_UPDATE: always update the Caret position, even when Document 381 * updates don't occur on the Event Dispatcher thread. 382 * NEVER_UPDATE: don't update the Caret position unless the Document 383 * length becomes less than the Caret position (then update the 384 * Caret to the end of the Document). 385 * UPDATE_WHEN_ON_EDT: update the Caret position when the 386 * Document updates occur on the Event Dispatcher thread. This is the 387 * default. 388 * 389 * @since 1.5 390 * @throws IllegalArgumentException if policy is not one of the above. 391 */ setUpdatePolicy(int policy)392 public void setUpdatePolicy (int policy) 393 { 394 if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE 395 && policy != UPDATE_WHEN_ON_EDT) 396 throw new 397 IllegalArgumentException 398 ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT"); 399 this.policy = policy; 400 } 401 402 /** 403 * Gets the caret update policy. 404 * 405 * @return the caret update policy. 406 * @since 1.5 407 */ getUpdatePolicy()408 public int getUpdatePolicy () 409 { 410 return policy; 411 } 412 413 /** 414 * Moves the caret position when the mouse is dragged over the text 415 * component, modifying the selectiony. 416 * 417 * <p>When the text component where the caret is installed is disabled, 418 * the selection is not change but you can still scroll the text and 419 * update the caret's location.</p> 420 * 421 * @param event the <code>MouseEvent</code> describing the drag operation 422 */ mouseDragged(MouseEvent event)423 public void mouseDragged(MouseEvent event) 424 { 425 if (event.getButton() == MouseEvent.BUTTON1) 426 { 427 if (textComponent.isEnabled()) 428 moveCaret(event); 429 else 430 positionCaret(event); 431 } 432 } 433 434 /** 435 * Indicates a mouse movement over the text component. Does nothing here. 436 * 437 * @param event the <code>MouseEvent</code> describing the mouse operation 438 */ mouseMoved(MouseEvent event)439 public void mouseMoved(MouseEvent event) 440 { 441 // Nothing to do here. 442 } 443 444 /** 445 * When the click is received from Button 1 then the following actions 446 * are performed here: 447 * 448 * <ul> 449 * <li>If we receive a double click, the caret position (dot) is set 450 * to the position associated to the mouse click and the word at 451 * this location is selected. If there is no word at the pointer 452 * the gap is selected instead.</li> 453 * <li>If we receive a triple click, the caret position (dot) is set 454 * to the position associated to the mouse click and the line at 455 * this location is selected.</li> 456 * </ul> 457 * 458 * @param event the <code>MouseEvent</code> describing the click operation 459 */ mouseClicked(MouseEvent event)460 public void mouseClicked(MouseEvent event) 461 { 462 // Do not modify selection if component is disabled. 463 if (!textComponent.isEnabled()) 464 return; 465 466 int count = event.getClickCount(); 467 468 if (event.getButton() == MouseEvent.BUTTON1 && count >= 2) 469 { 470 int newDot = getComponent().viewToModel(event.getPoint()); 471 JTextComponent t = getComponent(); 472 473 try 474 { 475 if (count == 3) 476 { 477 setDot(Utilities.getRowStart(t, newDot)); 478 moveDot( Utilities.getRowEnd(t, newDot)); 479 } 480 else 481 { 482 int wordStart = Utilities.getWordStart(t, newDot); 483 484 // When the mouse points at the offset of the first character 485 // in a word Utilities().getPreviousWord will not return that 486 // word but we want to select that. We have to use 487 // Utilities.getWordStart() to get it. 488 if (newDot == wordStart) 489 { 490 setDot(wordStart); 491 moveDot(Utilities.getWordEnd(t, wordStart)); 492 } 493 else 494 { 495 int nextWord = Utilities.getNextWord(t, newDot); 496 int previousWord = Utilities.getPreviousWord(t, newDot); 497 int previousWordEnd = Utilities.getWordEnd(t, previousWord); 498 499 // If the user clicked in the space between two words, 500 // then select the space. 501 if (newDot >= previousWordEnd && newDot <= nextWord) 502 { 503 setDot(previousWordEnd); 504 moveDot(nextWord); 505 } 506 // Otherwise select the word under the mouse pointer. 507 else 508 { 509 setDot(previousWord); 510 moveDot(previousWordEnd); 511 } 512 } 513 } 514 } 515 catch(BadLocationException ble) 516 { 517 // TODO: Swallowing ok here? 518 } 519 } 520 521 } 522 523 /** 524 * Indicates that the mouse has entered the text component. Nothing is done 525 * here. 526 * 527 * @param event the <code>MouseEvent</code> describing the mouse operation 528 */ mouseEntered(MouseEvent event)529 public void mouseEntered(MouseEvent event) 530 { 531 // Nothing to do here. 532 } 533 534 /** 535 * Indicates that the mouse has exited the text component. Nothing is done 536 * here. 537 * 538 * @param event the <code>MouseEvent</code> describing the mouse operation 539 */ mouseExited(MouseEvent event)540 public void mouseExited(MouseEvent event) 541 { 542 // Nothing to do here. 543 } 544 545 /** 546 * If the button 1 is pressed, the caret position is updated to the 547 * position of the mouse click and the text component requests the input 548 * focus if it is enabled. If the SHIFT key is held down, the caret will 549 * be moved, which might select the text between the old and new location. 550 * 551 * @param event the <code>MouseEvent</code> describing the press operation 552 */ mousePressed(MouseEvent event)553 public void mousePressed(MouseEvent event) 554 { 555 556 // The implementation assumes that consuming the event makes the AWT event 557 // mechanism forget about this event instance and not transfer focus. 558 // By observing how the RI reacts the following behavior has been 559 // implemented (in regard to text components): 560 // - a left-click moves the caret 561 // - a left-click when shift is held down expands the selection 562 // - a right-click or click with any additional mouse button 563 // on a text component is ignored 564 // - a middle-click positions the caret and pastes the clipboard 565 // contents. 566 // - a middle-click when shift is held down is ignored 567 568 if (SwingUtilities.isLeftMouseButton(event)) 569 { 570 // Handle the caret. 571 if (event.isShiftDown() && getDot() != -1) 572 { 573 moveCaret(event); 574 } 575 else 576 { 577 positionCaret(event); 578 } 579 580 // Handle the focus. 581 if (textComponent != null && textComponent.isEnabled() 582 && textComponent.isRequestFocusEnabled()) 583 { 584 textComponent.requestFocus(); 585 } 586 587 // TODO: Handle double click for selecting words. 588 } 589 else if(event.getButton() == MouseEvent.BUTTON2) 590 { 591 // Special handling for X11-style pasting. 592 if (! event.isShiftDown()) 593 { 594 positionCaret(event); 595 textComponent.paste(); 596 } 597 } 598 } 599 600 /** 601 * Indicates that a mouse button has been released on the text component. 602 * Nothing is done here. 603 * 604 * @param event the <code>MouseEvent</code> describing the mouse operation 605 */ mouseReleased(MouseEvent event)606 public void mouseReleased(MouseEvent event) 607 { 608 // Nothing to do here. 609 } 610 611 /** 612 * Sets the caret to <code>visible</code> if the text component is editable. 613 * 614 * @param event the <code>FocusEvent</code> 615 */ focusGained(FocusEvent event)616 public void focusGained(FocusEvent event) 617 { 618 if (textComponent.isEditable()) 619 { 620 setVisible(true); 621 updateTimerStatus(); 622 } 623 } 624 625 /** 626 * Sets the caret to <code>invisible</code>. 627 * 628 * @param event the <code>FocusEvent</code> 629 */ focusLost(FocusEvent event)630 public void focusLost(FocusEvent event) 631 { 632 if (textComponent.isEditable() && event.isTemporary() == false) 633 { 634 setVisible(false); 635 636 // Stop the blinker, if running. 637 if (blinkTimer != null && blinkTimer.isRunning()) 638 blinkTimer.stop(); 639 } 640 } 641 642 /** 643 * Install (if not present) and start the timer, if the caret must blink. The 644 * caret does not blink if it is invisible, or the component is disabled or 645 * not editable. 646 */ updateTimerStatus()647 private void updateTimerStatus() 648 { 649 if (textComponent.isEnabled() && textComponent.isEditable()) 650 { 651 if (blinkTimer == null) 652 initBlinkTimer(); 653 if (!blinkTimer.isRunning()) 654 blinkTimer.start(); 655 } 656 else 657 { 658 if (blinkTimer != null) 659 blinkTimer.stop(); 660 } 661 } 662 663 /** 664 * Moves the caret to the position specified in the <code>MouseEvent</code>. 665 * This will cause a selection if the dot and mark are different. 666 * 667 * @param event the <code>MouseEvent</code> from which to fetch the position 668 */ moveCaret(MouseEvent event)669 protected void moveCaret(MouseEvent event) 670 { 671 int newDot = getComponent().viewToModel(event.getPoint()); 672 moveDot(newDot); 673 } 674 675 /** 676 * Repositions the caret to the position specified in the 677 * <code>MouseEvent</code>. 678 * 679 * @param event the <code>MouseEvent</code> from which to fetch the position 680 */ positionCaret(MouseEvent event)681 protected void positionCaret(MouseEvent event) 682 { 683 int newDot = getComponent().viewToModel(event.getPoint()); 684 setDot(newDot); 685 } 686 687 /** 688 * Deinstalls this <code>Caret</code> from the specified 689 * <code>JTextComponent</code>. This removes any listeners that have been 690 * registered by this <code>Caret</code>. 691 * 692 * @param c the text component from which to install this caret 693 */ deinstall(JTextComponent c)694 public void deinstall(JTextComponent c) 695 { 696 textComponent.removeFocusListener(this); 697 textComponent.removeMouseListener(this); 698 textComponent.removeMouseMotionListener(this); 699 textComponent.getDocument().removeDocumentListener(documentListener); 700 documentListener = null; 701 textComponent.removePropertyChangeListener(propertyChangeListener); 702 propertyChangeListener = null; 703 textComponent = null; 704 705 // Deinstall blink timer if present. 706 if (blinkTimer != null) 707 blinkTimer.stop(); 708 blinkTimer = null; 709 } 710 711 /** 712 * Installs this <code>Caret</code> on the specified 713 * <code>JTextComponent</code>. This registers a couple of listeners 714 * on the text component. 715 * 716 * @param c the text component on which to install this caret 717 */ install(JTextComponent c)718 public void install(JTextComponent c) 719 { 720 textComponent = c; 721 textComponent.addFocusListener(this); 722 textComponent.addMouseListener(this); 723 textComponent.addMouseMotionListener(this); 724 propertyChangeListener = new PropertyChangeHandler(); 725 textComponent.addPropertyChangeListener(propertyChangeListener); 726 documentListener = new DocumentHandler(); 727 728 Document doc = textComponent.getDocument(); 729 if (doc != null) 730 doc.addDocumentListener(documentListener); 731 732 active = textComponent.isEditable() && textComponent.isEnabled(); 733 734 repaint(); 735 } 736 737 /** 738 * Sets the current visual position of this <code>Caret</code>. 739 * 740 * @param p the Point to use for the saved location. May be <code>null</code> 741 * to indicate that there is no visual location 742 */ setMagicCaretPosition(Point p)743 public void setMagicCaretPosition(Point p) 744 { 745 magicCaretPosition = p; 746 } 747 748 /** 749 * Returns the current visual position of this <code>Caret</code>. 750 * 751 * @return the current visual position of this <code>Caret</code> 752 * 753 * @see #setMagicCaretPosition 754 */ getMagicCaretPosition()755 public Point getMagicCaretPosition() 756 { 757 return magicCaretPosition; 758 } 759 760 /** 761 * Returns the current position of the <code>mark</code>. The 762 * <code>mark</code> marks the location in the <code>Document</code> that 763 * is the end of a selection. If there is no selection, the <code>mark</code> 764 * is the same as the <code>dot</code>. 765 * 766 * @return the current position of the mark 767 */ getMark()768 public int getMark() 769 { 770 return mark; 771 } 772 clearHighlight()773 private void clearHighlight() 774 { 775 Highlighter highlighter = textComponent.getHighlighter(); 776 777 if (highlighter == null) 778 return; 779 780 if (selectionVisible) 781 { 782 try 783 { 784 if (highlightEntry != null) 785 highlighter.changeHighlight(highlightEntry, 0, 0); 786 787 // Free the global variable which stores the text component with an active 788 // selection. 789 if (componentWithSelection == textComponent) 790 componentWithSelection = null; 791 } 792 catch (BadLocationException e) 793 { 794 // This should never happen. 795 throw new InternalError(); 796 } 797 } 798 else 799 { 800 if (highlightEntry != null) 801 { 802 highlighter.removeHighlight(highlightEntry); 803 highlightEntry = null; 804 } 805 } 806 } 807 handleHighlight()808 private void handleHighlight() 809 { 810 Highlighter highlighter = textComponent.getHighlighter(); 811 812 if (highlighter == null) 813 return; 814 815 int p0 = Math.min(dot, mark); 816 int p1 = Math.max(dot, mark); 817 818 if (selectionVisible) 819 { 820 try 821 { 822 if (highlightEntry == null) 823 highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter()); 824 else 825 highlighter.changeHighlight(highlightEntry, p0, p1); 826 827 // If another component currently has a text selection clear that selection 828 // first. 829 if (componentWithSelection != null) 830 if (componentWithSelection != textComponent) 831 { 832 Caret c = componentWithSelection.getCaret(); 833 c.setDot(c.getDot()); 834 } 835 componentWithSelection = textComponent; 836 837 } 838 catch (BadLocationException e) 839 { 840 // This should never happen. 841 throw new InternalError(); 842 } 843 } 844 else 845 { 846 if (highlightEntry != null) 847 { 848 highlighter.removeHighlight(highlightEntry); 849 highlightEntry = null; 850 } 851 } 852 } 853 854 /** 855 * Sets the visiblity state of the selection. 856 * 857 * @param v <code>true</code> if the selection should be visible, 858 * <code>false</code> otherwise 859 */ setSelectionVisible(boolean v)860 public void setSelectionVisible(boolean v) 861 { 862 if (selectionVisible == v) 863 return; 864 865 selectionVisible = v; 866 handleHighlight(); 867 repaint(); 868 } 869 870 /** 871 * Returns <code>true</code> if the selection is currently visible, 872 * <code>false</code> otherwise. 873 * 874 * @return <code>true</code> if the selection is currently visible, 875 * <code>false</code> otherwise 876 */ isSelectionVisible()877 public boolean isSelectionVisible() 878 { 879 return selectionVisible; 880 } 881 882 /** 883 * Causes the <code>Caret</code> to repaint itself. 884 */ repaint()885 protected final void repaint() 886 { 887 getComponent().repaint(x, y, width, height); 888 } 889 890 /** 891 * Paints this <code>Caret</code> using the specified <code>Graphics</code> 892 * context. 893 * 894 * @param g the graphics context to use 895 */ paint(Graphics g)896 public void paint(Graphics g) 897 { 898 JTextComponent comp = getComponent(); 899 if (comp == null) 900 return; 901 902 // Make sure the dot has a sane position. 903 dot = Math.min(dot, textComponent.getDocument().getLength()); 904 dot = Math.max(dot, 0); 905 906 Rectangle rect = null; 907 908 try 909 { 910 rect = textComponent.modelToView(dot); 911 } 912 catch (BadLocationException e) 913 { 914 // Let's ignore that. This shouldn't really occur. But if it 915 // does (it seems that this happens when the model is mutating), 916 // it causes no real damage. Uncomment this for debugging. 917 // e.printStackTrace(); 918 } 919 920 if (rect == null) 921 return; 922 923 // Check if paint has possibly been called directly, without a previous 924 // call to damage(). In this case we need to do some cleanup first. 925 if ((x != rect.x) || (y != rect.y)) 926 { 927 repaint(); // Erase previous location of caret. 928 x = rect.x; 929 y = rect.y; 930 width = 1; 931 height = rect.height; 932 } 933 934 // Now draw the caret on the new position if visible. 935 if (visible && active) 936 { 937 g.setColor(textComponent.getCaretColor()); 938 g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1); 939 } 940 } 941 942 /** 943 * Returns all registered event listeners of the specified type. 944 * 945 * @param listenerType the type of listener to return 946 * 947 * @return all registered event listeners of the specified type 948 */ getListeners(Class<T> listenerType)949 public <T extends EventListener> T[] getListeners(Class<T> listenerType) 950 { 951 return listenerList.getListeners(listenerType); 952 } 953 954 /** 955 * Registers a {@link ChangeListener} that is notified whenever that state 956 * of this <code>Caret</code> changes. 957 * 958 * @param listener the listener to register to this caret 959 */ addChangeListener(ChangeListener listener)960 public void addChangeListener(ChangeListener listener) 961 { 962 listenerList.add(ChangeListener.class, listener); 963 } 964 965 /** 966 * Removes a {@link ChangeListener} from the list of registered listeners. 967 * 968 * @param listener the listener to remove 969 */ removeChangeListener(ChangeListener listener)970 public void removeChangeListener(ChangeListener listener) 971 { 972 listenerList.remove(ChangeListener.class, listener); 973 } 974 975 /** 976 * Returns all registered {@link ChangeListener}s of this <code>Caret</code>. 977 * 978 * @return all registered {@link ChangeListener}s of this <code>Caret</code> 979 */ getChangeListeners()980 public ChangeListener[] getChangeListeners() 981 { 982 return (ChangeListener[]) getListeners(ChangeListener.class); 983 } 984 985 /** 986 * Notifies all registered {@link ChangeListener}s that the state 987 * of this <code>Caret</code> has changed. 988 */ fireStateChanged()989 protected void fireStateChanged() 990 { 991 ChangeListener[] listeners = getChangeListeners(); 992 993 for (int index = 0; index < listeners.length; ++index) 994 listeners[index].stateChanged(changeEvent); 995 } 996 997 /** 998 * Returns the <code>JTextComponent</code> on which this <code>Caret</code> 999 * is installed. 1000 * 1001 * @return the <code>JTextComponent</code> on which this <code>Caret</code> 1002 * is installed 1003 */ getComponent()1004 protected final JTextComponent getComponent() 1005 { 1006 return textComponent; 1007 } 1008 1009 /** 1010 * Returns the blink rate of this <code>Caret</code> in milliseconds. 1011 * A value of <code>0</code> means that the caret does not blink. 1012 * 1013 * @return the blink rate of this <code>Caret</code> or <code>0</code> if 1014 * this caret does not blink 1015 */ getBlinkRate()1016 public int getBlinkRate() 1017 { 1018 return blinkRate; 1019 } 1020 1021 /** 1022 * Sets the blink rate of this <code>Caret</code> in milliseconds. 1023 * A value of <code>0</code> means that the caret does not blink. 1024 * 1025 * @param rate the new blink rate to set 1026 */ setBlinkRate(int rate)1027 public void setBlinkRate(int rate) 1028 { 1029 if (blinkTimer != null) 1030 blinkTimer.setDelay(rate); 1031 blinkRate = rate; 1032 } 1033 1034 /** 1035 * Returns the current position of this <code>Caret</code> within the 1036 * <code>Document</code>. 1037 * 1038 * @return the current position of this <code>Caret</code> within the 1039 * <code>Document</code> 1040 */ getDot()1041 public int getDot() 1042 { 1043 return dot; 1044 } 1045 1046 /** 1047 * Moves the <code>dot</code> location without touching the 1048 * <code>mark</code>. This is used when making a selection. 1049 * 1050 * <p>If the underlying text component has a {@link NavigationFilter} 1051 * installed the caret will call the corresponding method of that object.</p> 1052 * 1053 * @param dot the location where to move the dot 1054 * 1055 * @see #setDot(int) 1056 */ moveDot(int dot)1057 public void moveDot(int dot) 1058 { 1059 NavigationFilter filter = textComponent.getNavigationFilter(); 1060 if (filter != null) 1061 filter.moveDot(getBypass(), dot, Bias.Forward); 1062 else 1063 moveDotImpl(dot); 1064 } 1065 moveDotImpl(int dot)1066 void moveDotImpl(int dot) 1067 { 1068 if (dot >= 0) 1069 { 1070 Document doc = textComponent.getDocument(); 1071 if (doc != null) 1072 this.dot = Math.min(dot, doc.getLength()); 1073 this.dot = Math.max(this.dot, 0); 1074 1075 handleHighlight(); 1076 1077 appear(); 1078 } 1079 } 1080 1081 /** 1082 * Sets the current position of this <code>Caret</code> within the 1083 * <code>Document</code>. This also sets the <code>mark</code> to the new 1084 * location. 1085 * 1086 * <p>If the underlying text component has a {@link NavigationFilter} 1087 * installed the caret will call the corresponding method of that object.</p> 1088 * 1089 * @param dot 1090 * the new position to be set 1091 * @see #moveDot(int) 1092 */ setDot(int dot)1093 public void setDot(int dot) 1094 { 1095 NavigationFilter filter = textComponent.getNavigationFilter(); 1096 if (filter != null) 1097 filter.setDot(getBypass(), dot, Bias.Forward); 1098 else 1099 setDotImpl(dot); 1100 } 1101 setDotImpl(int dot)1102 void setDotImpl(int dot) 1103 { 1104 if (dot >= 0) 1105 { 1106 Document doc = textComponent.getDocument(); 1107 if (doc != null) 1108 this.dot = Math.min(dot, doc.getLength()); 1109 this.dot = Math.max(this.dot, 0); 1110 this.mark = this.dot; 1111 1112 clearHighlight(); 1113 1114 appear(); 1115 } 1116 } 1117 1118 /** 1119 * Show the caret (may be hidden due blinking) and adjust the timer not to 1120 * hide it (possibly immediately). 1121 * 1122 * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) 1123 */ appear()1124 void appear() 1125 { 1126 // All machinery is only required if the carret is blinking. 1127 if (blinkListener != null) 1128 { 1129 blinkListener.ignoreNextEvent = true; 1130 1131 // If the caret is visible, erase the current position by repainting 1132 // over. 1133 if (visible) 1134 repaint(); 1135 1136 // Draw the caret in the new position. 1137 visible = true; 1138 1139 Rectangle area = null; 1140 int dot = getDot(); 1141 try 1142 { 1143 area = getComponent().modelToView(dot); 1144 } 1145 catch (BadLocationException e) 1146 { 1147 // Let's ignore that. This shouldn't really occur. But if it 1148 // does (it seems that this happens when the model is mutating), 1149 // it causes no real damage. Uncomment this for debugging. 1150 // e.printStackTrace(); 1151 } 1152 if (area != null) 1153 { 1154 adjustVisibility(area); 1155 if (getMagicCaretPosition() == null) 1156 setMagicCaretPosition(new Point(area.x, area.y)); 1157 damage(area); 1158 } 1159 } 1160 repaint(); 1161 } 1162 1163 /** 1164 * Returns <code>true</code> if this <code>Caret</code> is blinking, 1165 * and <code>false</code> if not. The returned value is independent of 1166 * the visiblity of this <code>Caret</code> as returned by {@link #isVisible()}. 1167 * 1168 * @return <code>true</code> if this <code>Caret</code> is blinking, 1169 * and <code>false</code> if not. 1170 * @see #isVisible() 1171 * @since 1.5 1172 */ isActive()1173 public boolean isActive() 1174 { 1175 if (blinkTimer != null) 1176 return blinkTimer.isRunning(); 1177 1178 return false; 1179 } 1180 1181 /** 1182 * Returns <code>true</code> if this <code>Caret</code> is currently visible, 1183 * and <code>false</code> if it is not. 1184 * 1185 * @return <code>true</code> if this <code>Caret</code> is currently visible, 1186 * and <code>false</code> if it is not 1187 */ isVisible()1188 public boolean isVisible() 1189 { 1190 return visible && active; 1191 } 1192 1193 /** 1194 * Sets the visibility state of the caret. <code>true</code> shows the 1195 * <code>Caret</code>, <code>false</code> hides it. 1196 * 1197 * @param v the visibility to set 1198 */ setVisible(boolean v)1199 public void setVisible(boolean v) 1200 { 1201 if (v != visible) 1202 { 1203 visible = v; 1204 updateTimerStatus(); 1205 Rectangle area = null; 1206 int dot = getDot(); 1207 try 1208 { 1209 area = getComponent().modelToView(dot); 1210 } 1211 catch (BadLocationException e) 1212 { 1213 AssertionError ae; 1214 ae = new AssertionError("Unexpected bad caret location: " + dot); 1215 ae.initCause(e); 1216 throw ae; 1217 } 1218 if (area != null) 1219 damage(area); 1220 } 1221 } 1222 1223 /** 1224 * Returns the {@link Highlighter.HighlightPainter} that should be used 1225 * to paint the selection. 1226 * 1227 * @return the {@link Highlighter.HighlightPainter} that should be used 1228 * to paint the selection 1229 */ getSelectionPainter()1230 protected Highlighter.HighlightPainter getSelectionPainter() 1231 { 1232 return DefaultHighlighter.DefaultPainter; 1233 } 1234 1235 /** 1236 * Updates the carets rectangle properties to the specified rectangle and 1237 * repaints the caret. 1238 * 1239 * @param r the rectangle to set as the caret rectangle 1240 */ damage(Rectangle r)1241 protected void damage(Rectangle r) 1242 { 1243 if (r == null) 1244 return; 1245 x = r.x; 1246 y = r.y; 1247 width = 1; 1248 // height is normally set in paint and we leave it untouched. However, we 1249 // must set a valid value here, since otherwise the painting mechanism 1250 // sets a zero clip and never calls paint. 1251 if (height <= 0) 1252 try 1253 { 1254 height = textComponent.modelToView(dot).height; 1255 } 1256 catch (BadLocationException ble) 1257 { 1258 // Should not happen. 1259 throw new InternalError("Caret location not within document range."); 1260 } 1261 1262 repaint(); 1263 } 1264 1265 /** 1266 * Adjusts the text component so that the caret is visible. This default 1267 * implementation simply calls 1268 * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. 1269 * Subclasses may wish to change this. 1270 */ adjustVisibility(Rectangle rect)1271 protected void adjustVisibility(Rectangle rect) 1272 { 1273 getComponent().scrollRectToVisible(rect); 1274 } 1275 1276 /** 1277 * Initializes the blink timer. 1278 */ initBlinkTimer()1279 private void initBlinkTimer() 1280 { 1281 // Setup the blink timer. 1282 blinkListener = new BlinkTimerListener(); 1283 blinkTimer = new Timer(getBlinkRate(), blinkListener); 1284 blinkTimer.setRepeats(true); 1285 } 1286 1287 } 1288