/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE 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 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</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
* {@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:</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™
* has been added to the <code>java.beans</code> 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</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="https://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>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
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.
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</code>
* @throws IllegalArgumentException if the bias is <code>null</code>
* @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</code>
* @throws IllegalArgumentException if the bias is <code>null</code>
* @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</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 &&
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</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;
} 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);
}
}
}