Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]
    1   /*
    2    * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.awt;
   28   import java.awt.event;
   29   import java.awt.datatransfer;
   30   import java.beans;
   31   import java.awt.event.ActionEvent;
   32   import java.awt.event.ActionListener;
   33   import java.io;
   34   import javax.swing;
   35   import javax.swing.event;
   36   import javax.swing.plaf;
   37   import java.util.EventListener;
   38   import sun.swing.SwingUtilities2;
   39   
   40   /**
   41    * A default implementation of Caret.  The caret is rendered as
   42    * a vertical line in the color specified by the CaretColor property
   43    * of the associated JTextComponent.  It can blink at the rate specified
   44    * by the BlinkRate property.
   45    * <p>
   46    * This implementation expects two sources of asynchronous notification.
   47    * The timer thread fires asynchronously, and causes the caret to simply
   48    * repaint the most recent bounding box.  The caret also tracks change
   49    * as the document is modified.  Typically this will happen on the
   50    * event dispatch thread as a result of some mouse or keyboard event.
   51    * The caret behavior on both synchronous and asynchronous documents updates
   52    * is controlled by <code>UpdatePolicy</code> property. The repaint of the
   53    * new caret location will occur on the event thread in any case, as calls to
   54    * <code>modelToView</code> are only safe on the event thread.
   55    * <p>
   56    * The caret acts as a mouse and focus listener on the text component
   57    * it has been installed in, and defines the caret semantics based upon
   58    * those events.  The listener methods can be reimplemented to change the
   59    * semantics.
   60    * By default, the first mouse button will be used to set focus and caret
   61    * position.  Dragging the mouse pointer with the first mouse button will
   62    * sweep out a selection that is contiguous in the model.  If the associated
   63    * text component is editable, the caret will become visible when focus
   64    * is gained, and invisible when focus is lost.
   65    * <p>
   66    * The Highlighter bound to the associated text component is used to
   67    * render the selection by default.
   68    * Selection appearance can be customized by supplying a
   69    * painter to use for the highlights.  By default a painter is used that
   70    * will render a solid color as specified in the associated text component
   71    * in the <code>SelectionColor</code> property.  This can easily be changed
   72    * by reimplementing the
   73    * <a href="#getSelectionHighlighter">getSelectionHighlighter</a>
   74    * method.
   75    * <p>
   76    * A customized caret appearance can be achieved by reimplementing
   77    * the paint method.  If the paint method is changed, the damage method
   78    * should also be reimplemented to cause a repaint for the area needed
   79    * to render the caret.  The caret extends the Rectangle class which
   80    * is used to hold the bounding box for where the caret was last rendered.
   81    * This enables the caret to repaint in a thread-safe manner when the
   82    * caret moves without making a call to modelToView which is unstable
   83    * between model updates and view repair (i.e. the order of delivery
   84    * to DocumentListeners is not guaranteed).
   85    * <p>
   86    * The magic caret position is set to null when the caret position changes.
   87    * A timer is used to determine the new location (after the caret change).
   88    * When the timer fires, if the magic caret position is still null it is
   89    * reset to the current caret position. Any actions that change
   90    * the caret position and want the magic caret position to remain the
   91    * same, must remember the magic caret position, change the cursor, and
   92    * then set the magic caret position to its original value. This has the
   93    * benefit that only actions that want the magic caret position to persist
   94    * (such as open/down) need to know about it.
   95    * <p>
   96    * <strong>Warning:</strong>
   97    * Serialized objects of this class will not be compatible with
   98    * future Swing releases. The current serialization support is
   99    * appropriate for short term storage or RMI between applications running
  100    * the same version of Swing.  As of 1.4, support for long term storage
  101    * of all JavaBeans<sup><font size="-2">TM</font></sup>
  102    * has been added to the <code>java.beans</code> package.
  103    * Please see {@link java.beans.XMLEncoder}.
  104    *
  105    * @author  Timothy Prinzing
  106    * @see     Caret
  107    */
  108   public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener {
  109   
  110       /**
  111        * Indicates that the caret position is to be updated only when
  112        * document changes are performed on the Event Dispatching Thread.
  113        * @see #setUpdatePolicy
  114        * @see #getUpdatePolicy
  115        * @since 1.5
  116        */
  117       public static final int UPDATE_WHEN_ON_EDT = 0;
  118   
  119       /**
  120        * Indicates that the caret should remain at the same
  121        * absolute position in the document regardless of any document
  122        * updates, except when the document length becomes less than
  123        * the current caret position due to removal. In that case the caret
  124        * position is adjusted to the end of the document.
  125        *
  126        * @see #setUpdatePolicy
  127        * @see #getUpdatePolicy
  128        * @since 1.5
  129        */
  130       public static final int NEVER_UPDATE = 1;
  131   
  132       /**
  133        * Indicates that the caret position is to be <b>always</b>
  134        * updated accordingly to the document changes regardless whether
  135        * the document updates are performed on the Event Dispatching Thread
  136        * or not.
  137        *
  138        * @see #setUpdatePolicy
  139        * @see #getUpdatePolicy
  140        * @since 1.5
  141        */
  142       public static final int ALWAYS_UPDATE = 2;
  143   
  144       /**
  145        * Constructs a default caret.
  146        */
  147       public DefaultCaret() {
  148       }
  149   
  150       /**
  151        * Sets the caret movement policy on the document updates. Normally
  152        * the caret updates its absolute position within the document on
  153        * insertions occurred before or at the caret position and
  154        * on removals before the caret position. 'Absolute position'
  155        * means here the position relative to the start of the document.
  156        * For example if
  157        * a character is typed within editable text component it is inserted
  158        * at the caret position and the caret moves to the next absolute
  159        * position within the document due to insertion and if
  160        * <code>BACKSPACE</code> is typed then caret decreases its absolute
  161        * position due to removal of a character before it. Sometimes
  162        * it may be useful to turn off the caret position updates so that
  163        * the caret stays at the same absolute position within the
  164        * document position regardless of any document updates.
  165        * <p>
  166        * The following update policies are allowed:
  167        * <ul>
  168        *   <li><code>NEVER_UPDATE</code>: the caret stays at the same
  169        *       absolute position in the document regardless of any document
  170        *       updates, except when document length becomes less than
  171        *       the current caret position due to removal. In that case caret
  172        *       position is adjusted to the end of the document.
  173        *       The caret doesn't try to keep itself visible by scrolling
  174        *       the associated view when using this policy. </li>
  175        *   <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
  176        *       changes. For regular changes it increases its position
  177        *       if an insertion occurs before or at its current position,
  178        *       and decreases position if a removal occurs before
  179        *       its current position. For undo/redo updates it is always
  180        *       moved to the position where update occurred. The caret
  181        *       also tries to keep itself visible by calling
  182        *       <code>adjustVisibility</code> method.</li>
  183        *   <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
  184        *       if the document updates are performed on the Event Dispatching Thread
  185        *       and like <code>NEVER_UPDATE</code> if updates are performed on
  186        *       other thread. </li>
  187        * </ul> <p>
  188        * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
  189        *
  190        * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
  191        * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
  192        * @throws IllegalArgumentException if invalid value is passed
  193        *
  194        * @see #getUpdatePolicy
  195        * @see #adjustVisibility
  196        * @see #UPDATE_WHEN_ON_EDT
  197        * @see #NEVER_UPDATE
  198        * @see #ALWAYS_UPDATE
  199        *
  200        * @since 1.5
  201        */
  202       public void setUpdatePolicy(int policy) {
  203           updatePolicy = policy;
  204       }
  205   
  206       /**
  207        * Gets the caret movement policy on document updates.
  208        *
  209        * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
  210        * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
  211        *
  212        * @see #setUpdatePolicy
  213        * @see #UPDATE_WHEN_ON_EDT
  214        * @see #NEVER_UPDATE
  215        * @see #ALWAYS_UPDATE
  216        *
  217        * @since 1.5
  218        */
  219       public int getUpdatePolicy() {
  220           return updatePolicy;
  221       }
  222   
  223       /**
  224        * Gets the text editor component that this caret is
  225        * is bound to.
  226        *
  227        * @return the component
  228        */
  229       protected final JTextComponent getComponent() {
  230           return component;
  231       }
  232   
  233       /**
  234        * Cause the caret to be painted.  The repaint
  235        * area is the bounding box of the caret (i.e.
  236        * the caret rectangle or <em>this</em>).
  237        * <p>
  238        * This method is thread safe, although most Swing methods
  239        * are not. Please see
  240        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  241        * to Use Threads</A> for more information.
  242        */
  243       protected final synchronized void repaint() {
  244           if (component != null) {
  245               component.repaint(x, y, width, height);
  246           }
  247       }
  248   
  249       /**
  250        * Damages the area surrounding the caret to cause
  251        * it to be repainted in a new location.  If paint()
  252        * is reimplemented, this method should also be
  253        * reimplemented.  This method should update the
  254        * caret bounds (x, y, width, and height).
  255        *
  256        * @param r  the current location of the caret
  257        * @see #paint
  258        */
  259       protected synchronized void damage(Rectangle r) {
  260           if (r != null) {
  261               int damageWidth = getCaretWidth(r.height);
  262               x = r.x - 4 - (damageWidth >> 1);
  263               y = r.y;
  264               width = 9 + damageWidth;
  265               height = r.height;
  266               repaint();
  267           }
  268       }
  269   
  270       /**
  271        * Scrolls the associated view (if necessary) to make
  272        * the caret visible.  Since how this should be done
  273        * is somewhat of a policy, this method can be
  274        * reimplemented to change the behavior.  By default
  275        * the scrollRectToVisible method is called on the
  276        * associated component.
  277        *
  278        * @param nloc the new position to scroll to
  279        */
  280       protected void adjustVisibility(Rectangle nloc) {
  281           if(component == null) {
  282               return;
  283           }
  284           if (SwingUtilities.isEventDispatchThread()) {
  285                   component.scrollRectToVisible(nloc);
  286           } else {
  287               SwingUtilities.invokeLater(new SafeScroller(nloc));
  288           }
  289       }
  290   
  291       /**
  292        * Gets the painter for the Highlighter.
  293        *
  294        * @return the painter
  295        */
  296       protected Highlighter.HighlightPainter getSelectionPainter() {
  297           return DefaultHighlighter.DefaultPainter;
  298       }
  299   
  300       /**
  301        * Tries to set the position of the caret from
  302        * the coordinates of a mouse event, using viewToModel().
  303        *
  304        * @param e the mouse event
  305        */
  306       protected void positionCaret(MouseEvent e) {
  307           Point pt = new Point(e.getX(), e.getY());
  308           Position.Bias[] biasRet = new Position.Bias[1];
  309           int pos = component.getUI().viewToModel(component, pt, biasRet);
  310           if(biasRet[0] == null)
  311               biasRet[0] = Position.Bias.Forward;
  312           if (pos >= 0) {
  313               setDot(pos, biasRet[0]);
  314           }
  315       }
  316   
  317       /**
  318        * Tries to move the position of the caret from
  319        * the coordinates of a mouse event, using viewToModel().
  320        * This will cause a selection if the dot and mark
  321        * are different.
  322        *
  323        * @param e the mouse event
  324        */
  325       protected void moveCaret(MouseEvent e) {
  326           Point pt = new Point(e.getX(), e.getY());
  327           Position.Bias[] biasRet = new Position.Bias[1];
  328           int pos = component.getUI().viewToModel(component, pt, biasRet);
  329           if(biasRet[0] == null)
  330               biasRet[0] = Position.Bias.Forward;
  331           if (pos >= 0) {
  332               moveDot(pos, biasRet[0]);
  333           }
  334       }
  335   
  336       // --- FocusListener methods --------------------------
  337   
  338       /**
  339        * Called when the component containing the caret gains
  340        * focus.  This is implemented to set the caret to visible
  341        * if the component is editable.
  342        *
  343        * @param e the focus event
  344        * @see FocusListener#focusGained
  345        */
  346       public void focusGained(FocusEvent e) {
  347           if (component.isEnabled()) {
  348               if (component.isEditable()) {
  349                   setVisible(true);
  350               }
  351               setSelectionVisible(true);
  352           }
  353       }
  354   
  355       /**
  356        * Called when the component containing the caret loses
  357        * focus.  This is implemented to set the caret to visibility
  358        * to false.
  359        *
  360        * @param e the focus event
  361        * @see FocusListener#focusLost
  362        */
  363       public void focusLost(FocusEvent e) {
  364           setVisible(false);
  365           setSelectionVisible(ownsSelection || e.isTemporary());
  366       }
  367   
  368   
  369       /**
  370        * Selects word based on the MouseEvent
  371        */
  372       private void selectWord(MouseEvent e) {
  373           if (selectedWordEvent != null
  374               && selectedWordEvent.getX() == e.getX()
  375               && selectedWordEvent.getY() == e.getY()) {
  376               //we already done selection for this
  377               return;
  378           }
  379                       Action a = null;
  380                       ActionMap map = getComponent().getActionMap();
  381                       if (map != null) {
  382                           a = map.get(DefaultEditorKit.selectWordAction);
  383                       }
  384                       if (a == null) {
  385                           if (selectWord == null) {
  386                               selectWord = new DefaultEditorKit.SelectWordAction();
  387                           }
  388                           a = selectWord;
  389                       }
  390                       a.actionPerformed(new ActionEvent(getComponent(),
  391                                                         ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  392           selectedWordEvent = e;
  393       }
  394   
  395       // --- MouseListener methods -----------------------------------
  396   
  397       /**
  398        * Called when the mouse is clicked.  If the click was generated
  399        * from button1, a double click selects a word,
  400        * and a triple click the current line.
  401        *
  402        * @param e the mouse event
  403        * @see MouseListener#mouseClicked
  404        */
  405       public void mouseClicked(MouseEvent e) {
  406           int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
  407   
  408           if (! e.isConsumed()) {
  409               if (SwingUtilities.isLeftMouseButton(e)) {
  410                   // mouse 1 behavior
  411                   if(nclicks == 1) {
  412                       selectedWordEvent = null;
  413                   } else if(nclicks == 2
  414                             && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  415                       selectWord(e);
  416                       selectedWordEvent = null;
  417                   } else if(nclicks == 3
  418                             && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  419                       Action a = null;
  420                       ActionMap map = getComponent().getActionMap();
  421                       if (map != null) {
  422                           a = map.get(DefaultEditorKit.selectLineAction);
  423                       }
  424                       if (a == null) {
  425                           if (selectLine == null) {
  426                               selectLine = new DefaultEditorKit.SelectLineAction();
  427                           }
  428                           a = selectLine;
  429                       }
  430                       a.actionPerformed(new ActionEvent(getComponent(),
  431                                                         ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
  432                   }
  433               } else if (SwingUtilities.isMiddleMouseButton(e)) {
  434                   // mouse 2 behavior
  435                   if (nclicks == 1 && component.isEditable() && component.isEnabled()
  436                       && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  437                       // paste system selection, if it exists
  438                       JTextComponent c = (JTextComponent) e.getSource();
  439                       if (c != null) {
  440                           try {
  441                               Toolkit tk = c.getToolkit();
  442                               Clipboard buffer = tk.getSystemSelection();
  443                               if (buffer != null) {
  444                                   // platform supports system selections, update it.
  445                                   adjustCaret(e);
  446                                   TransferHandler th = c.getTransferHandler();
  447                                   if (th != null) {
  448                                       Transferable trans = null;
  449   
  450                                       try {
  451                                           trans = buffer.getContents(null);
  452                                       } catch (IllegalStateException ise) {
  453                                           // clipboard was unavailable
  454                                           UIManager.getLookAndFeel().provideErrorFeedback(c);
  455                                       }
  456   
  457                                       if (trans != null) {
  458                                           th.importData(c, trans);
  459                                       }
  460                                   }
  461                                   adjustFocus(true);
  462                               }
  463                           } catch (HeadlessException he) {
  464                               // do nothing... there is no system clipboard
  465                           }
  466                       }
  467                   }
  468               }
  469           }
  470       }
  471   
  472       /**
  473        * If button 1 is pressed, this is implemented to
  474        * request focus on the associated text component,
  475        * and to set the caret position. If the shift key is held down,
  476        * the caret will be moved, potentially resulting in a selection,
  477        * otherwise the
  478        * caret position will be set to the new location.  If the component
  479        * is not enabled, there will be no request for focus.
  480        *
  481        * @param e the mouse event
  482        * @see MouseListener#mousePressed
  483        */
  484       public void mousePressed(MouseEvent e) {
  485           int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e);
  486   
  487           if (SwingUtilities.isLeftMouseButton(e)) {
  488               if (e.isConsumed()) {
  489                   shouldHandleRelease = true;
  490               } else {
  491                   shouldHandleRelease = false;
  492                   adjustCaretAndFocus(e);
  493                   if (nclicks == 2
  494                       && SwingUtilities2.canEventAccessSystemClipboard(e)) {
  495                       selectWord(e);
  496                   }
  497               }
  498           }
  499       }
  500   
  501       void adjustCaretAndFocus(MouseEvent e) {
  502           adjustCaret(e);
  503           adjustFocus(false);
  504       }
  505   
  506       /**
  507        * Adjusts the caret location based on the MouseEvent.
  508        */
  509       private void adjustCaret(MouseEvent e) {
  510           if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
  511               getDot() != -1) {
  512               moveCaret(e);
  513           } else {
  514               positionCaret(e);
  515           }
  516       }
  517   
  518       /**
  519        * Adjusts the focus, if necessary.
  520        *
  521        * @param inWindow if true indicates requestFocusInWindow should be used
  522        */
  523       private void adjustFocus(boolean inWindow) {
  524           if ((component != null) && component.isEnabled() &&
  525                                      component.isRequestFocusEnabled()) {
  526               if (inWindow) {
  527                   component.requestFocusInWindow();
  528               }
  529               else {
  530                   component.requestFocus();
  531               }
  532           }
  533       }
  534   
  535       /**
  536        * Called when the mouse is released.
  537        *
  538        * @param e the mouse event
  539        * @see MouseListener#mouseReleased
  540        */
  541       public void mouseReleased(MouseEvent e) {
  542           if (!e.isConsumed()
  543                   && shouldHandleRelease
  544                   && SwingUtilities.isLeftMouseButton(e)) {
  545   
  546               adjustCaretAndFocus(e);
  547           }
  548       }
  549   
  550       /**
  551        * Called when the mouse enters a region.
  552        *
  553        * @param e the mouse event
  554        * @see MouseListener#mouseEntered
  555        */
  556       public void mouseEntered(MouseEvent e) {
  557       }
  558   
  559       /**
  560        * Called when the mouse exits a region.
  561        *
  562        * @param e the mouse event
  563        * @see MouseListener#mouseExited
  564        */
  565       public void mouseExited(MouseEvent e) {
  566       }
  567   
  568       // --- MouseMotionListener methods -------------------------
  569   
  570       /**
  571        * Moves the caret position
  572        * according to the mouse pointer's current
  573        * location.  This effectively extends the
  574        * selection.  By default, this is only done
  575        * for mouse button 1.
  576        *
  577        * @param e the mouse event
  578        * @see MouseMotionListener#mouseDragged
  579        */
  580       public void mouseDragged(MouseEvent e) {
  581           if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
  582               moveCaret(e);
  583           }
  584       }
  585   
  586       /**
  587        * Called when the mouse is moved.
  588        *
  589        * @param e the mouse event
  590        * @see MouseMotionListener#mouseMoved
  591        */
  592       public void mouseMoved(MouseEvent e) {
  593       }
  594   
  595       // ---- Caret methods ---------------------------------
  596   
  597       /**
  598        * Renders the caret as a vertical line.  If this is reimplemented
  599        * the damage method should also be reimplemented as it assumes the
  600        * shape of the caret is a vertical line.  Sets the caret color to
  601        * the value returned by getCaretColor().
  602        * <p>
  603        * If there are multiple text directions present in the associated
  604        * document, a flag indicating the caret bias will be rendered.
  605        * This will occur only if the associated document is a subclass
  606        * of AbstractDocument and there are multiple bidi levels present
  607        * in the bidi element structure (i.e. the text has multiple
  608        * directions associated with it).
  609        *
  610        * @param g the graphics context
  611        * @see #damage
  612        */
  613       public void paint(Graphics g) {
  614           if(isVisible()) {
  615               try {
  616                   TextUI mapper = component.getUI();
  617                   Rectangle r = mapper.modelToView(component, dot, dotBias);
  618   
  619                   if ((r == null) || ((r.width == 0) && (r.height == 0))) {
  620                       return;
  621                   }
  622                   if (width > 0 && height > 0 &&
  623                                   !this._contains(r.x, r.y, r.width, r.height)) {
  624                       // We seem to have gotten out of sync and no longer
  625                       // contain the right location, adjust accordingly.
  626                       Rectangle clip = g.getClipBounds();
  627   
  628                       if (clip != null && !clip.contains(this)) {
  629                           // Clip doesn't contain the old location, force it
  630                           // to be repainted lest we leave a caret around.
  631                           repaint();
  632                       }
  633                       // This will potentially cause a repaint of something
  634                       // we're already repainting, but without changing the
  635                       // semantics of damage we can't really get around this.
  636                       damage(r);
  637                   }
  638                   g.setColor(component.getCaretColor());
  639                   int paintWidth = getCaretWidth(r.height);
  640                   r.x -= paintWidth  >> 1;
  641                   g.fillRect(r.x, r.y, paintWidth, r.height);
  642   
  643                   // see if we should paint a flag to indicate the bias
  644                   // of the caret.
  645                   // PENDING(prinz) this should be done through
  646                   // protected methods so that alternative LAF
  647                   // will show bidi information.
  648                   Document doc = component.getDocument();
  649                   if (doc instanceof AbstractDocument) {
  650                       Element bidi = ((AbstractDocument)doc).getBidiRootElement();
  651                       if ((bidi != null) && (bidi.getElementCount() > 1)) {
  652                           // there are multiple directions present.
  653                           flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
  654                           flagYPoints[0] = r.y;
  655                           flagXPoints[1] = flagXPoints[0];
  656                           flagYPoints[1] = flagYPoints[0] + 4;
  657                           flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
  658                           flagYPoints[2] = flagYPoints[0];
  659                           g.fillPolygon(flagXPoints, flagYPoints, 3);
  660                       }
  661                   }
  662               } catch (BadLocationException e) {
  663                   // can't render I guess
  664                   //System.err.println("Can't render cursor");
  665               }
  666           }
  667       }
  668   
  669       /**
  670        * Called when the UI is being installed into the
  671        * interface of a JTextComponent.  This can be used
  672        * to gain access to the model that is being navigated
  673        * by the implementation of this interface.  Sets the dot
  674        * and mark to 0, and establishes document, property change,
  675        * focus, mouse, and mouse motion listeners.
  676        *
  677        * @param c the component
  678        * @see Caret#install
  679        */
  680       public void install(JTextComponent c) {
  681           component = c;
  682           Document doc = c.getDocument();
  683           dot = mark = 0;
  684           dotLTR = markLTR = true;
  685           dotBias = markBias = Position.Bias.Forward;
  686           if (doc != null) {
  687               doc.addDocumentListener(handler);
  688           }
  689           c.addPropertyChangeListener(handler);
  690           c.addFocusListener(this);
  691           c.addMouseListener(this);
  692           c.addMouseMotionListener(this);
  693   
  694           // if the component already has focus, it won't
  695           // be notified.
  696           if (component.hasFocus()) {
  697               focusGained(null);
  698           }
  699   
  700           Number ratio = (Number) c.getClientProperty("caretAspectRatio");
  701           if (ratio != null) {
  702               aspectRatio = ratio.floatValue();
  703           } else {
  704               aspectRatio = -1;
  705           }
  706   
  707           Integer width = (Integer) c.getClientProperty("caretWidth");
  708           if (width != null) {
  709               caretWidth = width.intValue();
  710           } else {
  711               caretWidth = -1;
  712           }
  713       }
  714   
  715       /**
  716        * Called when the UI is being removed from the
  717        * interface of a JTextComponent.  This is used to
  718        * unregister any listeners that were attached.
  719        *
  720        * @param c the component
  721        * @see Caret#deinstall
  722        */
  723       public void deinstall(JTextComponent c) {
  724           c.removeMouseListener(this);
  725           c.removeMouseMotionListener(this);
  726           c.removeFocusListener(this);
  727           c.removePropertyChangeListener(handler);
  728           Document doc = c.getDocument();
  729           if (doc != null) {
  730               doc.removeDocumentListener(handler);
  731           }
  732           synchronized(this) {
  733               component = null;
  734           }
  735           if (flasher != null) {
  736               flasher.stop();
  737           }
  738   
  739   
  740       }
  741   
  742       /**
  743        * Adds a listener to track whenever the caret position has
  744        * been changed.
  745        *
  746        * @param l the listener
  747        * @see Caret#addChangeListener
  748        */
  749       public void addChangeListener(ChangeListener l) {
  750           listenerList.add(ChangeListener.class, l);
  751       }
  752   
  753       /**
  754        * Removes a listener that was tracking caret position changes.
  755        *
  756        * @param l the listener
  757        * @see Caret#removeChangeListener
  758        */
  759       public void removeChangeListener(ChangeListener l) {
  760           listenerList.remove(ChangeListener.class, l);
  761       }
  762   
  763       /**
  764        * Returns an array of all the change listeners
  765        * registered on this caret.
  766        *
  767        * @return all of this caret's <code>ChangeListener</code>s
  768        *         or an empty
  769        *         array if no change listeners are currently registered
  770        *
  771        * @see #addChangeListener
  772        * @see #removeChangeListener
  773        *
  774        * @since 1.4
  775        */
  776       public ChangeListener[] getChangeListeners() {
  777           return (ChangeListener[])listenerList.getListeners(
  778                   ChangeListener.class);
  779       }
  780   
  781       /**
  782        * Notifies all listeners that have registered interest for
  783        * notification on this event type.  The event instance
  784        * is lazily created using the parameters passed into
  785        * the fire method.  The listener list is processed last to first.
  786        *
  787        * @see EventListenerList
  788        */
  789       protected void fireStateChanged() {
  790           // Guaranteed to return a non-null array
  791           Object[] listeners = listenerList.getListenerList();
  792           // Process the listeners last to first, notifying
  793           // those that are interested in this event
  794           for (int i = listeners.length-2; i>=0; i-=2) {
  795               if (listeners[i]==ChangeListener.class) {
  796                   // Lazily create the event:
  797                   if (changeEvent == null)
  798                       changeEvent = new ChangeEvent(this);
  799                   ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
  800               }
  801           }
  802       }
  803   
  804       /**
  805        * Returns an array of all the objects currently registered
  806        * as <code><em>Foo</em>Listener</code>s
  807        * upon this caret.
  808        * <code><em>Foo</em>Listener</code>s are registered using the
  809        * <code>add<em>Foo</em>Listener</code> method.
  810        *
  811        * <p>
  812        *
  813        * You can specify the <code>listenerType</code> argument
  814        * with a class literal,
  815        * such as
  816        * <code><em>Foo</em>Listener.class</code>.
  817        * For example, you can query a
  818        * <code>DefaultCaret</code> <code>c</code>
  819        * for its change listeners with the following code:
  820        *
  821        * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
  822        *
  823        * If no such listeners exist, this method returns an empty array.
  824        *
  825        * @param listenerType the type of listeners requested; this parameter
  826        *          should specify an interface that descends from
  827        *          <code>java.util.EventListener</code>
  828        * @return an array of all objects registered as
  829        *          <code><em>Foo</em>Listener</code>s on this component,
  830        *          or an empty array if no such
  831        *          listeners have been added
  832        * @exception ClassCastException if <code>listenerType</code>
  833        *          doesn't specify a class or interface that implements
  834        *          <code>java.util.EventListener</code>
  835        *
  836        * @see #getChangeListeners
  837        *
  838        * @since 1.3
  839        */
  840       public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
  841           return listenerList.getListeners(listenerType);
  842       }
  843   
  844       /**
  845        * Changes the selection visibility.
  846        *
  847        * @param vis the new visibility
  848        */
  849       public void setSelectionVisible(boolean vis) {
  850           if (vis != selectionVisible) {
  851               selectionVisible = vis;
  852               if (selectionVisible) {
  853                   // show
  854                   Highlighter h = component.getHighlighter();
  855                   if ((dot != mark) && (h != null) && (selectionTag == null)) {
  856                       int p0 = Math.min(dot, mark);
  857                       int p1 = Math.max(dot, mark);
  858                       Highlighter.HighlightPainter p = getSelectionPainter();
  859                       try {
  860                           selectionTag = h.addHighlight(p0, p1, p);
  861                       } catch (BadLocationException bl) {
  862                           selectionTag = null;
  863                       }
  864                   }
  865               } else {
  866                   // hide
  867                   if (selectionTag != null) {
  868                       Highlighter h = component.getHighlighter();
  869                       h.removeHighlight(selectionTag);
  870                       selectionTag = null;
  871                   }
  872               }
  873           }
  874       }
  875   
  876       /**
  877        * Checks whether the current selection is visible.
  878        *
  879        * @return true if the selection is visible
  880        */
  881       public boolean isSelectionVisible() {
  882           return selectionVisible;
  883       }
  884   
  885       /**
  886        * Determines if the caret is currently active.
  887        * <p>
  888        * This method returns whether or not the <code>Caret</code>
  889        * is currently in a blinking state. It does not provide
  890        * information as to whether it is currently blinked on or off.
  891        * To determine if the caret is currently painted use the
  892        * <code>isVisible</code> method.
  893        *
  894        * @return <code>true</code> if active else <code>false</code>
  895        * @see #isVisible
  896        *
  897        * @since 1.5
  898        */
  899       public boolean isActive() {
  900           return active;
  901       }
  902   
  903       /**
  904        * Indicates whether or not the caret is currently visible. As the
  905        * caret flashes on and off the return value of this will change
  906        * between true, when the caret is painted, and false, when the
  907        * caret is not painted. <code>isActive</code> indicates whether
  908        * or not the caret is in a blinking state, such that it <b>can</b>
  909        * be visible, and <code>isVisible</code> indicates whether or not
  910        * the caret <b>is</b> actually visible.
  911        * <p>
  912        * Subclasses that wish to render a different flashing caret
  913        * should override paint and only paint the caret if this method
  914        * returns true.
  915        *
  916        * @return true if visible else false
  917        * @see Caret#isVisible
  918        * @see #isActive
  919        */
  920       public boolean isVisible() {
  921           return visible;
  922       }
  923   
  924       /**
  925        * Sets the caret visibility, and repaints the caret.
  926        * It is important to understand the relationship between this method,
  927        * <code>isVisible</code> and <code>isActive</code>.
  928        * Calling this method with a value of <code>true</code> activates the
  929        * caret blinking. Setting it to <code>false</code> turns it completely off.
  930        * To determine whether the blinking is active, you should call
  931        * <code>isActive</code>. In effect, <code>isActive</code> is an
  932        * appropriate corresponding "getter" method for this one.
  933        * <code>isVisible</code> can be used to fetch the current
  934        * visibility status of the caret, meaning whether or not it is currently
  935        * painted. This status will change as the caret blinks on and off.
  936        * <p>
  937        * Here's a list showing the potential return values of both
  938        * <code>isActive</code> and <code>isVisible</code>
  939        * after calling this method:
  940        * <p>
  941        * <b><code>setVisible(true)</code></b>:
  942        * <ul>
  943        *     <li>isActive(): true</li>
  944        *     <li>isVisible(): true or false depending on whether
  945        *         or not the caret is blinked on or off</li>
  946        * </ul>
  947        * <p>
  948        * <b><code>setVisible(false)</code></b>:
  949        * <ul>
  950        *     <li>isActive(): false</li>
  951        *     <li>isVisible(): false</li>
  952        * </ul>
  953        *
  954        * @param e the visibility specifier
  955        * @see #isActive
  956        * @see Caret#setVisible
  957        */
  958       public void setVisible(boolean e) {
  959           // focus lost notification can come in later after the
  960           // caret has been deinstalled, in which case the component
  961           // will be null.
  962           if (component != null) {
  963               active = e;
  964               TextUI mapper = component.getUI();
  965               if (visible != e) {
  966                   visible = e;
  967                   // repaint the caret
  968                   try {
  969                       Rectangle loc = mapper.modelToView(component, dot,dotBias);
  970                       damage(loc);
  971                   } catch (BadLocationException badloc) {
  972                       // hmm... not legally positioned
  973                   }
  974               }
  975           }
  976           if (flasher != null) {
  977               if (visible) {
  978                   flasher.start();
  979               } else {
  980                   flasher.stop();
  981               }
  982           }
  983       }
  984   
  985       /**
  986        * Sets the caret blink rate.
  987        *
  988        * @param rate the rate in milliseconds, 0 to stop blinking
  989        * @see Caret#setBlinkRate
  990        */
  991       public void setBlinkRate(int rate) {
  992           if (rate != 0) {
  993               if (flasher == null) {
  994                   flasher = new Timer(rate, handler);
  995               }
  996               flasher.setDelay(rate);
  997           } else {
  998               if (flasher != null) {
  999                   flasher.stop();
 1000                   flasher.removeActionListener(handler);
 1001                   flasher = null;
 1002               }
 1003           }
 1004       }
 1005   
 1006       /**
 1007        * Gets the caret blink rate.
 1008        *
 1009        * @return the delay in milliseconds.  If this is
 1010        *  zero the caret will not blink.
 1011        * @see Caret#getBlinkRate
 1012        */
 1013       public int getBlinkRate() {
 1014           return (flasher == null) ? 0 : flasher.getDelay();
 1015       }
 1016   
 1017       /**
 1018        * Fetches the current position of the caret.
 1019        *
 1020        * @return the position &gt;= 0
 1021        * @see Caret#getDot
 1022        */
 1023       public int getDot() {
 1024           return dot;
 1025       }
 1026   
 1027       /**
 1028        * Fetches the current position of the mark.  If there is a selection,
 1029        * the dot and mark will not be the same.
 1030        *
 1031        * @return the position &gt;= 0
 1032        * @see Caret#getMark
 1033        */
 1034       public int getMark() {
 1035           return mark;
 1036       }
 1037   
 1038       /**
 1039        * Sets the caret position and mark to the specified position,
 1040        * with a forward bias. This implicitly sets the
 1041        * selection range to zero.
 1042        *
 1043        * @param dot the position &gt;= 0
 1044        * @see #setDot(int, Position.Bias)
 1045        * @see Caret#setDot
 1046        */
 1047       public void setDot(int dot) {
 1048           setDot(dot, Position.Bias.Forward);
 1049       }
 1050   
 1051       /**
 1052        * Moves the caret position to the specified position,
 1053        * with a forward bias.
 1054        *
 1055        * @param dot the position &gt;= 0
 1056        * @see #moveDot(int, javax.swing.text.Position.Bias)
 1057        * @see Caret#moveDot
 1058        */
 1059       public void moveDot(int dot) {
 1060           moveDot(dot, Position.Bias.Forward);
 1061       }
 1062   
 1063       // ---- Bidi methods (we could put these in a subclass)
 1064   
 1065       /**
 1066        * Moves the caret position to the specified position, with the
 1067        * specified bias.
 1068        *
 1069        * @param dot the position &gt;= 0
 1070        * @param dotBias the bias for this position, not <code>null</code>
 1071        * @throws IllegalArgumentException if the bias is <code>null</code>
 1072        * @see Caret#moveDot
 1073        * @since 1.6
 1074        */
 1075       public void moveDot(int dot, Position.Bias dotBias) {
 1076           if (dotBias == null) {
 1077               throw new IllegalArgumentException("null bias");
 1078           }
 1079   
 1080           if (! component.isEnabled()) {
 1081               // don't allow selection on disabled components.
 1082               setDot(dot, dotBias);
 1083               return;
 1084           }
 1085           if (dot != this.dot) {
 1086               NavigationFilter filter = component.getNavigationFilter();
 1087   
 1088               if (filter != null) {
 1089                   filter.moveDot(getFilterBypass(), dot, dotBias);
 1090               }
 1091               else {
 1092                   handleMoveDot(dot, dotBias);
 1093               }
 1094           }
 1095       }
 1096   
 1097       void handleMoveDot(int dot, Position.Bias dotBias) {
 1098           changeCaretPosition(dot, dotBias);
 1099   
 1100           if (selectionVisible) {
 1101               Highlighter h = component.getHighlighter();
 1102               if (h != null) {
 1103                   int p0 = Math.min(dot, mark);
 1104                   int p1 = Math.max(dot, mark);
 1105   
 1106                   // if p0 == p1 then there should be no highlight, remove it if necessary
 1107                   if (p0 == p1) {
 1108                       if (selectionTag != null) {
 1109                           h.removeHighlight(selectionTag);
 1110                           selectionTag = null;
 1111                       }
 1112                   // otherwise, change or add the highlight
 1113                   } else {
 1114                       try {
 1115                           if (selectionTag != null) {
 1116                               h.changeHighlight(selectionTag, p0, p1);
 1117                           } else {
 1118                               Highlighter.HighlightPainter p = getSelectionPainter();
 1119                               selectionTag = h.addHighlight(p0, p1, p);
 1120                           }
 1121                       } catch (BadLocationException e) {
 1122                           throw new StateInvariantError("Bad caret position");
 1123                       }
 1124                   }
 1125               }
 1126           }
 1127       }
 1128   
 1129       /**
 1130        * Sets the caret position and mark to the specified position, with the
 1131        * specified bias. This implicitly sets the selection range
 1132        * to zero.
 1133        *
 1134        * @param dot the position &gt;= 0
 1135        * @param dotBias the bias for this position, not <code>null</code>
 1136        * @throws IllegalArgumentException if the bias is <code>null</code>
 1137        * @see Caret#setDot
 1138        * @since 1.6
 1139        */
 1140       public void setDot(int dot, Position.Bias dotBias) {
 1141           if (dotBias == null) {
 1142               throw new IllegalArgumentException("null bias");
 1143           }
 1144   
 1145           NavigationFilter filter = component.getNavigationFilter();
 1146   
 1147           if (filter != null) {
 1148               filter.setDot(getFilterBypass(), dot, dotBias);
 1149           }
 1150           else {
 1151               handleSetDot(dot, dotBias);
 1152           }
 1153       }
 1154   
 1155       void handleSetDot(int dot, Position.Bias dotBias) {
 1156           // move dot, if it changed
 1157           Document doc = component.getDocument();
 1158           if (doc != null) {
 1159               dot = Math.min(dot, doc.getLength());
 1160           }
 1161           dot = Math.max(dot, 0);
 1162   
 1163           // The position (0,Backward) is out of range so disallow it.
 1164           if( dot == 0 )
 1165               dotBias = Position.Bias.Forward;
 1166   
 1167           mark = dot;
 1168           if (this.dot != dot || this.dotBias != dotBias ||
 1169               selectionTag != null || forceCaretPositionChange) {
 1170               changeCaretPosition(dot, dotBias);
 1171           }
 1172           this.markBias = this.dotBias;
 1173           this.markLTR = dotLTR;
 1174           Highlighter h = component.getHighlighter();
 1175           if ((h != null) && (selectionTag != null)) {
 1176               h.removeHighlight(selectionTag);
 1177               selectionTag = null;
 1178           }
 1179       }
 1180   
 1181       /**
 1182        * Returns the bias of the caret position.
 1183        *
 1184        * @return the bias of the caret position
 1185        * @since 1.6
 1186        */
 1187       public Position.Bias getDotBias() {
 1188           return dotBias;
 1189       }
 1190   
 1191       /**
 1192        * Returns the bias of the mark.
 1193        *
 1194        * @return the bias of the mark
 1195        * @since 1.6
 1196        */
 1197       public Position.Bias getMarkBias() {
 1198           return markBias;
 1199       }
 1200   
 1201       boolean isDotLeftToRight() {
 1202           return dotLTR;
 1203       }
 1204   
 1205       boolean isMarkLeftToRight() {
 1206           return markLTR;
 1207       }
 1208   
 1209       boolean isPositionLTR(int position, Position.Bias bias) {
 1210           Document doc = component.getDocument();
 1211           if(doc instanceof AbstractDocument ) {
 1212               if(bias == Position.Bias.Backward && --position < 0)
 1213                   position = 0;
 1214               return ((AbstractDocument)doc).isLeftToRight(position, position);
 1215           }
 1216           return true;
 1217       }
 1218   
 1219       Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
 1220                                        boolean lastLTR) {
 1221           // There is an abiguous case here. That if your model looks like:
 1222           // abAB with the cursor at abB]A (visual representation of
 1223           // 3 forward) deleting could either become abB] or
 1224           // ab[B. I'ld actually prefer abB]. But, if I implement that
 1225           // a delete at abBA] would result in aBA] vs a[BA which I
 1226           // think is totally wrong. To get this right we need to know what
 1227           // was deleted. And we could get this from the bidi structure
 1228           // in the change event. So:
 1229           // PENDING: base this off what was deleted.
 1230           if(lastLTR != isPositionLTR(offset, lastBias)) {
 1231               lastBias = Position.Bias.Backward;
 1232           }
 1233           else if(lastBias != Position.Bias.Backward &&
 1234                   lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
 1235               lastBias = Position.Bias.Backward;
 1236           }
 1237           if (lastBias == Position.Bias.Backward && offset > 0) {
 1238               try {
 1239                   Segment s = new Segment();
 1240                   component.getDocument().getText(offset - 1, 1, s);
 1241                   if (s.count > 0 && s.array[s.offset] == '\n') {
 1242                       lastBias = Position.Bias.Forward;
 1243                   }
 1244               }
 1245               catch (BadLocationException ble) {}
 1246           }
 1247           return lastBias;
 1248       }
 1249   
 1250       // ---- local methods --------------------------------------------
 1251   
 1252       /**
 1253        * Sets the caret position (dot) to a new location.  This
 1254        * causes the old and new location to be repainted.  It
 1255        * also makes sure that the caret is within the visible
 1256        * region of the view, if the view is scrollable.
 1257        */
 1258       void changeCaretPosition(int dot, Position.Bias dotBias) {
 1259           // repaint the old position and set the new value of
 1260           // the dot.
 1261           repaint();
 1262   
 1263   
 1264           // Make sure the caret is visible if this window has the focus.
 1265           if (flasher != null && flasher.isRunning()) {
 1266               visible = true;
 1267               flasher.restart();
 1268           }
 1269   
 1270           // notify listeners at the caret moved
 1271           this.dot = dot;
 1272           this.dotBias = dotBias;
 1273           dotLTR = isPositionLTR(dot, dotBias);
 1274           fireStateChanged();
 1275   
 1276           updateSystemSelection();
 1277   
 1278           setMagicCaretPosition(null);
 1279   
 1280           // We try to repaint the caret later, since things
 1281           // may be unstable at the time this is called
 1282           // (i.e. we don't want to depend upon notification
 1283           // order or the fact that this might happen on
 1284           // an unsafe thread).
 1285           Runnable callRepaintNewCaret = new Runnable() {
 1286               public void run() {
 1287                   repaintNewCaret();
 1288               }
 1289           };
 1290           SwingUtilities.invokeLater(callRepaintNewCaret);
 1291       }
 1292   
 1293       /**
 1294        * Repaints the new caret position, with the
 1295        * assumption that this is happening on the
 1296        * event thread so that calling <code>modelToView</code>
 1297        * is safe.
 1298        */
 1299       void repaintNewCaret() {
 1300           if (component != null) {
 1301               TextUI mapper = component.getUI();
 1302               Document doc = component.getDocument();
 1303               if ((mapper != null) && (doc != null)) {
 1304                   // determine the new location and scroll if
 1305                   // not visible.
 1306                   Rectangle newLoc;
 1307                   try {
 1308                       newLoc = mapper.modelToView(component, this.dot, this.dotBias);
 1309                   } catch (BadLocationException e) {
 1310                       newLoc = null;
 1311                   }
 1312                   if (newLoc != null) {
 1313                       adjustVisibility(newLoc);
 1314                       // If there is no magic caret position, make one
 1315                       if (getMagicCaretPosition() == null) {
 1316                           setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
 1317                       }
 1318                   }
 1319   
 1320                   // repaint the new position
 1321                   damage(newLoc);
 1322               }
 1323           }
 1324       }
 1325   
 1326       private void updateSystemSelection() {
 1327           if ( ! SwingUtilities2.canCurrentEventAccessSystemClipboard() ) {
 1328               return;
 1329           }
 1330           if (this.dot != this.mark && component != null) {
 1331               Clipboard clip = getSystemSelection();
 1332               if (clip != null) {
 1333                   String selectedText = null;
 1334                   if (component instanceof JPasswordField
 1335                       && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
 1336                       Boolean.TRUE) {
 1337                       //fix for 4793761
 1338                       StringBuffer txt = null;
 1339                       char echoChar = ((JPasswordField)component).getEchoChar();
 1340                       int p0 = Math.min(getDot(), getMark());
 1341                       int p1 = Math.max(getDot(), getMark());
 1342                       for (int i = p0; i < p1; i++) {
 1343                           if (txt == null) {
 1344                               txt = new StringBuffer();
 1345                           }
 1346                           txt.append(echoChar);
 1347                       }
 1348                       selectedText = (txt != null) ? txt.toString() : null;
 1349                   } else {
 1350                       selectedText = component.getSelectedText();
 1351                   }
 1352                   try {
 1353                       clip.setContents(
 1354                           new StringSelection(selectedText), getClipboardOwner());
 1355   
 1356                       ownsSelection = true;
 1357                   } catch (IllegalStateException ise) {
 1358                       // clipboard was unavailable
 1359                       // no need to provide error feedback to user since updating
 1360                       // the system selection is not a user invoked action
 1361                   }
 1362               }
 1363           }
 1364       }
 1365   
 1366       private Clipboard getSystemSelection() {
 1367           try {
 1368               return component.getToolkit().getSystemSelection();
 1369           } catch (HeadlessException he) {
 1370               // do nothing... there is no system clipboard
 1371           } catch (SecurityException se) {
 1372               // do nothing... there is no allowed system clipboard
 1373           }
 1374           return null;
 1375       }
 1376   
 1377       private ClipboardOwner getClipboardOwner() {
 1378           return handler;
 1379       }
 1380   
 1381       /**
 1382        * This is invoked after the document changes to verify the current
 1383        * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
 1384        * changed where to position the dot, that resulted in the current location
 1385        * being bogus.
 1386        */
 1387       private void ensureValidPosition() {
 1388           int length = component.getDocument().getLength();
 1389           if (dot > length || mark > length) {
 1390               // Current location is bogus and filter likely vetoed the
 1391               // change, force the reset without giving the filter a
 1392               // chance at changing it.
 1393               handleSetDot(length, Position.Bias.Forward);
 1394           }
 1395       }
 1396   
 1397   
 1398       /**
 1399        * Saves the current caret position.  This is used when
 1400        * caret up/down actions occur, moving between lines
 1401        * that have uneven end positions.
 1402        *
 1403        * @param p the position
 1404        * @see #getMagicCaretPosition
 1405        */
 1406       public void setMagicCaretPosition(Point p) {
 1407           magicCaretPosition = p;
 1408       }
 1409   
 1410       /**
 1411        * Gets the saved caret position.
 1412        *
 1413        * @return the position
 1414        * see #setMagicCaretPosition
 1415        */
 1416       public Point getMagicCaretPosition() {
 1417           return magicCaretPosition;
 1418       }
 1419   
 1420       /**
 1421        * Compares this object to the specified object.
 1422        * The superclass behavior of comparing rectangles
 1423        * is not desired, so this is changed to the Object
 1424        * behavior.
 1425        *
 1426        * @param     obj   the object to compare this font with
 1427        * @return    <code>true</code> if the objects are equal;
 1428        *            <code>false</code> otherwise
 1429        */
 1430       public boolean equals(Object obj) {
 1431           return (this == obj);
 1432       }
 1433   
 1434       public String toString() {
 1435           String s = "Dot=(" + dot + ", " + dotBias + ")";
 1436           s += " Mark=(" + mark + ", " + markBias + ")";
 1437           return s;
 1438       }
 1439   
 1440       private NavigationFilter.FilterBypass getFilterBypass() {
 1441           if (filterBypass == null) {
 1442               filterBypass = new DefaultFilterBypass();
 1443           }
 1444           return filterBypass;
 1445       }
 1446   
 1447       // Rectangle.contains returns false if passed a rect with a w or h == 0,
 1448       // this won't (assuming X,Y are contained with this rectangle).
 1449       private boolean _contains(int X, int Y, int W, int H) {
 1450           int w = this.width;
 1451           int h = this.height;
 1452           if ((w | h | W | H) < 0) {
 1453               // At least one of the dimensions is negative...
 1454               return false;
 1455           }
 1456           // Note: if any dimension is zero, tests below must return false...
 1457           int x = this.x;
 1458           int y = this.y;
 1459           if (X < x || Y < y) {
 1460               return false;
 1461           }
 1462           if (W > 0) {
 1463               w += x;
 1464               W += X;
 1465               if (W <= X) {
 1466                   // X+W overflowed or W was zero, return false if...
 1467                   // either original w or W was zero or
 1468                   // x+w did not overflow or
 1469                   // the overflowed x+w is smaller than the overflowed X+W
 1470                   if (w >= x || W > w) return false;
 1471               } else {
 1472                   // X+W did not overflow and W was not zero, return false if...
 1473                   // original w was zero or
 1474                   // x+w did not overflow and x+w is smaller than X+W
 1475                   if (w >= x && W > w) return false;
 1476               }
 1477           }
 1478           else if ((x + w) < X) {
 1479               return false;
 1480           }
 1481           if (H > 0) {
 1482               h += y;
 1483               H += Y;
 1484               if (H <= Y) {
 1485                   if (h >= y || H > h) return false;
 1486               } else {
 1487                   if (h >= y && H > h) return false;
 1488               }
 1489           }
 1490           else if ((y + h) < Y) {
 1491               return false;
 1492           }
 1493           return true;
 1494       }
 1495   
 1496       int getCaretWidth(int height) {
 1497           if (aspectRatio > -1) {
 1498               return (int) (aspectRatio * height) + 1;
 1499           }
 1500   
 1501           if (caretWidth > -1) {
 1502               return caretWidth;
 1503           }
 1504   
 1505           return 1;
 1506       }
 1507   
 1508       // --- serialization ---------------------------------------------
 1509   
 1510       private void readObject(ObjectInputStream s)
 1511         throws ClassNotFoundException, IOException
 1512       {
 1513           s.defaultReadObject();
 1514           handler = new Handler();
 1515           if (!s.readBoolean()) {
 1516               dotBias = Position.Bias.Forward;
 1517           }
 1518           else {
 1519               dotBias = Position.Bias.Backward;
 1520           }
 1521           if (!s.readBoolean()) {
 1522               markBias = Position.Bias.Forward;
 1523           }
 1524           else {
 1525               markBias = Position.Bias.Backward;
 1526           }
 1527       }
 1528   
 1529       private void writeObject(ObjectOutputStream s) throws IOException {
 1530           s.defaultWriteObject();
 1531           s.writeBoolean((dotBias == Position.Bias.Backward));
 1532           s.writeBoolean((markBias == Position.Bias.Backward));
 1533       }
 1534   
 1535       // ---- member variables ------------------------------------------
 1536   
 1537       /**
 1538        * The event listener list.
 1539        */
 1540       protected EventListenerList listenerList = new EventListenerList();
 1541   
 1542       /**
 1543        * The change event for the model.
 1544        * Only one ChangeEvent is needed per model instance since the
 1545        * event's only (read-only) state is the source property.  The source
 1546        * of events generated here is always "this".
 1547        */
 1548       protected transient ChangeEvent changeEvent = null;
 1549   
 1550       // package-private to avoid inner classes private member
 1551       // access bug
 1552       JTextComponent component;
 1553   
 1554       int updatePolicy = UPDATE_WHEN_ON_EDT;
 1555       boolean visible;
 1556       boolean active;
 1557       int dot;
 1558       int mark;
 1559       Object selectionTag;
 1560       boolean selectionVisible;
 1561       Timer flasher;
 1562       Point magicCaretPosition;
 1563       transient Position.Bias dotBias;
 1564       transient Position.Bias markBias;
 1565       boolean dotLTR;
 1566       boolean markLTR;
 1567       transient Handler handler = new Handler();
 1568       transient private int[] flagXPoints = new int[3];
 1569       transient private int[] flagYPoints = new int[3];
 1570       private transient NavigationFilter.FilterBypass filterBypass;
 1571       static private transient Action selectWord = null;
 1572       static private transient Action selectLine = null;
 1573       /**
 1574        * This is used to indicate if the caret currently owns the selection.
 1575        * This is always false if the system does not support the system
 1576        * clipboard.
 1577        */
 1578       private boolean ownsSelection;
 1579   
 1580       /**
 1581        * If this is true, the location of the dot is updated regardless of
 1582        * the current location. This is set in the DocumentListener
 1583        * such that even if the model location of dot hasn't changed (perhaps do
 1584        * to a forward delete) the visual location is updated.
 1585        */
 1586       private boolean forceCaretPositionChange;
 1587   
 1588       /**
 1589        * Whether or not mouseReleased should adjust the caret and focus.
 1590        * This flag is set by mousePressed if it wanted to adjust the caret
 1591        * and focus but couldn't because of a possible DnD operation.
 1592        */
 1593       private transient boolean shouldHandleRelease;
 1594   
 1595   
 1596       /**
 1597        * holds last MouseEvent which caused the word selection
 1598        */
 1599       private transient MouseEvent selectedWordEvent = null;
 1600   
 1601       /**
 1602        * The width of the caret in pixels.
 1603        */
 1604       private int caretWidth = -1;
 1605       private float aspectRatio = -1;
 1606   
 1607       class SafeScroller implements Runnable {
 1608   
 1609           SafeScroller(Rectangle r) {
 1610               this.r = r;
 1611           }
 1612   
 1613           public void run() {
 1614               if (component != null) {
 1615                   component.scrollRectToVisible(r);
 1616               }
 1617           }
 1618   
 1619           Rectangle r;
 1620       }
 1621   
 1622   
 1623       class Handler implements PropertyChangeListener, DocumentListener, ActionListener, ClipboardOwner {
 1624   
 1625           // --- ActionListener methods ----------------------------------
 1626   
 1627           /**
 1628            * Invoked when the blink timer fires.  This is called
 1629            * asynchronously.  The simply changes the visibility
 1630            * and repaints the rectangle that last bounded the caret.
 1631            *
 1632            * @param e the action event
 1633            */
 1634           public void actionPerformed(ActionEvent e) {
 1635               if (width == 0 || height == 0) {
 1636                   // setVisible(true) will cause a scroll, only do this if the
 1637                   // new location is really valid.
 1638                   if (component != null) {
 1639                       TextUI mapper = component.getUI();
 1640                       try {
 1641                           Rectangle r = mapper.modelToView(component, dot,
 1642                                                            dotBias);
 1643                           if (r != null && r.width != 0 && r.height != 0) {
 1644                               damage(r);
 1645                           }
 1646                       } catch (BadLocationException ble) {
 1647                       }
 1648                   }
 1649               }
 1650               visible = !visible;
 1651               repaint();
 1652           }
 1653   
 1654           // --- DocumentListener methods --------------------------------
 1655   
 1656           /**
 1657            * Updates the dot and mark if they were changed by
 1658            * the insertion.
 1659            *
 1660            * @param e the document event
 1661            * @see DocumentListener#insertUpdate
 1662            */
 1663           public void insertUpdate(DocumentEvent e) {
 1664               if (getUpdatePolicy() == NEVER_UPDATE ||
 1665                       (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
 1666                       !SwingUtilities.isEventDispatchThread())) {
 1667   
 1668                   if ((e.getOffset() <= dot || e.getOffset() <= mark)
 1669                           && selectionTag != null) {
 1670                       try {
 1671                           component.getHighlighter().changeHighlight(selectionTag,
 1672                                   Math.min(dot, mark), Math.max(dot, mark));
 1673                       } catch (BadLocationException e1) {
 1674                           e1.printStackTrace();
 1675                       }
 1676                   }
 1677                   return;
 1678               }
 1679               int adjust = 0;
 1680               int offset = e.getOffset();
 1681               int length = e.getLength();
 1682               int newDot = dot;
 1683               short changed = 0;
 1684   
 1685               if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
 1686                   setDot(offset + length);
 1687                   return;
 1688               }
 1689               if (newDot >= offset) {
 1690                   newDot += length;
 1691                   changed |= 1;
 1692               }
 1693               int newMark = mark;
 1694               if (newMark >= offset) {
 1695                   newMark += length;
 1696                   changed |= 2;
 1697               }
 1698   
 1699               if (changed != 0) {
 1700                   Position.Bias dotBias = DefaultCaret.this.dotBias;
 1701                   if (dot == offset) {
 1702                       Document doc = component.getDocument();
 1703                       boolean isNewline;
 1704                       try {
 1705                           Segment s = new Segment();
 1706                           doc.getText(newDot - 1, 1, s);
 1707                           isNewline = (s.count > 0 &&
 1708                                   s.array[s.offset] == '\n');
 1709                       } catch (BadLocationException ble) {
 1710                           isNewline = false;
 1711                       }
 1712                       if (isNewline) {
 1713                           dotBias = Position.Bias.Forward;
 1714                       } else {
 1715                           dotBias = Position.Bias.Backward;
 1716                       }
 1717                   }
 1718                   if (newMark == newDot) {
 1719                       setDot(newDot, dotBias);
 1720                       ensureValidPosition();
 1721                   }
 1722                   else {
 1723                       setDot(newMark, markBias);
 1724                       if (getDot() == newMark) {
 1725                           // Due this test in case the filter vetoed the
 1726                           // change in which case this probably won't be
 1727                           // valid either.
 1728                           moveDot(newDot, dotBias);
 1729                       }
 1730                       ensureValidPosition();
 1731                   }
 1732               }
 1733           }
 1734   
 1735           /**
 1736            * Updates the dot and mark if they were changed
 1737            * by the removal.
 1738            *
 1739            * @param e the document event
 1740            * @see DocumentListener#removeUpdate
 1741            */
 1742           public void removeUpdate(DocumentEvent e) {
 1743               if (getUpdatePolicy() == NEVER_UPDATE ||
 1744                       (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
 1745                       !SwingUtilities.isEventDispatchThread())) {
 1746   
 1747                   int length = component.getDocument().getLength();
 1748                   dot = Math.min(dot, length);
 1749                   mark = Math.min(mark, length);
 1750                   if ((e.getOffset() < dot || e.getOffset() < mark)
 1751                           && selectionTag != null) {
 1752                       try {
 1753                           component.getHighlighter().changeHighlight(selectionTag,
 1754                                   Math.min(dot, mark), Math.max(dot, mark));
 1755                       } catch (BadLocationException e1) {
 1756                           e1.printStackTrace();
 1757                       }
 1758                   }
 1759                   return;
 1760               }
 1761               int offs0 = e.getOffset();
 1762               int offs1 = offs0 + e.getLength();
 1763               int adjust = 0;
 1764               int newDot = dot;
 1765               boolean adjustDotBias = false;
 1766               int newMark = mark;
 1767               boolean adjustMarkBias = false;
 1768   
 1769               if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
 1770                   setDot(offs0);
 1771                   return;
 1772               }
 1773               if (newDot >= offs1) {
 1774                   newDot -= (offs1 - offs0);
 1775                   if(newDot == offs1) {
 1776                       adjustDotBias = true;
 1777                   }
 1778               } else if (newDot >= offs0) {
 1779                   newDot = offs0;
 1780                   adjustDotBias = true;
 1781               }
 1782               if (newMark >= offs1) {
 1783                   newMark -= (offs1 - offs0);
 1784                   if(newMark == offs1) {
 1785                       adjustMarkBias = true;
 1786                   }
 1787               } else if (newMark >= offs0) {
 1788                   newMark = offs0;
 1789                   adjustMarkBias = true;
 1790               }
 1791               if (newMark == newDot) {
 1792                   forceCaretPositionChange = true;
 1793                   try {
 1794                       setDot(newDot, guessBiasForOffset(newDot, dotBias,
 1795                               dotLTR));
 1796                   } finally {
 1797                       forceCaretPositionChange = false;
 1798                   }
 1799                   ensureValidPosition();
 1800               } else {
 1801                   Position.Bias dotBias = DefaultCaret.this.dotBias;
 1802                   Position.Bias markBias = DefaultCaret.this.markBias;
 1803                   if(adjustDotBias) {
 1804                       dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
 1805                   }
 1806                   if(adjustMarkBias) {
 1807                       markBias = guessBiasForOffset(mark, markBias, markLTR);
 1808                   }
 1809                   setDot(newMark, markBias);
 1810                   if (getDot() == newMark) {
 1811                       // Due this test in case the filter vetoed the change
 1812                       // in which case this probably won't be valid either.
 1813                       moveDot(newDot, dotBias);
 1814                   }
 1815                   ensureValidPosition();
 1816               }
 1817           }
 1818   
 1819           /**
 1820            * Gives notification that an attribute or set of attributes changed.
 1821            *
 1822            * @param e the document event
 1823            * @see DocumentListener#changedUpdate
 1824            */
 1825           public void changedUpdate(DocumentEvent e) {
 1826               if (getUpdatePolicy() == NEVER_UPDATE ||
 1827                       (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
 1828                       !SwingUtilities.isEventDispatchThread())) {
 1829                   return;
 1830               }
 1831               if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
 1832                   setDot(e.getOffset() + e.getLength());
 1833               }
 1834           }
 1835   
 1836           // --- PropertyChangeListener methods -----------------------
 1837   
 1838           /**
 1839            * This method gets called when a bound property is changed.
 1840            * We are looking for document changes on the editor.
 1841            */
 1842           public void propertyChange(PropertyChangeEvent evt) {
 1843               Object oldValue = evt.getOldValue();
 1844               Object newValue = evt.getNewValue();
 1845               if ((oldValue instanceof Document) || (newValue instanceof Document)) {
 1846                   setDot(0);
 1847                   if (oldValue != null) {
 1848                       ((Document)oldValue).removeDocumentListener(this);
 1849                   }
 1850                   if (newValue != null) {
 1851                       ((Document)newValue).addDocumentListener(this);
 1852                   }
 1853               } else if("enabled".equals(evt.getPropertyName())) {
 1854                   Boolean enabled = (Boolean) evt.getNewValue();
 1855                   if(component.isFocusOwner()) {
 1856                       if(enabled == Boolean.TRUE) {
 1857                           if(component.isEditable()) {
 1858                               setVisible(true);
 1859                           }
 1860                           setSelectionVisible(true);
 1861                       } else {
 1862                           setVisible(false);
 1863                           setSelectionVisible(false);
 1864                       }
 1865                   }
 1866               } else if("caretWidth".equals(evt.getPropertyName())) {
 1867                   Integer newWidth = (Integer) evt.getNewValue();
 1868                   if (newWidth != null) {
 1869                       caretWidth = newWidth.intValue();
 1870                   } else {
 1871                       caretWidth = -1;
 1872                   }
 1873                   repaint();
 1874               } else if("caretAspectRatio".equals(evt.getPropertyName())) {
 1875                   Number newRatio = (Number) evt.getNewValue();
 1876                   if (newRatio != null) {
 1877                       aspectRatio = newRatio.floatValue();
 1878                   } else {
 1879                       aspectRatio = -1;
 1880                   }
 1881                   repaint();
 1882               }
 1883           }
 1884   
 1885   
 1886           //
 1887           // ClipboardOwner
 1888           //
 1889           /**
 1890            * Toggles the visibility of the selection when ownership is lost.
 1891            */
 1892           public void lostOwnership(Clipboard clipboard,
 1893                                         Transferable contents) {
 1894               if (ownsSelection) {
 1895                   ownsSelection = false;
 1896                   if (component != null && !component.hasFocus()) {
 1897                       setSelectionVisible(false);
 1898                   }
 1899               }
 1900           }
 1901       }
 1902   
 1903   
 1904       private class DefaultFilterBypass extends NavigationFilter.FilterBypass {
 1905           public Caret getCaret() {
 1906               return DefaultCaret.this;
 1907           }
 1908   
 1909           public void setDot(int dot, Position.Bias bias) {
 1910               handleSetDot(dot, bias);
 1911           }
 1912   
 1913           public void moveDot(int dot, Position.Bias bias) {
 1914               handleMoveDot(dot, bias);
 1915           }
 1916       }
 1917   }

Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]