|
Java example source code file (DefaultCaret.java)
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>* * 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 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 |
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.