Frames | No Frames |
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: 87: public Caret getCaret() 88: { 89: return DefaultCaret.this; 90: } 91: 92: public void moveDot(int dot, Bias bias) 93: { 94: DefaultCaret.this.moveDotImpl(dot); 95: } 96: 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: */ 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: */ 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: */ 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: */ 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: */ 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: oldDoc.removeDocumentListener(documentListener); 225: Document newDoc = (Document) e.getNewValue(); 226: newDoc.addDocumentListener(documentListener); 227: } 228: else if (name.equals("editable")) 229: { 230: active = (((Boolean) e.getNewValue()).booleanValue() 231: && textComponent.isEnabled()); 232: } 233: else if (name.equals("enabled")) 234: { 235: active = (((Boolean) e.getNewValue()).booleanValue() 236: && textComponent.isEditable()); 237: } 238: 239: } 240: 241: } 242: 243: /** The serialization UID (compatible with JDK1.5). */ 244: private static final long serialVersionUID = 4325555698756477346L; 245: 246: /** 247: * Indicates the Caret position should always be updated after Document 248: * changes even if the updates are not performed on the Event Dispatching 249: * thread. 250: * 251: * @since 1.5 252: */ 253: public static final int ALWAYS_UPDATE = 2; 254: 255: /** 256: * Indicates the Caret position should not be changed unless the Document 257: * length becomes less than the Caret position, in which case the Caret 258: * is moved to the end of the Document. 259: * 260: * @since 1.5 261: */ 262: public static final int NEVER_UPDATE = 1; 263: 264: /** 265: * Indicates the Caret position should be updated only if Document changes 266: * are made on the Event Dispatcher thread. 267: * 268: * @since 1.5 269: */ 270: public static final int UPDATE_WHEN_ON_EDT = 0; 271: 272: /** Keeps track of the current update policy **/ 273: int policy = UPDATE_WHEN_ON_EDT; 274: 275: /** 276: * The <code>ChangeEvent</code> that is fired by {@link #fireStateChanged()}. 277: */ 278: protected ChangeEvent changeEvent = new ChangeEvent(this); 279: 280: /** 281: * Stores all registered event listeners. 282: */ 283: protected EventListenerList listenerList = new EventListenerList(); 284: 285: /** 286: * Our document listener. 287: */ 288: DocumentListener documentListener; 289: 290: /** 291: * Our property listener. 292: */ 293: PropertyChangeListener propertyChangeListener; 294: 295: /** 296: * The text component in which this caret is installed. 297: * 298: * (Package private to avoid synthetic accessor method.) 299: */ 300: JTextComponent textComponent; 301: 302: /** 303: * Indicates if the selection should be visible or not. 304: */ 305: private boolean selectionVisible = true; 306: 307: /** 308: * The blink rate of this <code>Caret</code>. 309: */ 310: private int blinkRate = 500; 311: 312: /** 313: * The current dot position. 314: */ 315: private int dot = 0; 316: 317: /** 318: * The current mark position. 319: */ 320: private int mark = 0; 321: 322: /** 323: * The current visual caret position. 324: */ 325: private Point magicCaretPosition = null; 326: 327: /** 328: * Indicates if this <code>Caret</code> is currently visible or not. This is 329: * package private to avoid an accessor method. 330: */ 331: boolean visible = false; 332: 333: /** Indicates whether the text component where the caret is installed is 334: * editable and enabled. If either of these properties is <code>false</code> 335: * the caret is not drawn. 336: */ 337: boolean active = true; 338: 339: /** 340: * The current highlight entry. 341: */ 342: private Object highlightEntry; 343: 344: private Timer blinkTimer; 345: 346: private BlinkTimerListener blinkListener; 347: 348: /** 349: * A <code>NavigationFilter.FilterBypass</code> instance which 350: * is provided to the a <code>NavigationFilter</code> to 351: * unconditionally set or move the caret. 352: */ 353: NavigationFilter.FilterBypass bypass; 354: 355: /** 356: * Creates a new <code>DefaultCaret</code> instance. 357: */ 358: public DefaultCaret() 359: { 360: // Nothing to do here. 361: } 362: 363: /** Returns the caret's <code>NavigationFilter.FilterBypass</code> instance 364: * and creates it if it does not yet exist. 365: * 366: * @return The caret's <code>NavigationFilter.FilterBypass</code> instance. 367: */ 368: private NavigationFilter.FilterBypass getBypass() 369: { 370: return (bypass == null) ? bypass = new Bypass() : bypass; 371: } 372: 373: /** 374: * Sets the Caret update policy. 375: * 376: * @param policy the new policy. Valid values are: 377: * ALWAYS_UPDATE: always update the Caret position, even when Document 378: * updates don't occur on the Event Dispatcher thread. 379: * NEVER_UPDATE: don't update the Caret position unless the Document 380: * length becomes less than the Caret position (then update the 381: * Caret to the end of the Document). 382: * UPDATE_WHEN_ON_EDT: update the Caret position when the 383: * Document updates occur on the Event Dispatcher thread. This is the 384: * default. 385: * 386: * @since 1.5 387: * @throws IllegalArgumentException if policy is not one of the above. 388: */ 389: public void setUpdatePolicy (int policy) 390: { 391: if (policy != ALWAYS_UPDATE && policy != NEVER_UPDATE 392: && policy != UPDATE_WHEN_ON_EDT) 393: throw new 394: IllegalArgumentException 395: ("policy must be ALWAYS_UPDATE, NEVER__UPDATE, or UPDATE_WHEN_ON_EDT"); 396: this.policy = policy; 397: } 398: 399: /** 400: * Gets the caret update policy. 401: * 402: * @return the caret update policy. 403: * @since 1.5 404: */ 405: public int getUpdatePolicy () 406: { 407: return policy; 408: } 409: 410: /** 411: * Moves the caret position when the mouse is dragged over the text 412: * component, modifying the selectiony. 413: * 414: * <p>When the text component where the caret is installed is disabled, 415: * the selection is not change but you can still scroll the text and 416: * update the caret's location.</p> 417: * 418: * @param event the <code>MouseEvent</code> describing the drag operation 419: */ 420: public void mouseDragged(MouseEvent event) 421: { 422: if (event.getButton() == MouseEvent.BUTTON1) 423: { 424: if (textComponent.isEnabled()) 425: moveCaret(event); 426: else 427: positionCaret(event); 428: } 429: } 430: 431: /** 432: * Indicates a mouse movement over the text component. Does nothing here. 433: * 434: * @param event the <code>MouseEvent</code> describing the mouse operation 435: */ 436: public void mouseMoved(MouseEvent event) 437: { 438: // Nothing to do here. 439: } 440: 441: /** 442: * When the click is received from Button 1 then the following actions 443: * are performed here: 444: * 445: * <ul> 446: * <li>If we receive a double click, the caret position (dot) is set 447: * to the position associated to the mouse click and the word at 448: * this location is selected. If there is no word at the pointer 449: * the gap is selected instead.</li> 450: * <li>If we receive a triple click, the caret position (dot) is set 451: * to the position associated to the mouse click and the line at 452: * this location is selected.</li> 453: * </ul> 454: * 455: * @param event the <code>MouseEvent</code> describing the click operation 456: */ 457: public void mouseClicked(MouseEvent event) 458: { 459: // Do not modify selection if component is disabled. 460: if (!textComponent.isEnabled()) 461: return; 462: 463: int count = event.getClickCount(); 464: 465: if (event.getButton() == MouseEvent.BUTTON1 && count >= 2) 466: { 467: int newDot = getComponent().viewToModel(event.getPoint()); 468: JTextComponent t = getComponent(); 469: 470: try 471: { 472: if (count == 3) 473: { 474: setDot(Utilities.getRowStart(t, newDot)); 475: moveDot( Utilities.getRowEnd(t, newDot)); 476: } 477: else 478: { 479: int wordStart = Utilities.getWordStart(t, newDot); 480: 481: // When the mouse points at the offset of the first character 482: // in a word Utilities().getPreviousWord will not return that 483: // word but we want to select that. We have to use 484: // Utilities.getWordStart() to get it. 485: if (newDot == wordStart) 486: { 487: setDot(wordStart); 488: moveDot(Utilities.getWordEnd(t, wordStart)); 489: } 490: else 491: { 492: int nextWord = Utilities.getNextWord(t, newDot); 493: int previousWord = Utilities.getPreviousWord(t, newDot); 494: int previousWordEnd = Utilities.getWordEnd(t, previousWord); 495: 496: // If the user clicked in the space between two words, 497: // then select the space. 498: if (newDot >= previousWordEnd && newDot <= nextWord) 499: { 500: setDot(previousWordEnd); 501: moveDot(nextWord); 502: } 503: // Otherwise select the word under the mouse pointer. 504: else 505: { 506: setDot(previousWord); 507: moveDot(previousWordEnd); 508: } 509: } 510: } 511: } 512: catch(BadLocationException ble) 513: { 514: // TODO: Swallowing ok here? 515: } 516: } 517: 518: } 519: 520: /** 521: * Indicates that the mouse has entered the text component. Nothing is done 522: * here. 523: * 524: * @param event the <code>MouseEvent</code> describing the mouse operation 525: */ 526: public void mouseEntered(MouseEvent event) 527: { 528: // Nothing to do here. 529: } 530: 531: /** 532: * Indicates that the mouse has exited the text component. Nothing is done 533: * here. 534: * 535: * @param event the <code>MouseEvent</code> describing the mouse operation 536: */ 537: public void mouseExited(MouseEvent event) 538: { 539: // Nothing to do here. 540: } 541: 542: /** 543: * If the button 1 is pressed, the caret position is updated to the 544: * position of the mouse click and the text component requests the input 545: * focus if it is enabled. If the SHIFT key is held down, the caret will 546: * be moved, which might select the text between the old and new location. 547: * 548: * @param event the <code>MouseEvent</code> describing the press operation 549: */ 550: public void mousePressed(MouseEvent event) 551: { 552: int button = event.getButton(); 553: 554: // The implementation assumes that consuming the event makes the AWT event 555: // mechanism forget about this event instance and not transfer focus. 556: // By observing how the RI reacts the following behavior has been 557: // implemented (in regard to text components): 558: // - a left-click moves the caret 559: // - a left-click when shift is held down expands the selection 560: // - a right-click or click with any additional mouse button 561: // on a text component is ignored 562: // - a middle-click positions the caret and pastes the clipboard 563: // contents. 564: // - a middle-click when shift is held down is ignored 565: 566: if (button == MouseEvent.BUTTON1) 567: if (event.isShiftDown()) 568: moveCaret(event); 569: else 570: positionCaret(event); 571: else if(button == MouseEvent.BUTTON2) 572: if (event.isShiftDown()) 573: event.consume(); 574: else 575: { 576: positionCaret(event); 577: 578: textComponent.paste(); 579: } 580: else 581: event.consume(); 582: } 583: 584: /** 585: * Indicates that a mouse button has been released on the text component. 586: * Nothing is done here. 587: * 588: * @param event the <code>MouseEvent</code> describing the mouse operation 589: */ 590: public void mouseReleased(MouseEvent event) 591: { 592: // Nothing to do here. 593: } 594: 595: /** 596: * Sets the caret to <code>visible</code> if the text component is editable. 597: * 598: * @param event the <code>FocusEvent</code> 599: */ 600: public void focusGained(FocusEvent event) 601: { 602: if (textComponent.isEditable()) 603: { 604: setVisible(true); 605: updateTimerStatus(); 606: } 607: } 608: 609: /** 610: * Sets the caret to <code>invisible</code>. 611: * 612: * @param event the <code>FocusEvent</code> 613: */ 614: public void focusLost(FocusEvent event) 615: { 616: if (textComponent.isEditable() && event.isTemporary() == false) 617: { 618: setVisible(false); 619: 620: // Stop the blinker, if running. 621: if (blinkTimer != null && blinkTimer.isRunning()) 622: blinkTimer.stop(); 623: } 624: } 625: 626: /** 627: * Install (if not present) and start the timer, if the caret must blink. The 628: * caret does not blink if it is invisible, or the component is disabled or 629: * not editable. 630: */ 631: private void updateTimerStatus() 632: { 633: if (textComponent.isEnabled() && textComponent.isEditable()) 634: { 635: if (blinkTimer == null) 636: initBlinkTimer(); 637: if (!blinkTimer.isRunning()) 638: blinkTimer.start(); 639: } 640: else 641: { 642: if (blinkTimer != null) 643: blinkTimer.stop(); 644: } 645: } 646: 647: /** 648: * Moves the caret to the position specified in the <code>MouseEvent</code>. 649: * This will cause a selection if the dot and mark are different. 650: * 651: * @param event the <code>MouseEvent</code> from which to fetch the position 652: */ 653: protected void moveCaret(MouseEvent event) 654: { 655: int newDot = getComponent().viewToModel(event.getPoint()); 656: moveDot(newDot); 657: } 658: 659: /** 660: * Repositions the caret to the position specified in the 661: * <code>MouseEvent</code>. 662: * 663: * @param event the <code>MouseEvent</code> from which to fetch the position 664: */ 665: protected void positionCaret(MouseEvent event) 666: { 667: int newDot = getComponent().viewToModel(event.getPoint()); 668: setDot(newDot); 669: } 670: 671: /** 672: * Deinstalls this <code>Caret</code> from the specified 673: * <code>JTextComponent</code>. This removes any listeners that have been 674: * registered by this <code>Caret</code>. 675: * 676: * @param c the text component from which to install this caret 677: */ 678: public void deinstall(JTextComponent c) 679: { 680: textComponent.removeFocusListener(this); 681: textComponent.removeMouseListener(this); 682: textComponent.removeMouseMotionListener(this); 683: textComponent.getDocument().removeDocumentListener(documentListener); 684: documentListener = null; 685: textComponent.removePropertyChangeListener(propertyChangeListener); 686: propertyChangeListener = null; 687: textComponent = null; 688: 689: // Deinstall blink timer if present. 690: if (blinkTimer != null) 691: blinkTimer.stop(); 692: blinkTimer = null; 693: } 694: 695: /** 696: * Installs this <code>Caret</code> on the specified 697: * <code>JTextComponent</code>. This registers a couple of listeners 698: * on the text component. 699: * 700: * @param c the text component on which to install this caret 701: */ 702: public void install(JTextComponent c) 703: { 704: textComponent = c; 705: textComponent.addFocusListener(this); 706: textComponent.addMouseListener(this); 707: textComponent.addMouseMotionListener(this); 708: propertyChangeListener = new PropertyChangeHandler(); 709: textComponent.addPropertyChangeListener(propertyChangeListener); 710: documentListener = new DocumentHandler(); 711: textComponent.getDocument().addDocumentListener(documentListener); 712: active = textComponent.isEditable() && textComponent.isEnabled(); 713: 714: repaint(); 715: } 716: 717: /** 718: * Sets the current visual position of this <code>Caret</code>. 719: * 720: * @param p the Point to use for the saved location. May be <code>null</code> 721: * to indicate that there is no visual location 722: */ 723: public void setMagicCaretPosition(Point p) 724: { 725: magicCaretPosition = p; 726: } 727: 728: /** 729: * Returns the current visual position of this <code>Caret</code>. 730: * 731: * @return the current visual position of this <code>Caret</code> 732: * 733: * @see #setMagicCaretPosition 734: */ 735: public Point getMagicCaretPosition() 736: { 737: return magicCaretPosition; 738: } 739: 740: /** 741: * Returns the current position of the <code>mark</code>. The 742: * <code>mark</code> marks the location in the <code>Document</code> that 743: * is the end of a selection. If there is no selection, the <code>mark</code> 744: * is the same as the <code>dot</code>. 745: * 746: * @return the current position of the mark 747: */ 748: public int getMark() 749: { 750: return mark; 751: } 752: 753: private void clearHighlight() 754: { 755: Highlighter highlighter = textComponent.getHighlighter(); 756: 757: if (highlighter == null) 758: return; 759: 760: if (selectionVisible) 761: { 762: try 763: { 764: if (highlightEntry != null) 765: highlighter.changeHighlight(highlightEntry, 0, 0); 766: 767: // Free the global variable which stores the text component with an active 768: // selection. 769: if (componentWithSelection == textComponent) 770: componentWithSelection = null; 771: } 772: catch (BadLocationException e) 773: { 774: // This should never happen. 775: throw new InternalError(); 776: } 777: } 778: else 779: { 780: if (highlightEntry != null) 781: { 782: highlighter.removeHighlight(highlightEntry); 783: highlightEntry = null; 784: } 785: } 786: } 787: 788: private void handleHighlight() 789: { 790: Highlighter highlighter = textComponent.getHighlighter(); 791: 792: if (highlighter == null) 793: return; 794: 795: int p0 = Math.min(dot, mark); 796: int p1 = Math.max(dot, mark); 797: 798: if (selectionVisible) 799: { 800: try 801: { 802: if (highlightEntry == null) 803: highlightEntry = highlighter.addHighlight(p0, p1, getSelectionPainter()); 804: else 805: highlighter.changeHighlight(highlightEntry, p0, p1); 806: 807: // If another component currently has a text selection clear that selection 808: // first. 809: if (componentWithSelection != null) 810: if (componentWithSelection != textComponent) 811: { 812: Caret c = componentWithSelection.getCaret(); 813: c.setDot(c.getDot()); 814: } 815: componentWithSelection = textComponent; 816: 817: } 818: catch (BadLocationException e) 819: { 820: // This should never happen. 821: throw new InternalError(); 822: } 823: } 824: else 825: { 826: if (highlightEntry != null) 827: { 828: highlighter.removeHighlight(highlightEntry); 829: highlightEntry = null; 830: } 831: } 832: } 833: 834: /** 835: * Sets the visiblity state of the selection. 836: * 837: * @param v <code>true</code> if the selection should be visible, 838: * <code>false</code> otherwise 839: */ 840: public void setSelectionVisible(boolean v) 841: { 842: if (selectionVisible == v) 843: return; 844: 845: selectionVisible = v; 846: handleHighlight(); 847: repaint(); 848: } 849: 850: /** 851: * Returns <code>true</code> if the selection is currently visible, 852: * <code>false</code> otherwise. 853: * 854: * @return <code>true</code> if the selection is currently visible, 855: * <code>false</code> otherwise 856: */ 857: public boolean isSelectionVisible() 858: { 859: return selectionVisible; 860: } 861: 862: /** 863: * Causes the <code>Caret</code> to repaint itself. 864: */ 865: protected final void repaint() 866: { 867: getComponent().repaint(x, y, width, height); 868: } 869: 870: /** 871: * Paints this <code>Caret</code> using the specified <code>Graphics</code> 872: * context. 873: * 874: * @param g the graphics context to use 875: */ 876: public void paint(Graphics g) 877: { 878: JTextComponent comp = getComponent(); 879: if (comp == null) 880: return; 881: 882: // Make sure the dot has a sane position. 883: dot = Math.min(dot, textComponent.getDocument().getLength()); 884: dot = Math.max(dot, 0); 885: 886: Rectangle rect = null; 887: 888: try 889: { 890: rect = textComponent.modelToView(dot); 891: } 892: catch (BadLocationException e) 893: { 894: AssertionError ae; 895: ae = new AssertionError("Unexpected bad caret location: " + dot); 896: ae.initCause(e); 897: throw ae; 898: } 899: 900: if (rect == null) 901: return; 902: 903: // Check if paint has possibly been called directly, without a previous 904: // call to damage(). In this case we need to do some cleanup first. 905: if ((x != rect.x) || (y != rect.y)) 906: { 907: repaint(); // Erase previous location of caret. 908: x = rect.x; 909: y = rect.y; 910: width = 1; 911: height = rect.height; 912: } 913: 914: // Now draw the caret on the new position if visible. 915: if (visible && active) 916: { 917: g.setColor(textComponent.getCaretColor()); 918: g.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1); 919: } 920: } 921: 922: /** 923: * Returns all registered event listeners of the specified type. 924: * 925: * @param listenerType the type of listener to return 926: * 927: * @return all registered event listeners of the specified type 928: */ 929: public EventListener[] getListeners(Class listenerType) 930: { 931: return listenerList.getListeners(listenerType); 932: } 933: 934: /** 935: * Registers a {@link ChangeListener} that is notified whenever that state 936: * of this <code>Caret</code> changes. 937: * 938: * @param listener the listener to register to this caret 939: */ 940: public void addChangeListener(ChangeListener listener) 941: { 942: listenerList.add(ChangeListener.class, listener); 943: } 944: 945: /** 946: * Removes a {@link ChangeListener} from the list of registered listeners. 947: * 948: * @param listener the listener to remove 949: */ 950: public void removeChangeListener(ChangeListener listener) 951: { 952: listenerList.remove(ChangeListener.class, listener); 953: } 954: 955: /** 956: * Returns all registered {@link ChangeListener}s of this <code>Caret</code>. 957: * 958: * @return all registered {@link ChangeListener}s of this <code>Caret</code> 959: */ 960: public ChangeListener[] getChangeListeners() 961: { 962: return (ChangeListener[]) getListeners(ChangeListener.class); 963: } 964: 965: /** 966: * Notifies all registered {@link ChangeListener}s that the state 967: * of this <code>Caret</code> has changed. 968: */ 969: protected void fireStateChanged() 970: { 971: ChangeListener[] listeners = getChangeListeners(); 972: 973: for (int index = 0; index < listeners.length; ++index) 974: listeners[index].stateChanged(changeEvent); 975: } 976: 977: /** 978: * Returns the <code>JTextComponent</code> on which this <code>Caret</code> 979: * is installed. 980: * 981: * @return the <code>JTextComponent</code> on which this <code>Caret</code> 982: * is installed 983: */ 984: protected final JTextComponent getComponent() 985: { 986: return textComponent; 987: } 988: 989: /** 990: * Returns the blink rate of this <code>Caret</code> in milliseconds. 991: * A value of <code>0</code> means that the caret does not blink. 992: * 993: * @return the blink rate of this <code>Caret</code> or <code>0</code> if 994: * this caret does not blink 995: */ 996: public int getBlinkRate() 997: { 998: return blinkRate; 999: } 1000: 1001: /** 1002: * Sets the blink rate of this <code>Caret</code> in milliseconds. 1003: * A value of <code>0</code> means that the caret does not blink. 1004: * 1005: * @param rate the new blink rate to set 1006: */ 1007: public void setBlinkRate(int rate) 1008: { 1009: if (blinkTimer != null) 1010: blinkTimer.setDelay(rate); 1011: blinkRate = rate; 1012: } 1013: 1014: /** 1015: * Returns the current position of this <code>Caret</code> within the 1016: * <code>Document</code>. 1017: * 1018: * @return the current position of this <code>Caret</code> within the 1019: * <code>Document</code> 1020: */ 1021: public int getDot() 1022: { 1023: return dot; 1024: } 1025: 1026: /** 1027: * Moves the <code>dot</code> location without touching the 1028: * <code>mark</code>. This is used when making a selection. 1029: * 1030: * <p>If the underlying text component has a {@link NavigationFilter} 1031: * installed the caret will call the corresponding method of that object.</p> 1032: * 1033: * @param dot the location where to move the dot 1034: * 1035: * @see #setDot(int) 1036: */ 1037: public void moveDot(int dot) 1038: { 1039: NavigationFilter filter = textComponent.getNavigationFilter(); 1040: if (filter != null) 1041: filter.moveDot(getBypass(), dot, Bias.Forward); 1042: else 1043: moveDotImpl(dot); 1044: } 1045: 1046: void moveDotImpl(int dot) 1047: { 1048: if (dot >= 0) 1049: { 1050: Document doc = textComponent.getDocument(); 1051: if (doc != null) 1052: this.dot = Math.min(dot, doc.getLength()); 1053: this.dot = Math.max(this.dot, 0); 1054: 1055: handleHighlight(); 1056: 1057: appear(); 1058: 1059: adjustVisibility(this); 1060: } 1061: } 1062: 1063: /** 1064: * Sets the current position of this <code>Caret</code> within the 1065: * <code>Document</code>. This also sets the <code>mark</code> to the new 1066: * location. 1067: * 1068: * <p>If the underlying text component has a {@link NavigationFilter} 1069: * installed the caret will call the corresponding method of that object.</p> 1070: * 1071: * @param dot 1072: * the new position to be set 1073: * @see #moveDot(int) 1074: */ 1075: public void setDot(int dot) 1076: { 1077: NavigationFilter filter = textComponent.getNavigationFilter(); 1078: if (filter != null) 1079: filter.setDot(getBypass(), dot, Bias.Forward); 1080: else 1081: setDotImpl(dot); 1082: } 1083: 1084: void setDotImpl(int dot) 1085: { 1086: if (dot >= 0) 1087: { 1088: Document doc = textComponent.getDocument(); 1089: if (doc != null) 1090: this.dot = Math.min(dot, doc.getLength()); 1091: this.dot = Math.max(this.dot, 0); 1092: this.mark = this.dot; 1093: 1094: clearHighlight(); 1095: 1096: appear(); 1097: 1098: adjustVisibility(this); 1099: } 1100: } 1101: 1102: /** 1103: * Show the caret (may be hidden due blinking) and adjust the timer not to 1104: * hide it (possibly immediately). 1105: * 1106: * @author Audrius Meskauskas (AudriusA@Bioinformatics.org) 1107: */ 1108: void appear() 1109: { 1110: // All machinery is only required if the carret is blinking. 1111: if (blinkListener != null) 1112: { 1113: blinkListener.ignoreNextEvent = true; 1114: 1115: // If the caret is visible, erase the current position by repainting 1116: // over. 1117: if (visible) 1118: repaint(); 1119: 1120: // Draw the caret in the new position. 1121: visible = true; 1122: 1123: Rectangle area = null; 1124: int dot = getDot(); 1125: try 1126: { 1127: area = getComponent().modelToView(dot); 1128: } 1129: catch (BadLocationException e) 1130: { 1131: AssertionError ae; 1132: ae = new AssertionError("Unexpected bad caret location: " + dot); 1133: ae.initCause(e); 1134: throw ae; 1135: } 1136: if (area != null) 1137: damage(area); 1138: } 1139: repaint(); 1140: } 1141: 1142: /** 1143: * Returns <code>true</code> if this <code>Caret</code> is currently visible, 1144: * and <code>false</code> if it is not. 1145: * 1146: * @return <code>true</code> if this <code>Caret</code> is currently visible, 1147: * and <code>false</code> if it is not 1148: */ 1149: public boolean isVisible() 1150: { 1151: return visible && active; 1152: } 1153: 1154: /** 1155: * Sets the visibility state of the caret. <code>true</code> shows the 1156: * <code>Caret</code>, <code>false</code> hides it. 1157: * 1158: * @param v the visibility to set 1159: */ 1160: public void setVisible(boolean v) 1161: { 1162: if (v != visible) 1163: { 1164: visible = v; 1165: updateTimerStatus(); 1166: Rectangle area = null; 1167: int dot = getDot(); 1168: try 1169: { 1170: area = getComponent().modelToView(dot); 1171: } 1172: catch (BadLocationException e) 1173: { 1174: AssertionError ae; 1175: ae = new AssertionError("Unexpected bad caret location: " + dot); 1176: ae.initCause(e); 1177: throw ae; 1178: } 1179: if (area != null) 1180: damage(area); 1181: } 1182: } 1183: 1184: /** 1185: * Returns the {@link Highlighter.HighlightPainter} that should be used 1186: * to paint the selection. 1187: * 1188: * @return the {@link Highlighter.HighlightPainter} that should be used 1189: * to paint the selection 1190: */ 1191: protected Highlighter.HighlightPainter getSelectionPainter() 1192: { 1193: return DefaultHighlighter.DefaultPainter; 1194: } 1195: 1196: /** 1197: * Updates the carets rectangle properties to the specified rectangle and 1198: * repaints the caret. 1199: * 1200: * @param r the rectangle to set as the caret rectangle 1201: */ 1202: protected void damage(Rectangle r) 1203: { 1204: if (r == null) 1205: return; 1206: x = r.x; 1207: y = r.y; 1208: width = 1; 1209: // height is normally set in paint and we leave it untouched. However, we 1210: // must set a valid value here, since otherwise the painting mechanism 1211: // sets a zero clip and never calls paint. 1212: if (height <= 0) 1213: try 1214: { 1215: height = textComponent.modelToView(dot).height; 1216: } 1217: catch (BadLocationException ble) 1218: { 1219: // Should not happen. 1220: throw new InternalError("Caret location not within document range."); 1221: } 1222: 1223: repaint(); 1224: } 1225: 1226: /** 1227: * Adjusts the text component so that the caret is visible. This default 1228: * implementation simply calls 1229: * {@link JComponent#scrollRectToVisible(Rectangle)} on the text component. 1230: * Subclasses may wish to change this. 1231: */ 1232: protected void adjustVisibility(Rectangle rect) 1233: { 1234: getComponent().scrollRectToVisible(rect); 1235: } 1236: 1237: /** 1238: * Initializes the blink timer. 1239: */ 1240: private void initBlinkTimer() 1241: { 1242: // Setup the blink timer. 1243: blinkListener = new BlinkTimerListener(); 1244: blinkTimer = new Timer(getBlinkRate(), blinkListener); 1245: blinkTimer.setRepeats(true); 1246: } 1247: 1248: }