alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (DefaultCaret.java)

This example Java source code file (DefaultCaret.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

awt, badlocationexception, document, event, gui, handler, highlighter, integer, jtextcomponent, never_update, number, object, plaf, rectangle, swing, update_when_on_edt, util

The DefaultCaret.java Java example source code

/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
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 sun.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 property. The repaint of the
 * new caret location will occur on the event thread in any case, as calls to
 * <code>modelToView 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 property.  This can easily be changed
 * by reimplementing the
 * {@link #getSelectionPainter getSelectionPainter}
 * 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:
 * 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™
 * has been added to the <code>java.beans package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author  Timothy Prinzing
 * @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
     * 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 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>NEVER_UPDATE: 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>ALWAYS_UPDATE: 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 method.
     *   <li>UPDATE_WHEN_ON_EDT: acts like ALWAYS_UPDATE
     *       if the document updates are performed on the Event Dispatching Thread
     *       and like <code>NEVER_UPDATE if updates are performed on
     *       other thread. </li>
     * </ul> 

* The default property value is <code>UPDATE_WHEN_ON_EDT. * * @param policy one of the following values : <code>UPDATE_WHEN_ON_EDT, * <code>NEVER_UPDATE, ALWAYS_UPDATE * @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>NEVER_UPDATE, ALWAYS_UPDATE * * @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). * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in 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 (getComponent() == null) { return; } int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e); if (! e.isConsumed()) { 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) { int nclicks = SwingUtilities2.getAdjustedClickCount(getComponent(), e); if (SwingUtilities.isLeftMouseButton(e)) { if (e.isConsumed()) { shouldHandleRelease = true; } else { shouldHandleRelease = false; adjustCaretAndFocus(e); if (nclicks == 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 if (!e.isPopupTrigger()) { 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 (!e.isConsumed() && 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); // 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>ChangeListeners * or an empty * array if no change listeners are currently registered * * @see #addChangeListener * @see #removeChangeListener * * @since 1.4 */ public ChangeListener[] getChangeListeners() { return 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>FooListeners * upon this caret. * <code>FooListeners are registered using the * <code>addFooListener method. * * <p> * * You can specify the <code>listenerType argument * with a class literal, * such as * <code>FooListener.class. * For example, you can query a * <code>DefaultCaret c * for its change listeners with the following code: * * <pre>ChangeListener[] cls = (ChangeListener[])(c.getListeners(ChangeListener.class));

* * 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 * @return an array of all objects registered as * <code>FooListeners on this component, * or an empty array if no such * listeners have been added * @exception ClassCastException if <code>listenerType * doesn't specify a class or interface that implements * <code>java.util.EventListener * * @see #getChangeListeners * * @since 1.3 */ public <T extends EventListener> T[] getListeners(Class 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 * 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 method. * * @return <code>true if active else false * @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 indicates whether * or not the caret is in a blinking state, such that it <b>can * be visible, and <code>isVisible indicates whether or not * the caret <b>is 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 and isActive. * Calling this method with a value of <code>true activates the * caret blinking. Setting it to <code>false turns it completely off. * To determine whether the blinking is active, you should call * <code>isActive. In effect, isActive is an * appropriate corresponding "getter" method for this one. * <code>isVisible 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 and isVisible * after calling this method: * <p> * <b>setVisible(true): * <ul> * <li>isActive(): true * <li>isVisible(): true or false depending on whether * or not the caret is blinked on or off</li> * </ul> * <p> * <b>setVisible(false): * <ul> * <li>isActive(): false * <li>isVisible(): false * </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. active = e; if (component != null) { 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 the specified position, * with a forward bias. This implicitly sets the * selection range to zero. * * @param dot the position >= 0 * @see #setDot(int, Position.Bias) * @see Caret#setDot */ public void setDot(int dot) { setDot(dot, Position.Bias.Forward); } /** * Moves the caret position to the specified position, * with a forward bias. * * @param dot the position >= 0 * @see #moveDot(int, javax.swing.text.Position.Bias) * @see Caret#moveDot */ public void moveDot(int dot) { moveDot(dot, Position.Bias.Forward); } // ---- Bidi methods (we could put these in a subclass) /** * Moves the caret position to the specified position, with the * specified bias. * * @param dot the position >= 0 * @param dotBias the bias for this position, not <code>null * @throws IllegalArgumentException if the bias is <code>null * @see Caret#moveDot * @since 1.6 */ public void moveDot(int dot, Position.Bias dotBias) { if (dotBias == null) { throw new IllegalArgumentException("null bias"); } 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"); } } } } } /** * Sets the caret position and mark to the specified position, with the * specified bias. This implicitly sets the selection range * to zero. * * @param dot the position >= 0 * @param dotBias the bias for this position, not <code>null * @throws IllegalArgumentException if the bias is <code>null * @see Caret#setDot * @since 1.6 */ public void setDot(int dot, Position.Bias dotBias) { if (dotBias == null) { throw new IllegalArgumentException("null bias"); } 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; } } /** * Returns the bias of the caret position. * * @return the bias of the caret position * @since 1.6 */ public Position.Bias getDotBias() { return dotBias; } /** * Returns the bias of the mark. * * @return the bias of the mark * @since 1.6 */ public 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(bias == Position.Bias.Backward && --position < 0) position = 0; return AbstractDocument.isLeftToRight(doc, position, position); } 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 * 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 && component.hasFocus()) { Clipboard clip = getSystemSelection(); if (clip != null) { String selectedText; if (component instanceof JPasswordField && component.getClientProperty("JPasswordField.cutCopyAllowed") != Boolean.TRUE) { //fix for 4793761 StringBuilder 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 StringBuilder(); } 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 * 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 if the objects are equal; * <code>false 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; } else { Object property = UIManager.get("Caret.width"); if (property instanceof Integer) { return ((Integer) property).intValue(); } else { 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 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 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); } } }

Other Java examples (source code examples)

Here is a short list of links related to this Java DefaultCaret.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.