Source for javax.swing.text.DefaultCaret

   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: }