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