- /*
- * @(#)DefaultCaret.java 1.138 04/06/24
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.text;
-
- import java.awt.*;
- import java.awt.event.*;
- import java.awt.datatransfer.*;
- import java.beans.*;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.*;
- import javax.swing.*;
- import javax.swing.event.*;
- import javax.swing.plaf.*;
- import java.util.EventListener;
- import com.sun.java.swing.SwingUtilities2;
-
- /**
- * A default implementation of Caret. The caret is rendered as
- * a vertical line in the color specified by the CaretColor property
- * of the associated JTextComponent. It can blink at the rate specified
- * by the BlinkRate property.
- * <p>
- * This implementation expects two sources of asynchronous notification.
- * The timer thread fires asynchronously, and causes the caret to simply
- * repaint the most recent bounding box. The caret also tracks change
- * as the document is modified. Typically this will happen on the
- * event dispatch thread as a result of some mouse or keyboard event.
- * The caret behavior on both synchronous and asynchronous documents updates
- * is controlled by <code>UpdatePolicy</code> property. The repaint of the
- * new caret location will occur on the event thread in any case, as calls to
- * <code>modelToView</code> are only safe on the event thread.
- * <p>
- * The caret acts as a mouse and focus listener on the text component
- * it has been installed in, and defines the caret semantics based upon
- * those events. The listener methods can be reimplemented to change the
- * semantics.
- * By default, the first mouse button will be used to set focus and caret
- * position. Dragging the mouse pointer with the first mouse button will
- * sweep out a selection that is contiguous in the model. If the associated
- * text component is editable, the caret will become visible when focus
- * is gained, and invisible when focus is lost.
- * <p>
- * The Highlighter bound to the associated text component is used to
- * render the selection by default.
- * Selection appearance can be customized by supplying a
- * painter to use for the highlights. By default a painter is used that
- * will render a solid color as specified in the associated text component
- * in the <code>SelectionColor</code> property. This can easily be changed
- * by reimplementing the
- * <a href="#getSelectionHighlighter">getSelectionHighlighter</a>
- * method.
- * <p>
- * A customized caret appearance can be achieved by reimplementing
- * the paint method. If the paint method is changed, the damage method
- * should also be reimplemented to cause a repaint for the area needed
- * to render the caret. The caret extends the Rectangle class which
- * is used to hold the bounding box for where the caret was last rendered.
- * This enables the caret to repaint in a thread-safe manner when the
- * caret moves without making a call to modelToView which is unstable
- * between model updates and view repair (i.e. the order of delivery
- * to DocumentListeners is not guaranteed).
- * <p>
- * The magic caret position is set to null when the caret position changes.
- * A timer is used to determine the new location (after the caret change).
- * When the timer fires, if the magic caret position is still null it is
- * reset to the current caret position. Any actions that change
- * the caret position and want the magic caret position to remain the
- * same, must remember the magic caret position, change the cursor, and
- * then set the magic caret position to its original value. This has the
- * benefit that only actions that want the magic caret position to persist
- * (such as open/down) need to know about it.
- * <p>
- * <strong>Warning:</strong>
- * Serialized objects of this class will not be compatible with
- * future Swing releases. The current serialization support is
- * appropriate for short term storage or RMI between applications running
- * the same version of Swing. As of 1.4, support for long term storage
- * of all JavaBeans<sup><font size="-2">TM</font></sup>
- * has been added to the <code>java.beans</code> package.
- * Please see {@link java.beans.XMLEncoder}.
- *
- * @author Timothy Prinzing
- * @version 1.138 06/24/04
- * @see Caret
- */
- public class DefaultCaret extends Rectangle implements Caret, FocusListener, MouseListener, MouseMotionListener {
-
- /**
- * Indicates that the caret position is to be updated only when
- * document changes are performed on the Event Dispatching Thread.
- * @see #setUpdatePolicy
- * @see #getUpdatePolicy
- * @since 1.5
- */
- public static final int UPDATE_WHEN_ON_EDT = 0;
-
- /**
- * Indicates that the caret should remain at the same
- * absolute position in the document regardless of any document
- * updates, except when the document length becomes less than
- * the current caret position due to removal. In that case the caret
- * position is adjusted to the end of the document.
- *
- * @see #setUpdatePolicy
- * @see #getUpdatePolicy
- * @since 1.5
- */
- public static final int NEVER_UPDATE = 1;
-
- /**
- * Indicates that the caret position is to be <b>always</b>
- * updated accordingly to the document changes regardless whether
- * the document updates are performed on the Event Dispatching Thread
- * or not.
- *
- * @see #setUpdatePolicy
- * @see #getUpdatePolicy
- * @since 1.5
- */
- public static final int ALWAYS_UPDATE = 2;
-
- /**
- * Constructs a default caret.
- */
- public DefaultCaret() {
- }
-
- /**
- * Sets the caret movement policy on the document updates. Normally
- * the caret updates its absolute position within the document on
- * insertions occurred before or at the caret position and
- * on removals before the caret position. 'Absolute position'
- * means here the position relative to the start of the document.
- * For example if
- * a character is typed within editable text component it is inserted
- * at the caret position and the caret moves to the next absolute
- * position within the document due to insertion and if
- * <code>BACKSPACE</code> is typed then caret decreases its absolute
- * position due to removal of a character before it. Sometimes
- * it may be useful to turn off the caret position updates so that
- * the caret stays at the same absolute position within the
- * document position regardless of any document updates.
- * <p>
- * The following update policies are allowed:
- * <ul>
- * <li><code>NEVER_UPDATE</code>: the caret stays at the same
- * absolute position in the document regardless of any document
- * updates, except when document length becomes less than
- * the current caret position due to removal. In that case caret
- * position is adjusted to the end of the document.
- * The caret doesn't try to keep itself visible by scrolling
- * the associated view when using this policy. </li>
- * <li><code>ALWAYS_UPDATE</code>: the caret always tracks document
- * changes. For regular changes it increases its position
- * if an insertion occurs before or at its current position,
- * and decreases position if a removal occurs before
- * its current position. For undo/redo updates it is always
- * moved to the position where update occurred. The caret
- * also tries to keep itself visible by calling
- * <code>adjustVisibility</code> method.</li>
- * <li><code>UPDATE_WHEN_ON_EDT</code>: acts like <code>ALWAYS_UPDATE</code>
- * if the document updates are performed on the Event Dispatching Thread
- * and like <code>NEVER_UPDATE</code> if updates are performed on
- * other thread. </li>
- * </ul> <p>
- * The default property value is <code>UPDATE_WHEN_ON_EDT</code>.
- *
- * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
- * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
- * @throws IllegalArgumentException if invalid value is passed
- *
- * @see #getUpdatePolicy
- * @see #adjustVisibility
- * @see #UPDATE_WHEN_ON_EDT
- * @see #NEVER_UPDATE
- * @see #ALWAYS_UPDATE
- *
- * @since 1.5
- */
- public void setUpdatePolicy(int policy) {
- updatePolicy = policy;
- }
-
- /**
- * Gets the caret movement policy on document updates.
- *
- * @return one of the following values : <code>UPDATE_WHEN_ON_EDT</code>,
- * <code>NEVER_UPDATE</code>, <code>ALWAYS_UPDATE</code>
- *
- * @see #setUpdatePolicy
- * @see #UPDATE_WHEN_ON_EDT
- * @see #NEVER_UPDATE
- * @see #ALWAYS_UPDATE
- *
- * @since 1.5
- */
- public int getUpdatePolicy() {
- return updatePolicy;
- }
-
- /**
- * Gets the text editor component that this caret is
- * is bound to.
- *
- * @return the component
- */
- protected final JTextComponent getComponent() {
- return component;
- }
-
- /**
- * Cause the caret to be painted. The repaint
- * area is the bounding box of the caret (i.e.
- * the caret rectangle or <em>this</em>).
- * <p>
- * This method is thread safe, although most Swing methods
- * are not. Please see
- * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">
- * Threads and Swing</A> for more information.
- */
- protected final synchronized void repaint() {
- if (component != null) {
- component.repaint(x, y, width, height);
- }
- }
-
- /**
- * Damages the area surrounding the caret to cause
- * it to be repainted in a new location. If paint()
- * is reimplemented, this method should also be
- * reimplemented. This method should update the
- * caret bounds (x, y, width, and height).
- *
- * @param r the current location of the caret
- * @see #paint
- */
- protected synchronized void damage(Rectangle r) {
- if (r != null) {
- int damageWidth = getCaretWidth(r.height);
- x = r.x - 4 - (damageWidth >> 1);
- y = r.y;
- width = 9 + damageWidth;
- height = r.height;
- repaint();
- }
- }
-
- /**
- * Scrolls the associated view (if necessary) to make
- * the caret visible. Since how this should be done
- * is somewhat of a policy, this method can be
- * reimplemented to change the behavior. By default
- * the scrollRectToVisible method is called on the
- * associated component.
- *
- * @param nloc the new position to scroll to
- */
- protected void adjustVisibility(Rectangle nloc) {
- if(component == null) {
- return;
- }
- if (SwingUtilities.isEventDispatchThread()) {
- component.scrollRectToVisible(nloc);
- } else {
- SwingUtilities.invokeLater(new SafeScroller(nloc));
- }
- }
-
- /**
- * Gets the painter for the Highlighter.
- *
- * @return the painter
- */
- protected Highlighter.HighlightPainter getSelectionPainter() {
- return DefaultHighlighter.DefaultPainter;
- }
-
- /**
- * Tries to set the position of the caret from
- * the coordinates of a mouse event, using viewToModel().
- *
- * @param e the mouse event
- */
- protected void positionCaret(MouseEvent e) {
- Point pt = new Point(e.getX(), e.getY());
- Position.Bias[] biasRet = new Position.Bias[1];
- int pos = component.getUI().viewToModel(component, pt, biasRet);
- if(biasRet[0] == null)
- biasRet[0] = Position.Bias.Forward;
- if (pos >= 0) {
- setDot(pos, biasRet[0]);
- }
- }
-
- /**
- * Tries to move the position of the caret from
- * the coordinates of a mouse event, using viewToModel().
- * This will cause a selection if the dot and mark
- * are different.
- *
- * @param e the mouse event
- */
- protected void moveCaret(MouseEvent e) {
- Point pt = new Point(e.getX(), e.getY());
- Position.Bias[] biasRet = new Position.Bias[1];
- int pos = component.getUI().viewToModel(component, pt, biasRet);
- if(biasRet[0] == null)
- biasRet[0] = Position.Bias.Forward;
- if (pos >= 0) {
- moveDot(pos, biasRet[0]);
- }
- }
-
- // --- FocusListener methods --------------------------
-
- /**
- * Called when the component containing the caret gains
- * focus. This is implemented to set the caret to visible
- * if the component is editable.
- *
- * @param e the focus event
- * @see FocusListener#focusGained
- */
- public void focusGained(FocusEvent e) {
- if (component.isEnabled()) {
- if (component.isEditable()) {
- setVisible(true);
- }
- setSelectionVisible(true);
- }
- }
-
- /**
- * Called when the component containing the caret loses
- * focus. This is implemented to set the caret to visibility
- * to false.
- *
- * @param e the focus event
- * @see FocusListener#focusLost
- */
- public void focusLost(FocusEvent e) {
- setVisible(false);
- setSelectionVisible(ownsSelection || e.isTemporary());
- }
-
-
- /**
- * Selects word based on the MouseEvent
- */
- private void selectWord(MouseEvent e) {
- if (selectedWordEvent != null
- && selectedWordEvent.getX() == e.getX()
- && selectedWordEvent.getY() == e.getY()) {
- //we already done selection for this
- return;
- }
- Action a = null;
- ActionMap map = getComponent().getActionMap();
- if (map != null) {
- a = map.get(DefaultEditorKit.selectWordAction);
- }
- if (a == null) {
- if (selectWord == null) {
- selectWord = new DefaultEditorKit.SelectWordAction();
- }
- a = selectWord;
- }
- a.actionPerformed(new ActionEvent(getComponent(),
- ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
- selectedWordEvent = e;
- }
-
- // --- MouseListener methods -----------------------------------
-
- /**
- * Called when the mouse is clicked. If the click was generated
- * from button1, a double click selects a word,
- * and a triple click the current line.
- *
- * @param e the mouse event
- * @see MouseListener#mouseClicked
- */
- public void mouseClicked(MouseEvent e) {
- if (! e.isConsumed()) {
- int nclicks = e.getClickCount();
- if (SwingUtilities.isLeftMouseButton(e)) {
- // mouse 1 behavior
- if(nclicks == 1) {
- selectedWordEvent = null;
- } else if(nclicks == 2
- && SwingUtilities2.canEventAccessSystemClipboard(e)) {
- selectWord(e);
- selectedWordEvent = null;
- } else if(nclicks == 3
- && SwingUtilities2.canEventAccessSystemClipboard(e)) {
- Action a = null;
- ActionMap map = getComponent().getActionMap();
- if (map != null) {
- a = map.get(DefaultEditorKit.selectLineAction);
- }
- if (a == null) {
- if (selectLine == null) {
- selectLine = new DefaultEditorKit.SelectLineAction();
- }
- a = selectLine;
- }
- a.actionPerformed(new ActionEvent(getComponent(),
- ActionEvent.ACTION_PERFORMED, null, e.getWhen(), e.getModifiers()));
- }
- } else if (SwingUtilities.isMiddleMouseButton(e)) {
- // mouse 2 behavior
- if (nclicks == 1 && component.isEditable() && component.isEnabled()
- && SwingUtilities2.canEventAccessSystemClipboard(e)) {
- // paste system selection, if it exists
- JTextComponent c = (JTextComponent) e.getSource();
- if (c != null) {
- try {
- Toolkit tk = c.getToolkit();
- Clipboard buffer = tk.getSystemSelection();
- if (buffer != null) {
- // platform supports system selections, update it.
- adjustCaret(e);
- TransferHandler th = c.getTransferHandler();
- if (th != null) {
- Transferable trans = null;
-
- try {
- trans = buffer.getContents(null);
- } catch (IllegalStateException ise) {
- // clipboard was unavailable
- UIManager.getLookAndFeel().provideErrorFeedback(c);
- }
-
- if (trans != null) {
- th.importData(c, trans);
- }
- }
- adjustFocus(true);
- }
- } catch (HeadlessException he) {
- // do nothing... there is no system clipboard
- }
- }
- }
- }
- }
- }
-
- /**
- * If button 1 is pressed, this is implemented to
- * request focus on the associated text component,
- * and to set the caret position. If the shift key is held down,
- * the caret will be moved, potentially resulting in a selection,
- * otherwise the
- * caret position will be set to the new location. If the component
- * is not enabled, there will be no request for focus.
- *
- * @param e the mouse event
- * @see MouseListener#mousePressed
- */
- public void mousePressed(MouseEvent e) {
- if (SwingUtilities.isLeftMouseButton(e)) {
- if (e.isConsumed()) {
- shouldHandleRelease = true;
- } else {
- shouldHandleRelease = false;
- adjustCaretAndFocus(e);
- if (e.getClickCount() == 2
- && SwingUtilities2.canEventAccessSystemClipboard(e)) {
- selectWord(e);
- }
- }
- }
- }
-
- void adjustCaretAndFocus(MouseEvent e) {
- adjustCaret(e);
- adjustFocus(false);
- }
-
- /**
- * Adjusts the caret location based on the MouseEvent.
- */
- private void adjustCaret(MouseEvent e) {
- if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 &&
- getDot() != -1) {
- moveCaret(e);
- } else {
- positionCaret(e);
- }
- }
-
- /**
- * Adjusts the focus, if necessary.
- *
- * @param inWindow if true indicates requestFocusInWindow should be used
- */
- private void adjustFocus(boolean inWindow) {
- if ((component != null) && component.isEnabled() &&
- component.isRequestFocusEnabled()) {
- if (inWindow) {
- component.requestFocusInWindow();
- }
- else {
- component.requestFocus();
- }
- }
- }
-
- /**
- * Called when the mouse is released.
- *
- * @param e the mouse event
- * @see MouseListener#mouseReleased
- */
- public void mouseReleased(MouseEvent e) {
- if (shouldHandleRelease && SwingUtilities.isLeftMouseButton(e)) {
- adjustCaretAndFocus(e);
- }
- }
-
- /**
- * Called when the mouse enters a region.
- *
- * @param e the mouse event
- * @see MouseListener#mouseEntered
- */
- public void mouseEntered(MouseEvent e) {
- }
-
- /**
- * Called when the mouse exits a region.
- *
- * @param e the mouse event
- * @see MouseListener#mouseExited
- */
- public void mouseExited(MouseEvent e) {
- }
-
- // --- MouseMotionListener methods -------------------------
-
- /**
- * Moves the caret position
- * according to the mouse pointer's current
- * location. This effectively extends the
- * selection. By default, this is only done
- * for mouse button 1.
- *
- * @param e the mouse event
- * @see MouseMotionListener#mouseDragged
- */
- public void mouseDragged(MouseEvent e) {
- if ((! e.isConsumed()) && SwingUtilities.isLeftMouseButton(e)) {
- moveCaret(e);
- }
- }
-
- /**
- * Called when the mouse is moved.
- *
- * @param e the mouse event
- * @see MouseMotionListener#mouseMoved
- */
- public void mouseMoved(MouseEvent e) {
- }
-
- // ---- Caret methods ---------------------------------
-
- /**
- * Renders the caret as a vertical line. If this is reimplemented
- * the damage method should also be reimplemented as it assumes the
- * shape of the caret is a vertical line. Sets the caret color to
- * the value returned by getCaretColor().
- * <p>
- * If there are multiple text directions present in the associated
- * document, a flag indicating the caret bias will be rendered.
- * This will occur only if the associated document is a subclass
- * of AbstractDocument and there are multiple bidi levels present
- * in the bidi element structure (i.e. the text has multiple
- * directions associated with it).
- *
- * @param g the graphics context
- * @see #damage
- */
- public void paint(Graphics g) {
- if(isVisible()) {
- try {
- TextUI mapper = component.getUI();
- Rectangle r = mapper.modelToView(component, dot, dotBias);
-
- if ((r == null) || ((r.width == 0) && (r.height == 0))) {
- return;
- }
- if (width > 0 && height > 0 &&
- !this._contains(r.x, r.y, r.width, r.height)) {
- // We seem to have gotten out of sync and no longer
- // contain the right location, adjust accordingly.
- Rectangle clip = g.getClipBounds();
-
- if (clip != null && !clip.contains(this)) {
- // Clip doesn't contain the old location, force it
- // to be repainted lest we leave a caret around.
- repaint();
- }
- // This will potentially cause a repaint of something
- // we're already repainting, but without changing the
- // semantics of damage we can't really get around this.
- damage(r);
- }
- g.setColor(component.getCaretColor());
- int paintWidth = getCaretWidth(r.height);
- r.x -= paintWidth >> 1;
- g.fillRect(r.x, r.y, paintWidth , r.height - 1);
-
- // see if we should paint a flag to indicate the bias
- // of the caret.
- // PENDING(prinz) this should be done through
- // protected methods so that alternative LAF
- // will show bidi information.
- Document doc = component.getDocument();
- if (doc instanceof AbstractDocument) {
- Element bidi = ((AbstractDocument)doc).getBidiRootElement();
- if ((bidi != null) && (bidi.getElementCount() > 1)) {
- // there are multiple directions present.
- flagXPoints[0] = r.x + ((dotLTR) ? paintWidth : 0);
- flagYPoints[0] = r.y;
- flagXPoints[1] = flagXPoints[0];
- flagYPoints[1] = flagYPoints[0] + 4;
- flagXPoints[2] = flagXPoints[0] + ((dotLTR) ? 4 : -4);
- flagYPoints[2] = flagYPoints[0];
- g.fillPolygon(flagXPoints, flagYPoints, 3);
- }
- }
- } catch (BadLocationException e) {
- // can't render I guess
- //System.err.println("Can't render cursor");
- }
- }
- }
-
- /**
- * Called when the UI is being installed into the
- * interface of a JTextComponent. This can be used
- * to gain access to the model that is being navigated
- * by the implementation of this interface. Sets the dot
- * and mark to 0, and establishes document, property change,
- * focus, mouse, and mouse motion listeners.
- *
- * @param c the component
- * @see Caret#install
- */
- public void install(JTextComponent c) {
- component = c;
- Document doc = c.getDocument();
- dot = mark = 0;
- dotLTR = markLTR = true;
- dotBias = markBias = Position.Bias.Forward;
- if (doc != null) {
- doc.addDocumentListener(handler);
- }
- c.addPropertyChangeListener(handler);
- c.addFocusListener(this);
- c.addMouseListener(this);
- c.addMouseMotionListener(this);
-
- // if the component already has focus, it won't
- // be notified.
- if (component.hasFocus()) {
- focusGained(null);
- }
-
- Number ratio = (Number) c.getClientProperty("caretAspectRatio");
- if (ratio != null) {
- aspectRatio = ratio.floatValue();
- } else {
- aspectRatio = -1;
- }
-
- Integer width = (Integer) c.getClientProperty("caretWidth");
- if (width != null) {
- caretWidth = width.intValue();
- } else {
- caretWidth = -1;
- }
- }
-
- /**
- * Called when the UI is being removed from the
- * interface of a JTextComponent. This is used to
- * unregister any listeners that were attached.
- *
- * @param c the component
- * @see Caret#deinstall
- */
- public void deinstall(JTextComponent c) {
- c.removeMouseListener(this);
- c.removeMouseMotionListener(this);
- c.removeFocusListener(this);
- c.removePropertyChangeListener(handler);
- Document doc = c.getDocument();
- if (doc != null) {
- doc.removeDocumentListener(handler);
- }
- synchronized(this) {
- component = null;
- }
- if (flasher != null) {
- flasher.stop();
- }
-
-
- }
-
- /**
- * Adds a listener to track whenever the caret position has
- * been changed.
- *
- * @param l the listener
- * @see Caret#addChangeListener
- */
- public void addChangeListener(ChangeListener l) {
- listenerList.add(ChangeListener.class, l);
- }
-
- /**
- * Removes a listener that was tracking caret position changes.
- *
- * @param l the listener
- * @see Caret#removeChangeListener
- */
- public void removeChangeListener(ChangeListener l) {
- listenerList.remove(ChangeListener.class, l);
- }
-
- /**
- * Returns an array of all the change listeners
- * registered on this caret.
- *
- * @return all of this caret's <code>ChangeListener</code>s
- * or an empty
- * array if no change listeners are currently registered
- *
- * @see #addChangeListener
- * @see #removeChangeListener
- *
- * @since 1.4
- */
- public ChangeListener[] getChangeListeners() {
- return (ChangeListener[])listenerList.getListeners(
- ChangeListener.class);
- }
-
- /**
- * Notifies all listeners that have registered interest for
- * notification on this event type. The event instance
- * is lazily created using the parameters passed into
- * the fire method. The listener list is processed last to first.
- *
- * @see EventListenerList
- */
- protected void fireStateChanged() {
- // Guaranteed to return a non-null array
- Object[] listeners = listenerList.getListenerList();
- // Process the listeners last to first, notifying
- // those that are interested in this event
- for (int i = listeners.length-2; i>=0; i-=2) {
- if (listeners[i]==ChangeListener.class) {
- // Lazily create the event:
- if (changeEvent == null)
- changeEvent = new ChangeEvent(this);
- ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
- }
- }
- }
-
- /**
- * Returns an array of all the objects currently registered
- * as <code><em>Foo</em>Listener</code>s
- * upon this caret.
- * <code><em>Foo</em>Listener</code>s are registered using the
- * <code>add<em>Foo</em>Listener</code> method.
- *
- * <p>
- *
- * You can specify the <code>listenerType</code> argument
- * with a class literal,
- * such as
- * <code><em>Foo</em>Listener.class</code>.
- * For example, you can query a
- * <code>DefaultCaret</code> <code>c</code>
- * for its change listeners with the following code:
- *
- * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));</pre>
- *
- * If no such listeners exist, this method returns an empty array.
- *
- * @param listenerType the type of listeners requested; this parameter
- * should specify an interface that descends from
- * <code>java.util.EventListener</code>
- * @return an array of all objects registered as
- * <code><em>Foo</em>Listener</code>s on this component,
- * or an empty array if no such
- * listeners have been added
- * @exception ClassCastException if <code>listenerType</code>
- * doesn't specify a class or interface that implements
- * <code>java.util.EventListener</code>
- *
- * @see #getChangeListeners
- *
- * @since 1.3
- */
- public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
- return listenerList.getListeners(listenerType);
- }
-
- /**
- * Changes the selection visibility.
- *
- * @param vis the new visibility
- */
- public void setSelectionVisible(boolean vis) {
- if (vis != selectionVisible) {
- selectionVisible = vis;
- if (selectionVisible) {
- // show
- Highlighter h = component.getHighlighter();
- if ((dot != mark) && (h != null) && (selectionTag == null)) {
- int p0 = Math.min(dot, mark);
- int p1 = Math.max(dot, mark);
- Highlighter.HighlightPainter p = getSelectionPainter();
- try {
- selectionTag = h.addHighlight(p0, p1, p);
- } catch (BadLocationException bl) {
- selectionTag = null;
- }
- }
- } else {
- // hide
- if (selectionTag != null) {
- Highlighter h = component.getHighlighter();
- h.removeHighlight(selectionTag);
- selectionTag = null;
- }
- }
- }
- }
-
- /**
- * Checks whether the current selection is visible.
- *
- * @return true if the selection is visible
- */
- public boolean isSelectionVisible() {
- return selectionVisible;
- }
-
- /**
- * Determines if the caret is currently active.
- * <p>
- * This method returns whether or not the <code>Caret</code>
- * is currently in a blinking state. It does not provide
- * information as to whether it is currently blinked on or off.
- * To determine if the caret is currently painted use the
- * <code>isVisible</code> method.
- *
- * @return <code>true</code> if active else <code>false</code>
- * @see #isVisible
- *
- * @since 1.5
- */
- public boolean isActive() {
- return active;
- }
-
- /**
- * Indicates whether or not the caret is currently visible. As the
- * caret flashes on and off the return value of this will change
- * between true, when the caret is painted, and false, when the
- * caret is not painted. <code>isActive</code> indicates whether
- * or not the caret is in a blinking state, such that it <b>can</b>
- * be visible, and <code>isVisible</code> indicates whether or not
- * the caret <b>is</b> actually visible.
- * <p>
- * Subclasses that wish to render a different flashing caret
- * should override paint and only paint the caret if this method
- * returns true.
- *
- * @return true if visible else false
- * @see Caret#isVisible
- * @see #isActive
- */
- public boolean isVisible() {
- return visible;
- }
-
- /**
- * Sets the caret visibility, and repaints the caret.
- * It is important to understand the relationship between this method,
- * <code>isVisible</code> and <code>isActive</code>.
- * Calling this method with a value of <code>true</code> activates the
- * caret blinking. Setting it to <code>false</code> turns it completely off.
- * To determine whether the blinking is active, you should call
- * <code>isActive</code>. In effect, <code>isActive</code> is an
- * appropriate corresponding "getter" method for this one.
- * <code>isVisible</code> can be used to fetch the current
- * visibility status of the caret, meaning whether or not it is currently
- * painted. This status will change as the caret blinks on and off.
- * <p>
- * Here's a list showing the potential return values of both
- * <code>isActive</code> and <code>isVisible</code>
- * after calling this method:
- * <p>
- * <b><code>setVisible(true)</code></b>:
- * <ul>
- * <li>isActive(): true</li>
- * <li>isVisible(): true or false depending on whether
- * or not the caret is blinked on or off</li>
- * </ul>
- * <p>
- * <b><code>setVisible(false)</code></b>:
- * <ul>
- * <li>isActive(): false</li>
- * <li>isVisible(): false</li>
- * </ul>
- *
- * @param e the visibility specifier
- * @see #isActive
- * @see Caret#setVisible
- */
- public void setVisible(boolean e) {
- // focus lost notification can come in later after the
- // caret has been deinstalled, in which case the component
- // will be null.
- if (component != null) {
- active = e;
- TextUI mapper = component.getUI();
- if (visible != e) {
- visible = e;
- // repaint the caret
- try {
- Rectangle loc = mapper.modelToView(component, dot,dotBias);
- damage(loc);
- } catch (BadLocationException badloc) {
- // hmm... not legally positioned
- }
- }
- }
- if (flasher != null) {
- if (visible) {
- flasher.start();
- } else {
- flasher.stop();
- }
- }
- }
-
- /**
- * Sets the caret blink rate.
- *
- * @param rate the rate in milliseconds, 0 to stop blinking
- * @see Caret#setBlinkRate
- */
- public void setBlinkRate(int rate) {
- if (rate != 0) {
- if (flasher == null) {
- flasher = new Timer(rate, handler);
- }
- flasher.setDelay(rate);
- } else {
- if (flasher != null) {
- flasher.stop();
- flasher.removeActionListener(handler);
- flasher = null;
- }
- }
- }
-
- /**
- * Gets the caret blink rate.
- *
- * @return the delay in milliseconds. If this is
- * zero the caret will not blink.
- * @see Caret#getBlinkRate
- */
- public int getBlinkRate() {
- return (flasher == null) ? 0 : flasher.getDelay();
- }
-
- /**
- * Fetches the current position of the caret.
- *
- * @return the position >= 0
- * @see Caret#getDot
- */
- public int getDot() {
- return dot;
- }
-
- /**
- * Fetches the current position of the mark. If there is a selection,
- * the dot and mark will not be the same.
- *
- * @return the position >= 0
- * @see Caret#getMark
- */
- public int getMark() {
- return mark;
- }
-
- /**
- * Sets the caret position and mark to some position. This
- * implicitly sets the selection range to zero.
- *
- * @param dot the position >= 0
- * @see Caret#setDot
- */
- public void setDot(int dot) {
- setDot(dot, Position.Bias.Forward);
- }
-
- /**
- * Moves the caret position to some other position.
- *
- * @param dot the position >= 0
- * @see Caret#moveDot
- */
- public void moveDot(int dot) {
- moveDot(dot, Position.Bias.Forward);
- }
-
- // ---- Bidi methods (we could put these in a subclass)
-
- void moveDot(int dot, Position.Bias dotBias) {
- if (! component.isEnabled()) {
- // don't allow selection on disabled components.
- setDot(dot, dotBias);
- return;
- }
- if (dot != this.dot) {
- NavigationFilter filter = component.getNavigationFilter();
-
- if (filter != null) {
- filter.moveDot(getFilterBypass(), dot, dotBias);
- }
- else {
- handleMoveDot(dot, dotBias);
- }
- }
- }
-
- void handleMoveDot(int dot, Position.Bias dotBias) {
- changeCaretPosition(dot, dotBias);
-
- if (selectionVisible) {
- Highlighter h = component.getHighlighter();
- if (h != null) {
- int p0 = Math.min(dot, mark);
- int p1 = Math.max(dot, mark);
-
- // if p0 == p1 then there should be no highlight, remove it if necessary
- if (p0 == p1) {
- if (selectionTag != null) {
- h.removeHighlight(selectionTag);
- selectionTag = null;
- }
- // otherwise, change or add the highlight
- } else {
- try {
- if (selectionTag != null) {
- h.changeHighlight(selectionTag, p0, p1);
- } else {
- Highlighter.HighlightPainter p = getSelectionPainter();
- selectionTag = h.addHighlight(p0, p1, p);
- }
- } catch (BadLocationException e) {
- throw new StateInvariantError("Bad caret position");
- }
- }
- }
- }
- }
-
- void setDot(int dot, Position.Bias dotBias) {
- NavigationFilter filter = component.getNavigationFilter();
-
- if (filter != null) {
- filter.setDot(getFilterBypass(), dot, dotBias);
- }
- else {
- handleSetDot(dot, dotBias);
- }
- }
-
- void handleSetDot(int dot, Position.Bias dotBias) {
- // move dot, if it changed
- Document doc = component.getDocument();
- if (doc != null) {
- dot = Math.min(dot, doc.getLength());
- }
- dot = Math.max(dot, 0);
-
- // The position (0,Backward) is out of range so disallow it.
- if( dot == 0 )
- dotBias = Position.Bias.Forward;
-
- mark = dot;
- if (this.dot != dot || this.dotBias != dotBias ||
- selectionTag != null || forceCaretPositionChange) {
- changeCaretPosition(dot, dotBias);
- }
- this.markBias = this.dotBias;
- this.markLTR = dotLTR;
- Highlighter h = component.getHighlighter();
- if ((h != null) && (selectionTag != null)) {
- h.removeHighlight(selectionTag);
- selectionTag = null;
- }
- }
-
- Position.Bias getDotBias() {
- return dotBias;
- }
-
- Position.Bias getMarkBias() {
- return markBias;
- }
-
- boolean isDotLeftToRight() {
- return dotLTR;
- }
-
- boolean isMarkLeftToRight() {
- return markLTR;
- }
-
- boolean isPositionLTR(int position, Position.Bias bias) {
- Document doc = component.getDocument();
- if(doc instanceof AbstractDocument ) {
- if(bias == Position.Bias.Backward && --position < 0)
- position = 0;
- return ((AbstractDocument)doc).isLeftToRight(position, position);
- }
- return true;
- }
-
- Position.Bias guessBiasForOffset(int offset, Position.Bias lastBias,
- boolean lastLTR) {
- // There is an abiguous case here. That if your model looks like:
- // abAB with the cursor at abB]A (visual representation of
- // 3 forward) deleting could either become abB] or
- // ab[B. I'ld actually prefer abB]. But, if I implement that
- // a delete at abBA] would result in aBA] vs a[BA which I
- // think is totally wrong. To get this right we need to know what
- // was deleted. And we could get this from the bidi structure
- // in the change event. So:
- // PENDING: base this off what was deleted.
- if(lastLTR != isPositionLTR(offset, lastBias)) {
- lastBias = Position.Bias.Backward;
- }
- else if(lastBias != Position.Bias.Backward &&
- lastLTR != isPositionLTR(offset, Position.Bias.Backward)) {
- lastBias = Position.Bias.Backward;
- }
- if (lastBias == Position.Bias.Backward && offset > 0) {
- try {
- Segment s = new Segment();
- component.getDocument().getText(offset - 1, 1, s);
- if (s.count > 0 && s.array[s.offset] == '\n') {
- lastBias = Position.Bias.Forward;
- }
- }
- catch (BadLocationException ble) {}
- }
- return lastBias;
- }
-
- // ---- local methods --------------------------------------------
-
- /**
- * Sets the caret position (dot) to a new location. This
- * causes the old and new location to be repainted. It
- * also makes sure that the caret is within the visible
- * region of the view, if the view is scrollable.
- */
- void changeCaretPosition(int dot, Position.Bias dotBias) {
- // repaint the old position and set the new value of
- // the dot.
- repaint();
-
-
- // Make sure the caret is visible if this window has the focus.
- if (flasher != null && flasher.isRunning()) {
- visible = true;
- flasher.restart();
- }
-
- // notify listeners at the caret moved
- this.dot = dot;
- this.dotBias = dotBias;
- dotLTR = isPositionLTR(dot, dotBias);
- fireStateChanged();
-
- updateSystemSelection();
-
- setMagicCaretPosition(null);
-
- // We try to repaint the caret later, since things
- // may be unstable at the time this is called
- // (i.e. we don't want to depend upon notification
- // order or the fact that this might happen on
- // an unsafe thread).
- Runnable callRepaintNewCaret = new Runnable() {
- public void run() {
- repaintNewCaret();
- }
- };
- SwingUtilities.invokeLater(callRepaintNewCaret);
- }
-
- /**
- * Repaints the new caret position, with the
- * assumption that this is happening on the
- * event thread so that calling <code>modelToView</code>
- * is safe.
- */
- void repaintNewCaret() {
- if (component != null) {
- TextUI mapper = component.getUI();
- Document doc = component.getDocument();
- if ((mapper != null) && (doc != null)) {
- // determine the new location and scroll if
- // not visible.
- Rectangle newLoc;
- try {
- newLoc = mapper.modelToView(component, this.dot, this.dotBias);
- } catch (BadLocationException e) {
- newLoc = null;
- }
- if (newLoc != null) {
- adjustVisibility(newLoc);
- // If there is no magic caret position, make one
- if (getMagicCaretPosition() == null) {
- setMagicCaretPosition(new Point(newLoc.x, newLoc.y));
- }
- }
-
- // repaint the new position
- damage(newLoc);
- }
- }
- }
-
- private void updateSystemSelection() {
- if ( ! SwingUtilities2.canCurrentEventAccessSystemClipboard() ) {
- return;
- }
- if (this.dot != this.mark && component != null) {
- Clipboard clip = getSystemSelection();
- if (clip != null) {
- String selectedText = null;
- if (component instanceof JPasswordField
- && component.getClientProperty("JPasswordField.cutCopyAllowed") !=
- Boolean.TRUE) {
- //fix for 4793761
- StringBuffer txt = null;
- char echoChar = ((JPasswordField)component).getEchoChar();
- int p0 = Math.min(getDot(), getMark());
- int p1 = Math.max(getDot(), getMark());
- for (int i = p0; i < p1; i++) {
- if (txt == null) {
- txt = new StringBuffer();
- }
- txt.append(echoChar);
- }
- selectedText = (txt != null) ? txt.toString() : null;
- } else {
- selectedText = component.getSelectedText();
- }
- try {
- clip.setContents(
- new StringSelection(selectedText), getClipboardOwner());
-
- ownsSelection = true;
- } catch (IllegalStateException ise) {
- // clipboard was unavailable
- // no need to provide error feedback to user since updating
- // the system selection is not a user invoked action
- }
- }
- }
- }
-
- private Clipboard getSystemSelection() {
- try {
- return component.getToolkit().getSystemSelection();
- } catch (HeadlessException he) {
- // do nothing... there is no system clipboard
- } catch (SecurityException se) {
- // do nothing... there is no allowed system clipboard
- }
- return null;
- }
-
- private ClipboardOwner getClipboardOwner() {
- return handler;
- }
-
- /**
- * This is invoked after the document changes to verify the current
- * dot/mark is valid. We do this in case the <code>NavigationFilter</code>
- * changed where to position the dot, that resulted in the current location
- * being bogus.
- */
- private void ensureValidPosition() {
- int length = component.getDocument().getLength();
- if (dot > length || mark > length) {
- // Current location is bogus and filter likely vetoed the
- // change, force the reset without giving the filter a
- // chance at changing it.
- handleSetDot(length, Position.Bias.Forward);
- }
- }
-
-
- /**
- * Saves the current caret position. This is used when
- * caret up/down actions occur, moving between lines
- * that have uneven end positions.
- *
- * @param p the position
- * @see #getMagicCaretPosition
- */
- public void setMagicCaretPosition(Point p) {
- magicCaretPosition = p;
- }
-
- /**
- * Gets the saved caret position.
- *
- * @return the position
- * see #setMagicCaretPosition
- */
- public Point getMagicCaretPosition() {
- return magicCaretPosition;
- }
-
- /**
- * Compares this object to the specified object.
- * The superclass behavior of comparing rectangles
- * is not desired, so this is changed to the Object
- * behavior.
- *
- * @param obj the object to compare this font with
- * @return <code>true</code> if the objects are equal;
- * <code>false</code> otherwise
- */
- public boolean equals(Object obj) {
- return (this == obj);
- }
-
- public String toString() {
- String s = "Dot=(" + dot + ", " + dotBias + ")";
- s += " Mark=(" + mark + ", " + markBias + ")";
- return s;
- }
-
- private NavigationFilter.FilterBypass getFilterBypass() {
- if (filterBypass == null) {
- filterBypass = new DefaultFilterBypass();
- }
- return filterBypass;
- }
-
- // Rectangle.contains returns false if passed a rect with a w or h == 0,
- // this won't (assuming X,Y are contained with this rectangle).
- private boolean _contains(int X, int Y, int W, int H) {
- int w = this.width;
- int h = this.height;
- if ((w | h | W | H) < 0) {
- // At least one of the dimensions is negative...
- return false;
- }
- // Note: if any dimension is zero, tests below must return false...
- int x = this.x;
- int y = this.y;
- if (X < x || Y < y) {
- return false;
- }
- if (W > 0) {
- w += x;
- W += X;
- if (W <= X) {
- // X+W overflowed or W was zero, return false if...
- // either original w or W was zero or
- // x+w did not overflow or
- // the overflowed x+w is smaller than the overflowed X+W
- if (w >= x || W > w) return false;
- } else {
- // X+W did not overflow and W was not zero, return false if...
- // original w was zero or
- // x+w did not overflow and x+w is smaller than X+W
- if (w >= x && W > w) return false;
- }
- }
- else if ((x + w) < X) {
- return false;
- }
- if (H > 0) {
- h += y;
- H += Y;
- if (H <= Y) {
- if (h >= y || H > h) return false;
- } else {
- if (h >= y && H > h) return false;
- }
- }
- else if ((y + h) < Y) {
- return false;
- }
- return true;
- }
-
- int getCaretWidth(int height) {
- if (aspectRatio > -1) {
- return (int) (aspectRatio * height) + 1;
- }
-
- if (caretWidth > -1) {
- return caretWidth;
- }
-
- return 1;
- }
-
- // --- serialization ---------------------------------------------
-
- private void readObject(ObjectInputStream s)
- throws ClassNotFoundException, IOException
- {
- s.defaultReadObject();
- handler = new Handler();
- if (!s.readBoolean()) {
- dotBias = Position.Bias.Forward;
- }
- else {
- dotBias = Position.Bias.Backward;
- }
- if (!s.readBoolean()) {
- markBias = Position.Bias.Forward;
- }
- else {
- markBias = Position.Bias.Backward;
- }
- }
-
- private void writeObject(ObjectOutputStream s) throws IOException {
- s.defaultWriteObject();
- s.writeBoolean((dotBias == Position.Bias.Backward));
- s.writeBoolean((markBias == Position.Bias.Backward));
- }
-
- // ---- member variables ------------------------------------------
-
- /**
- * The event listener list.
- */
- protected EventListenerList listenerList = new EventListenerList();
-
- /**
- * The change event for the model.
- * Only one ChangeEvent is needed per model instance since the
- * event's only (read-only) state is the source property. The source
- * of events generated here is always "this".
- */
- protected transient ChangeEvent changeEvent = null;
-
- // package-private to avoid inner classes private member
- // access bug
- JTextComponent component;
-
- int updatePolicy = UPDATE_WHEN_ON_EDT;
- boolean visible;
- boolean active;
- int dot;
- int mark;
- Object selectionTag;
- boolean selectionVisible;
- Timer flasher;
- Point magicCaretPosition;
- transient Position.Bias dotBias;
- transient Position.Bias markBias;
- boolean dotLTR;
- boolean markLTR;
- transient Handler handler = new Handler();
- transient private int[] flagXPoints = new int[3];
- transient private int[] flagYPoints = new int[3];
- private transient NavigationFilter.FilterBypass filterBypass;
- static private transient Action selectWord = null;
- static private transient Action selectLine = null;
- /**
- * This is used to indicate if the caret currently owns the selection.
- * This is always false if the system does not support the system
- * clipboard.
- */
- private boolean ownsSelection;
-
- /**
- * If this is true, the location of the dot is updated regardless of
- * the current location. This is set in the DocumentListener
- * such that even if the model location of dot hasn't changed (perhaps do
- * to a forward delete) the visual location is updated.
- */
- private boolean forceCaretPositionChange;
-
- /**
- * Whether or not mouseReleased should adjust the caret and focus.
- * This flag is set by mousePressed if it wanted to adjust the caret
- * and focus but couldn't because of a possible DnD operation.
- */
- private transient boolean shouldHandleRelease;
-
-
- /**
- * holds last MouseEvent which caused the word selection
- */
- private transient MouseEvent selectedWordEvent = null;
-
- /**
- * The width of the caret in pixels.
- */
- private int caretWidth = -1;
- private float aspectRatio = -1;
-
- class SafeScroller implements Runnable {
-
- SafeScroller(Rectangle r) {
- this.r = r;
- }
-
- public void run() {
- if (component != null) {
- component.scrollRectToVisible(r);
- }
- }
-
- Rectangle r;
- }
-
-
- class Handler implements PropertyChangeListener, DocumentListener, ActionListener, ClipboardOwner {
-
- // --- ActionListener methods ----------------------------------
-
- /**
- * Invoked when the blink timer fires. This is called
- * asynchronously. The simply changes the visibility
- * and repaints the rectangle that last bounded the caret.
- *
- * @param e the action event
- */
- public void actionPerformed(ActionEvent e) {
- if (width == 0 || height == 0) {
- // setVisible(true) will cause a scroll, only do this if the
- // new location is really valid.
- if (component != null) {
- TextUI mapper = component.getUI();
- try {
- Rectangle r = mapper.modelToView(component, dot,
- dotBias);
- if (r != null && r.width != 0 && r.height != 0) {
- damage(r);
- }
- } catch (BadLocationException ble) {
- }
- }
- }
- visible = !visible;
- repaint();
- }
-
- // --- DocumentListener methods --------------------------------
-
- /**
- * Updates the dot and mark if they were changed by
- * the insertion.
- *
- * @param e the document event
- * @see DocumentListener#insertUpdate
- */
- public void insertUpdate(DocumentEvent e) {
- if (getUpdatePolicy() == NEVER_UPDATE ||
- (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
- !SwingUtilities.isEventDispatchThread())) {
-
- if ((e.getOffset() <= dot || e.getOffset() <= mark)
- && selectionTag != null) {
- try {
- component.getHighlighter().changeHighlight(selectionTag,
- Math.min(dot, mark), Math.max(dot, mark));
- } catch (BadLocationException e1) {
- e1.printStackTrace();
- }
- }
- return;
- }
- int adjust = 0;
- int offset = e.getOffset();
- int length = e.getLength();
- int newDot = dot;
- short changed = 0;
-
- if (e instanceof AbstractDocument.UndoRedoDocumentEvent) {
- setDot(offset + length);
- return;
- }
- if (newDot >= offset) {
- newDot += length;
- changed |= 1;
- }
- int newMark = mark;
- if (newMark >= offset) {
- newMark += length;
- changed |= 2;
- }
-
- if (changed != 0) {
- Position.Bias dotBias = DefaultCaret.this.dotBias;
- if (dot == offset) {
- Document doc = component.getDocument();
- boolean isNewline;
- try {
- Segment s = new Segment();
- doc.getText(newDot - 1, 1, s);
- isNewline = (s.count > 0 &&
- s.array[s.offset] == '\n');
- } catch (BadLocationException ble) {
- isNewline = false;
- }
- if (isNewline) {
- dotBias = Position.Bias.Forward;
- } else {
- dotBias = Position.Bias.Backward;
- }
- }
- if (newMark == newDot) {
- setDot(newDot, dotBias);
- ensureValidPosition();
- }
- else {
- setDot(newMark, markBias);
- if (getDot() == newMark) {
- // Due this test in case the filter vetoed the
- // change in which case this probably won't be
- // valid either.
- moveDot(newDot, dotBias);
- }
- ensureValidPosition();
- }
- }
- }
-
- /**
- * Updates the dot and mark if they were changed
- * by the removal.
- *
- * @param e the document event
- * @see DocumentListener#removeUpdate
- */
- public void removeUpdate(DocumentEvent e) {
- if (getUpdatePolicy() == NEVER_UPDATE ||
- (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
- !SwingUtilities.isEventDispatchThread())) {
-
- int length = component.getDocument().getLength();
- dot = Math.min(dot, length);
- mark = Math.min(mark, length);
- if ((e.getOffset() < dot || e.getOffset() < mark)
- && selectionTag != null) {
- try {
- component.getHighlighter().changeHighlight(selectionTag,
- Math.min(dot, mark), Math.max(dot, mark));
- } catch (BadLocationException e1) {
- e1.printStackTrace();
- }
- }
- return;
- }
- int offs0 = e.getOffset();
- int offs1 = offs0 + e.getLength();
- int adjust = 0;
- int newDot = dot;
- boolean adjustDotBias = false;
- int newMark = mark;
- boolean adjustMarkBias = false;
-
- if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
- setDot(offs0);
- return;
- }
- if (newDot >= offs1) {
- newDot -= (offs1 - offs0);
- if(newDot == offs1) {
- adjustDotBias = true;
- }
- } else if (newDot >= offs0) {
- newDot = offs0;
- adjustDotBias = true;
- }
- if (newMark >= offs1) {
- newMark -= (offs1 - offs0);
- if(newMark == offs1) {
- adjustMarkBias = true;
- }
- } else if (newMark >= offs0) {
- newMark = offs0;
- adjustMarkBias = true;
- }
- if (newMark == newDot) {
- forceCaretPositionChange = true;
- try {
- setDot(newDot, guessBiasForOffset(newDot, dotBias,
- dotLTR));
- } finally {
- forceCaretPositionChange = false;
- }
- ensureValidPosition();
- } else {
- Position.Bias dotBias = DefaultCaret.this.dotBias;
- Position.Bias markBias = DefaultCaret.this.markBias;
- if(adjustDotBias) {
- dotBias = guessBiasForOffset(newDot, dotBias, dotLTR);
- }
- if(adjustMarkBias) {
- markBias = guessBiasForOffset(mark, markBias, markLTR);
- }
- setDot(newMark, markBias);
- if (getDot() == newMark) {
- // Due this test in case the filter vetoed the change
- // in which case this probably won't be valid either.
- moveDot(newDot, dotBias);
- }
- ensureValidPosition();
- }
- }
-
- /**
- * Gives notification that an attribute or set of attributes changed.
- *
- * @param e the document event
- * @see DocumentListener#changedUpdate
- */
- public void changedUpdate(DocumentEvent e) {
- if (getUpdatePolicy() == NEVER_UPDATE ||
- (getUpdatePolicy() == UPDATE_WHEN_ON_EDT &&
- !SwingUtilities.isEventDispatchThread())) {
- return;
- }
- if(e instanceof AbstractDocument.UndoRedoDocumentEvent) {
- setDot(e.getOffset() + e.getLength());
- }
- }
-
- // --- PropertyChangeListener methods -----------------------
-
- /**
- * This method gets called when a bound property is changed.
- * We are looking for document changes on the editor.
- */
- public void propertyChange(PropertyChangeEvent evt) {
- Object oldValue = evt.getOldValue();
- Object newValue = evt.getNewValue();
- if ((oldValue instanceof Document) || (newValue instanceof Document)) {
- setDot(0);
- if (oldValue != null) {
- ((Document)oldValue).removeDocumentListener(this);
- }
- if (newValue != null) {
- ((Document)newValue).addDocumentListener(this);
- }
- } else if("enabled".equals(evt.getPropertyName())) {
- Boolean enabled = (Boolean) evt.getNewValue();
- if(component.isFocusOwner()) {
- if(enabled == Boolean.TRUE) {
- if(component.isEditable()) {
- setVisible(true);
- }
- setSelectionVisible(true);
- } else {
- setVisible(false);
- setSelectionVisible(false);
- }
- }
- } else if("caretWidth".equals(evt.getPropertyName())) {
- Integer newWidth = (Integer) evt.getNewValue();
- if (newWidth != null) {
- caretWidth = newWidth.intValue();
- } else {
- caretWidth = -1;
- }
- repaint();
- } else if("caretAspectRatio".equals(evt.getPropertyName())) {
- Number newRatio = (Number) evt.getNewValue();
- if (newRatio != null) {
- aspectRatio = newRatio.floatValue();
- } else {
- aspectRatio = -1;
- }
- repaint();
- }
- }
-
-
- //
- // ClipboardOwner
- //
- /**
- * Toggles the visibility of the selection when ownership is lost.
- */
- public void lostOwnership(Clipboard clipboard,
- Transferable contents) {
- if (ownsSelection) {
- ownsSelection = false;
- if (component != null && !component.hasFocus()) {
- setSelectionVisible(false);
- }
- }
- }
- }
-
-
- private class DefaultFilterBypass extends NavigationFilter.FilterBypass {
- public Caret getCaret() {
- return DefaultCaret.this;
- }
-
- public void setDot(int dot, Position.Bias bias) {
- handleSetDot(dot, bias);
- }
-
- public void moveDot(int dot, Position.Bias bias) {
- handleMoveDot(dot, bias);
- }
- }
- }
-