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