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: 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 768: public int getMark() 769: { 770: return mark; 771: } 772: 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: 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: */ 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: */ 877: public boolean isSelectionVisible() 878: { 879: return selectionVisible; 880: } 881: 882: /** 883: * Causes the <code>Caret</code> to repaint itself. 884: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: 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: */ 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: 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 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: */ 1271: protected void adjustVisibility(Rectangle rect) 1272: { 1273: getComponent().scrollRectToVisible(rect); 1274: } 1275: 1276: /** 1277: * Initializes the blink timer. 1278: */ 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: }