![JAR search and dependency download from the Maven repository](/logo.png)
org.netbeans.editor.BaseCaret Maven / Gradle / Ivy
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.IOException;
import javax.swing.Action;
import javax.swing.Timer;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Caret;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.EventListenerList;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Position;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldHierarchy;
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
import org.netbeans.api.editor.fold.FoldHierarchyListener;
import org.netbeans.api.editor.fold.FoldUtilities;
/**
* Caret implementation
*
* @author Miloslav Metelka
* @version 1.00
*/
public class BaseCaret extends Rectangle implements Caret, FocusListener,
MouseListener, MouseMotionListener, PropertyChangeListener,
DocumentListener, ActionListener, SettingsChangeListener,
AtomicLockListener, FoldHierarchyListener {
/** Caret type representing block covering current character */
public static final String BLOCK_CARET = "block-caret"; // NOI18N
/** Default caret type */
public static final String LINE_CARET = "line-caret"; // NOI18N
/** One dot thin line compatible with Swing default caret */
public static final String THIN_LINE_CARET = "thin-line-caret"; // NOI18N
private static final boolean debugCaretFocus
= Boolean.getBoolean("netbeans.debug.editor.caret.focus"); // NOI18N
private static final boolean debugCaretFocusExtra
= Boolean.getBoolean("netbeans.debug.editor.caret.focus.extra"); // NOI18N
/** Component this caret is bound to */
protected JTextComponent component;
/** Position of the caret on the screen. This helps to compute
* caret position on the next after jump.
*/
Point magicCaretPosition;
/** Draw mark designating the position of the caret. */
MarkFactory.DrawMark caretMark = new MarkFactory.CaretMark();
/** Draw mark that supports caret mark in creating selection */
MarkFactory.DrawMark selectionMark = new MarkFactory.DrawMark(
DrawLayerFactory.CARET_LAYER_NAME, null);
/** Is the caret visible */
boolean visible;
/** Caret is visible and the blink is visible. Both must be true
* in order to show the caret.
*/
boolean blinkVisible;
/** Is the selection currently visible? */
boolean selectionVisible;
/** Listeners */
protected EventListenerList listenerList = new EventListenerList();
/** Timer used for blinking the caret */
protected Timer flasher;
/** Type of the caret */
String type;
/** Is the caret italic for italic fonts */
boolean italic;
private int xPoints[] = new int[4];
private int yPoints[] = new int[4];
private Action selectWordAction;
private Action selectLineAction;
/** Change event. Only one instance needed because it has only source property */
protected ChangeEvent changeEvent;
private static char emptyDotChar[] = { ' ' };
/** Dot array of one character under caret */
protected char dotChar[] = emptyDotChar;
private boolean overwriteMode;
/** Remembering document on which caret listens avoids
* duplicate listener addition to SwingPropertyChangeSupport
* due to the bug 4200280
*/
private BaseDocument listenDoc;
/** Font of the text underlying the caret. It can be used
* in caret painting.
*/
protected Font afterCaretFont;
/** Font of the text right before the caret */
protected Font beforeCaretFont;
/** Foreground color of the text underlying the caret. It can be used
* in caret painting.
*/
protected Color textForeColor;
/** Background color of the text underlying the caret. It can be used
* in caret painting.
*/
protected Color textBackColor;
private transient FocusListener focusListener;
private transient boolean nextPaintUpdate;
private transient Rectangle nextPaintScrollRect;
private transient int nextPaintScrollPolicy;
/** Whether the text is being modified under atomic lock.
* If so just one caret change is fired at the end of all modifications.
*/
private transient boolean inAtomicLock;
/** Helps to check whether there was modification performed
* and so the caret change needs to be fired.
*/
private transient boolean modified;
/** Whether there was an undo done in the modification and the offset of the modification */
private transient int undoOffset = -1;
/** is jdk14 version running? */
private boolean jdk14;
private static final Class[] EMPTY_CLASS_ARRAY = new Class[] {};
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[] {};
static final long serialVersionUID =-9113841520331402768L;
public BaseCaret() {
Settings.addSettingsChangeListener(this);
String specVer = System.getProperty("java.specification.version"); //NOI18N
jdk14 = specVer!=null && specVer.startsWith("1.4"); //NOI18N
}
/** Called when settings were changed. The method is called
* also in constructor, so the code must count with the evt being null.
*/
public void settingsChange(SettingsChangeEvent evt) {
if( evt != null && SettingsNames.CARET_BLINK_RATE.equals( evt.getSettingName() ) ) {
JTextComponent c = component;
if (c == null) return;
if (evt.getKitClass() != Utilities.getKitClass(c)) return;
Object value = evt.getNewValue();
if( value instanceof Integer ) {
setBlinkRate( ((Integer)value).intValue() );
}
}
updateType();
}
void updateType() {
JTextComponent c = component;
if (c != null) {
Class kitClass = Utilities.getKitClass(c);
if (kitClass==null) return;
String newType;
boolean newItalic;
Color caretColor;
if (overwriteMode) {
newType = SettingsUtil.getString(kitClass,
SettingsNames.CARET_TYPE_OVERWRITE_MODE, LINE_CARET);
newItalic = SettingsUtil.getBoolean(kitClass,
SettingsNames.CARET_ITALIC_OVERWRITE_MODE, false);
caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_OVERWRITE_MODE,
SettingsDefaults.defaultCaretColorOvwerwriteMode );
} else { // insert mode
newType = SettingsUtil.getString(kitClass,
SettingsNames.CARET_TYPE_INSERT_MODE, LINE_CARET);
newItalic = SettingsUtil.getBoolean(kitClass,
SettingsNames.CARET_ITALIC_INSERT_MODE, false);
caretColor = getColor( kitClass, SettingsNames.CARET_COLOR_INSERT_MODE,
SettingsDefaults.defaultCaretColorInsertMode );
}
this.type = newType;
this.italic = newItalic;
c.setCaretColor(caretColor);
if (debugCaretFocusExtra){
System.err.println("Updating caret color:"+caretColor);
}
dispatchUpdate();
}
}
private static Color getColor( Class kitClass, String settingName,
Color defaultValue) {
Object value = Settings.getValue(kitClass, settingName);
return (value instanceof Color) ? (Color)value : defaultValue;
}
/** Called when UI is being installed into JTextComponent */
public void install(JTextComponent c) {
component = c;
blinkVisible = true;
component.addPropertyChangeListener(this);
focusListener = new FocusHandler(this);
component.addFocusListener(focusListener);
component.addMouseListener(this);
component.addMouseMotionListener(this);
EditorUI editorUI = Utilities.getEditorUI(component);
editorUI.addLayer(new DrawLayerFactory.CaretLayer(),
DrawLayerFactory.CARET_LAYER_VISIBILITY);
caretMark.setEditorUI(editorUI);
selectionMark.setEditorUI(editorUI);
editorUI.addPropertyChangeListener( this );
FoldHierarchy hierarchy = FoldHierarchy.get(c);
if (hierarchy != null) {
hierarchy.addFoldHierarchyListener(this);
}
BaseDocument doc = Utilities.getDocument(c);
if (doc != null) {
modelChanged(null, doc);
}
if (component.hasFocus()) {
focusGained(null); // emulate focus gained
if (debugCaretFocus || debugCaretFocusExtra) {
System.err.println("Component has focus, calling focusGained() on doc="
+ component.getDocument().getProperty(Document.TitleProperty));
}
}
}
/** Called when UI is being removed from JTextComponent */
public void deinstall(JTextComponent c) {
component = null; // invalidate
if (flasher != null) {
setBlinkRate(0);
}
Utilities.getEditorUI(c).removeLayer(DrawLayerFactory.CARET_LAYER_NAME);
c.removeMouseMotionListener(this);
c.removeMouseListener(this);
if (focusListener != null) {
c.removeFocusListener(focusListener);
focusListener = null;
}
c.removePropertyChangeListener(this);
FoldHierarchy hierarchy = FoldHierarchy.get(c);
if (hierarchy != null) {
hierarchy.removeFoldHierarchyListener(this);
}
modelChanged(listenDoc, null);
}
protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
// [PENDING] !!! this body looks strange because of the bug 4200280
if (oldDoc != null && listenDoc == oldDoc) {
oldDoc.removeDocumentListener(this);
oldDoc.removeAtomicLockListener(this);
try {
caretMark.remove();
selectionMark.remove();
} catch (InvalidMarkException e) {
Utilities.annotateLoggable(e);
}
listenDoc = null;
}
if (newDoc != null) {
if (listenDoc != null) {
// deinstall from the listenDoc first
modelChanged(listenDoc, null);
}
newDoc.addDocumentListener(this);
listenDoc = newDoc;
newDoc.addAtomicLockListener(this);
try {
Utilities.insertMark(newDoc, caretMark, 0);
Utilities.insertMark(newDoc, selectionMark, 0);
} catch (InvalidMarkException e) {
Utilities.annotateLoggable(e);
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
}
settingsChange(null); // update settings
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
updateType();
}
}
);
}
}
/** Renders the caret */
public void paint(Graphics g) {
JTextComponent c = component;
if (c == null) return;
EditorUI editorUI = Utilities.getEditorUI(c);
/* Fix of #8123 - besides nextPaintUpdate flag
* it's also necessary to check the isFontsInited() here.
*/
if (nextPaintUpdate && editorUI.isFontsInited()) { // need to update caret first
// running in AWT -> no dispatching
nextPaintUpdate = false; // no further updating
update(nextPaintScrollRect, nextPaintScrollPolicy);
// fix for issue #13049
nextPaintScrollRect = null;
}
if (visible && blinkVisible) {
paintCustomCaret(g);
}
}
protected void paintCustomCaret(Graphics g) {
JTextComponent c = component;
if (c != null) {
EditorUI editorUI = Utilities.getEditorUI(c);
if (THIN_LINE_CARET.equals(type)) { // thin line caret
g.setColor(c.getCaretColor());
int upperX = x;
if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
upperX += Math.tan(beforeCaretFont.getItalicAngle()) * height;
}
g.drawLine((int)upperX, y, x, (y + height - 1));
} else if (BLOCK_CARET.equals(type)) { // block caret
g.setColor(c.getCaretColor());
if (afterCaretFont != null) g.setFont(afterCaretFont);
if (afterCaretFont != null && afterCaretFont.isItalic() && italic) { // paint italic caret
int upperX = (int)(x + Math.tan(afterCaretFont.getItalicAngle()) * height);
xPoints[0] = upperX; yPoints[0] = y;
xPoints[1] = upperX + width; yPoints[1] = y;
xPoints[2] = x + width; yPoints[2] = y + height - 1;
xPoints[3] = x; yPoints[3] = y + height - 1;
g.fillPolygon(xPoints, yPoints, 4);
} else { // paint non-italic caret
g.fillRect(x, y, width, height);
}
if (!Character.isWhitespace(dotChar[0])) {
g.setColor(Color.white);
// int ascent = FontMetricsCache.getFontMetrics(afterCaretFont, c).getAscent();
g.drawChars(dotChar, 0, 1, x, y + editorUI.getLineAscent());
}
} else { // two dot line caret
g.setColor(c.getCaretColor());
int blkWidth = 2;
if (beforeCaretFont != null && beforeCaretFont.isItalic() && italic) {
int upperX = (int)(x + Math.tan(beforeCaretFont.getItalicAngle()) * height);
xPoints[0] = upperX; yPoints[0] = y;
xPoints[1] = upperX + blkWidth; yPoints[1] = y;
xPoints[2] = x + blkWidth; yPoints[2] = y + height - 1;
xPoints[3] = x; yPoints[3] = y + height - 1;
g.fillPolygon(xPoints, yPoints, 4);
} else { // paint non-italic caret
g.fillRect(x, y, blkWidth, height - 1);
}
}
}
}
/** Update the caret's visual position */
void dispatchUpdate() {
dispatchUpdate(null, EditorUI.SCROLL_MOVE);
}
void dispatchUpdate(final Rectangle scrollRect, final int scrollPolicy) {
JTextComponent c = component;
if (c == null) return;
EditorUI editorUI = Utilities.getEditorUI(c);
if (!editorUI.isFontsInited()) { // fonts not yet initialized
if (scrollRect != null) { // do not hide the "really" scrolling requests
nextPaintScrollRect = scrollRect;
}
nextPaintScrollPolicy = scrollPolicy;
nextPaintUpdate = true;
return;
}
/* part of fix of #18860 - Using runInEventDispatchThread() in AWT thread
* means that the code is executed immediately which can lead
* to problems once the insert/remove in document is performed
* because the update() uses views to find out the visual position
* and if the views doc listener is added AFTER the caret's listener
* then the views are not updated yet. Using SwingUtilities.invokeLater()
* should solve the problem although the view extent could flip
* once the extent would be explicitely scrolled to area that does
* not cover the caret's rectangle. It needs to be tested
* so that it does not happen.
*/
// Utilities.runInEventDispatchThread(
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
JTextComponent c2 = component;
if (c2 != null) {
BaseDocument doc = Utilities.getDocument(c2);
if (doc != null) {
doc.readLock();
try {
update(scrollRect, scrollPolicy);
} finally {
doc.readUnlock();
}
}
}
}
}
);
}
/** Update the caret. The document is read-locked while calling this method.
* @param scrollRect rectangle that should be visible after the updating
* of the caret. It can be null to do no scrolling (only update caret)
* or it can be caret rectangle to update the caret and make it visible
* or some other rectangle to guarantee that the rectangle will be visible.
* @param scrollPolicy scrolling policy as defined in EditorUI. It has
* no meaning if scrollRect
is null.
*/
protected void update(Rectangle scrollRect, int scrollPolicy) {
JTextComponent c = component;
if (c != null) {
BaseTextUI ui = (BaseTextUI)c.getUI();
EditorUI editorUI = ui.getEditorUI();
BaseDocument doc = Utilities.getDocument(c);
if (doc != null) {
Rectangle oldCaretRect = new Rectangle(this);
if (italic) { // caret is italic - add char height to the width of the rect
oldCaretRect.width += oldCaretRect.height;
}
int dot = getDot();
try {
//ui.modelToViewDG(dot, caretDG);
Rectangle r = component.getUI().modelToView(component, dot, Position.Bias.Forward);
if (r!=null){
if (isVisible()){
c.repaint(new Rectangle(this));
}
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.height;
}
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
}
resetBlink();
if (scrollRect == null
|| !editorUI.scrollRectToVisibleFragile(scrollRect, scrollPolicy)
) {
oldCaretRect.add(this); // adds the NEW caret rect - important!
c.repaint(oldCaretRect);
}
}
}
}
private void updateSystemSelection() {
if (!jdk14){
return;
}
if (getDot() != getMark() && component != null) {
Clipboard clip = getSystemSelection();
if (clip != null) {
clip.setContents(new java.awt.datatransfer.StringSelection(component.getSelectedText()), null);
}
}
}
private Clipboard getSystemSelection() {
if (!jdk14){
return null;
}
try {
// XXX remove reflection after NB will be compiled on jdk 1.4
java.lang.reflect.Method m;
m = component.getToolkit().getClass().getMethod("getSystemSelection", EMPTY_CLASS_ARRAY); //NOI18N
return (Clipboard)(m.invoke(component.getToolkit(), EMPTY_OBJECT_ARRAY));
} catch (Exception he) {
}
return null;
}
/** Redefine to Object.equals() to prevent defaulting to Rectangle.equals()
* which would cause incorrect firing
*/
public boolean equals(Object o) {
return (this == o);
}
/** Adds listener to track when caret position was changed */
public void addChangeListener(ChangeListener l) {
listenerList.add(ChangeListener.class, l);
}
/** Removes listeners to caret position changes */
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
}
/** Notifies listeners that caret position has changed */
protected void fireStateChanged() {
// Fix of #24336 - always do in AWT thread
Utilities.runInEventDispatchThread(new Runnable() {
public void run() {
Object listeners[] = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0 ; i -= 2) {
if (listeners[i] == ChangeListener.class) {
if (changeEvent == null) {
changeEvent = new ChangeEvent(BaseCaret.this);
}
((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
}
}
}
});
updateSystemSelection();
}
/** Is the caret currently visible */
public final boolean isVisible() {
return visible;
}
protected void setVisibleImpl(boolean v) {
synchronized (this) {
Timer t = flasher;
if (t != null) {
if (visible) {
t.stop();
}
if (v) {
if (debugCaretFocusExtra){
System.err.println("starting the caret blinking timer: visible="+visible+", blinkVisible="+blinkVisible);
}
t.start();
} else {
if (debugCaretFocusExtra){
System.err.println("stopping the caret blinking timer");
}
t.stop();
}
}
visible = v;
}
JTextComponent c = component;
if (c != null) {
Rectangle repaintRect = this;
if (italic) {
repaintRect = new Rectangle(this);
repaintRect.width += repaintRect.height;
}
c.repaint(repaintRect);
}
}
synchronized void resetBlink() {
Timer t = flasher;
if (t != null) {
t.stop();
blinkVisible = true;
if (isVisible()) {
if (debugCaretFocusExtra){
System.err.println("Reset blinking (caret already visible) - starting the caret blinking timer: visible="+visible+", blinkVisible="+blinkVisible);
}
t.start();
}else{
if (debugCaretFocusExtra){
System.err.println("Reset blinking (caret not visible) - caret blinking timer couldn't be started: visible="+visible+", blinkVisible="+blinkVisible);
}
}
}
}
/** Sets the caret visibility */
public void setVisible(final boolean v) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
setVisibleImpl(v);
}
}
);
}
/** Is the selection visible? */
public final boolean isSelectionVisible() {
return selectionVisible;
}
/** Sets the selection visibility */
public void setSelectionVisible(boolean v) {
if (selectionVisible == v) {
return;
}
JTextComponent c = component;
if (c != null) {
selectionVisible = v;
if (selectionVisible) {
int caretPos = getDot();
int selPos = getMark();
boolean selMarkFirst = (selPos < caretPos);
selectionMark.activateLayer = selMarkFirst;
caretMark.activateLayer = !selMarkFirst && !(selPos == caretPos);
} else { // make selection invisible
caretMark.activateLayer = false;
selectionMark.activateLayer = false;
}
// repaint the block
BaseTextUI ui = (BaseTextUI)c.getUI();
try {
ui.getEditorUI().repaintBlock(caretMark.getOffset(), selectionMark.getOffset());
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
} catch (InvalidMarkException e) {
Utilities.annotateLoggable(e);
}
}
}
/** Saves the current caret position. This is used when
* caret up or down actions occur, moving between lines
* that have uneven end positions.
*
* @param p the Point to use for the saved position
*/
public void setMagicCaretPosition(Point p) {
magicCaretPosition = p;
}
/** Get position used to mark begining of the selected block */
public final Point getMagicCaretPosition() {
return magicCaretPosition;
}
/** Sets the caret blink rate.
* @param rate blink rate in milliseconds, 0 means no blink
*/
public synchronized void setBlinkRate(int rate) {
if (flasher == null && rate > 0) {
flasher = new Timer(rate, new WeakTimerListener(this));
}
if (flasher != null) {
if (rate > 0) {
if (flasher.getDelay() != rate) {
if (debugCaretFocusExtra){
System.err.println("blink rate:"+rate);
}
flasher.setDelay(rate);
}
} else { // zero rate - don't blink
if (debugCaretFocusExtra){
System.err.println("zero rate - don't blink");
System.err.println("setting blinkVisible to true and disabling timer");
}
flasher.stop();
flasher.removeActionListener(this);
flasher = null;
blinkVisible = true;
}
}
}
/** Returns blink rate of the caret or 0 if caret doesn't blink */
public synchronized int getBlinkRate() {
return (flasher != null) ? flasher.getDelay() : 0;
}
/** Gets the current position of the caret */
public int getDot() {
if (component != null) {
try {
return caretMark.getOffset();
} catch (InvalidMarkException e) {
}
}
return 0;
}
/** Gets the current position of the selection mark.
* If there's a selection this position will be different
* from the caret position.
*/
public int getMark() {
if (component != null) {
if (selectionVisible) {
try {
return selectionMark.getOffset();
} catch (InvalidMarkException e) {
}
} else { // selection not visible
return getDot(); // must return same position as dot
}
}
return 0;
}
public void setDot(int offset) {
setDot(offset, this, EditorUI.SCROLL_DEFAULT);
}
public void setDot(int offset, boolean expandFold) {
setDot(offset, this, EditorUI.SCROLL_DEFAULT, expandFold);
}
/** Sets the caret position to some position. This
* causes removal of the active selection. If expandFold set to true
* fold containing offset position will be expanded.
*/
public void setDot(int offset, Rectangle scrollRect, int scrollPolicy, boolean expandFold) {
JTextComponent c = component;
if (c != null) {
setSelectionVisible(false);
BaseDocument doc = (BaseDocument)c.getDocument();
boolean dotChanged = false;
doc.readLock();
try {
if (doc != null && offset >= 0 && offset <= doc.getLength()) {
dotChanged = true;
try {
Utilities.moveMark(doc, caretMark, offset);
// Unfold fold
FoldHierarchy hierarchy = FoldHierarchy.get(c);
hierarchy.lock();
try {
Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset);
if (expandFold && collapsed != null && collapsed.getStartOffset() < offset &&
collapsed.getEndOffset() > offset) {
hierarchy.expand(collapsed);
}
} finally {
hierarchy.unlock();
}
} catch (BadLocationException e) {
throw new IllegalStateException(e.toString());
// setting the caret to wrong position leaves it at current position
} catch (InvalidMarkException e) {
throw new IllegalStateException(e.toString());
// Caret not installed or inside the initial-read
}
}
} finally {
doc.readUnlock();
}
if (dotChanged) {
fireStateChanged();
dispatchUpdate(scrollRect, scrollPolicy);
}
}
}
/** Sets the caret position to some position. This
* causes removal of the active selection.
*/
public void setDot(int offset, Rectangle scrollRect, int scrollPolicy) {
setDot(offset, scrollRect, scrollPolicy, true);
}
public void moveDot(int offset) {
moveDot(offset, this, EditorUI.SCROLL_MOVE);
}
/** Makes selection by moving dot but leaving mark */
public void moveDot(int offset, Rectangle scrollRect, int scrollPolicy) {
JTextComponent c = component;
if (c != null) {
BaseDocument doc = (BaseDocument)c.getDocument();
if (doc != null && offset >= 0 && offset <= doc.getLength()) {
try {
int oldCaretPos = getDot();
if (offset == oldCaretPos) { // no change
return;
}
int selPos; // current position of selection mark
if (selectionVisible) {
selPos = selectionMark.getOffset();
} else {
Utilities.moveMark(doc, selectionMark, oldCaretPos);
selPos = oldCaretPos;
}
Utilities.moveMark(doc, caretMark, offset);
if (selectionVisible) { // selection already visible
boolean selMarkFirst = (selPos < offset);
selectionMark.activateLayer = selMarkFirst;
caretMark.activateLayer = !selMarkFirst && !(selPos == offset);
Utilities.getEditorUI(c).repaintBlock(oldCaretPos, offset);
if (selPos == offset) { // same positions -> invisible selection
setSelectionVisible(false);
}
} else { // selection not yet visible
setSelectionVisible(true);
}
} catch (BadLocationException e) {
throw new IllegalStateException(e.toString());
// position is incorrect
} catch (InvalidMarkException e) {
throw new IllegalStateException(e.toString());
}
}
fireStateChanged();
dispatchUpdate(scrollRect, scrollPolicy);
}
}
// DocumentListener methods
public void insertUpdate(DocumentEvent evt) {
JTextComponent c = component;
if (c != null) {
BaseDocument doc = (BaseDocument)component.getDocument();
BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
if ((bevt.isInUndo() || bevt.isInRedo())
&& component == Utilities.getLastActiveComponent()
) {
// in undo mode and current component
undoOffset = evt.getOffset() + evt.getLength();
} else {
undoOffset = -1;
}
modified = true;
modifiedUpdate();
}
}
public void removeUpdate(DocumentEvent evt) {
JTextComponent c = component;
if (c != null) {
BaseDocument doc = (BaseDocument)c.getDocument();
// make selection invisible if removal shrinked block to zero size
if (selectionVisible && (getDot() == getMark())) {
setSelectionVisible(false);
}
BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
if ((bevt.isInUndo() || bevt.isInRedo())
&& c == Utilities.getLastActiveComponent()
) {
// in undo mode and current component
undoOffset = evt.getOffset();
} else {
undoOffset = -1;
}
modified = true;
modifiedUpdate();
}
}
private void modifiedUpdate() {
if (!inAtomicLock) {
JTextComponent c = component;
if (modified && c != null) {
if (undoOffset >= 0) { // last modification was undo => set the dot to undoOffset
setDot(undoOffset);
} else { // last modification was not undo
fireStateChanged();
// Scroll to caret only for component with focus
dispatchUpdate(c.hasFocus() ? this : null, EditorUI.SCROLL_MOVE);
}
modified = false;
}
}
}
public void atomicLock(AtomicLockEvent evt) {
inAtomicLock = true;
}
public void atomicUnlock(AtomicLockEvent evt) {
inAtomicLock = false;
modifiedUpdate();
}
public void changedUpdate(DocumentEvent evt) {
dispatchUpdate();
}
// FocusListener methods
public void focusGained(FocusEvent evt) {
if (debugCaretFocusExtra){
System.err.println("");
}
if (debugCaretFocus || debugCaretFocusExtra) {
System.err.println("BaseCaret.focusGained() in doc="
+ component.getDocument().getProperty(Document.TitleProperty));
}
JTextComponent c = component;
if (c != null) {
updateType();
if (debugCaretFocusExtra){
System.err.println("going to set caret visible to: "+c.isEnabled());
}
setVisible(c.isEnabled()); // invisible caret if disabled
}else{
if (debugCaretFocusExtra){
System.err.println("component is null, caret will not be dislayed");
}
}
}
public void focusLost(FocusEvent evt) {
if (debugCaretFocusExtra){
System.err.println("");
}
if (debugCaretFocus || debugCaretFocusExtra) {
System.err.println("BaseCaret.focusLost() in doc="
+ component.getDocument().getProperty(Document.TitleProperty));
}
// XXX #17959. Eliminate "strange" focus lost event.
SwingUtilities.invokeLater(new Runnable(){
public void run(){
Component c = component;
if(c != null) {
java.awt.Window window = SwingUtilities.windowForComponent(c);
if(window != null) {
if(window.getFocusOwner() == c) {
if (debugCaretFocusExtra){
System.err.println("caret will remain visible, windowForComponent(c).getFocusOwner is the same as the c");
System.err.println("window.getFocusOwner:"+window.getFocusOwner());
System.err.println("component:"+c);
}
return;
}
}
}
if (debugCaretFocusExtra) {
System.err.println("going to set caret visible to: false");
}
setVisible(false);
}
});
}
// MouseListener methods
public void mouseClicked(MouseEvent evt) {
JTextComponent c = component;
if (c != null) {
if (SwingUtilities.isLeftMouseButton(evt)) {
if (evt.getClickCount() == 2) {
BaseTextUI ui = (BaseTextUI)c.getUI();
// Expand fold if offset is in collapsed fold
int offset = ui.viewToModel(c,
evt.getX(), evt.getY());
FoldHierarchy hierarchy = FoldHierarchy.get(c);
Document doc = c.getDocument();
if (doc instanceof AbstractDocument) {
AbstractDocument adoc = (AbstractDocument)doc;
adoc.readLock();
try {
hierarchy.lock();
try {
Fold collapsed = FoldUtilities.findCollapsedFold(
hierarchy, offset, offset);
if (collapsed != null && collapsed.getStartOffset() <= offset &&
collapsed.getEndOffset() >= offset) {
hierarchy.expand(collapsed);
} else {
if (selectWordAction == null) {
selectWordAction = ((BaseKit)ui.getEditorKit(
c)).getActionByName(BaseKit.selectWordAction);
}
selectWordAction.actionPerformed(null);
}
} finally {
hierarchy.unlock();
}
} finally {
adoc.readUnlock();
}
}
} else if (evt.getClickCount() == 3) {
if (selectLineAction == null) {
BaseTextUI ui = (BaseTextUI)c.getUI();
selectLineAction = ((BaseKit)ui.getEditorKit(
c)).getActionByName(BaseKit.selectLineAction);
}
selectLineAction.actionPerformed(null);
}
} else if (SwingUtilities.isMiddleMouseButton(evt)){
if (evt.getClickCount() == 1) {
if (c == null) return;
Toolkit tk = c.getToolkit();
Clipboard buffer = getSystemSelection();
if (buffer == null) return;
Transferable trans = buffer.getContents(null);
if (trans == null) return;
BaseDocument doc = (BaseDocument)c.getDocument();
if (doc == null) return;
int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
evt.getX(), evt.getY());
try{
String pastingString = (String)trans.getTransferData(DataFlavor.stringFlavor);
if (pastingString == null) return;
try {
doc.atomicLock();
try {
doc.insertString(offset, pastingString, null);
setDot(offset+pastingString.length());
} finally {
doc.atomicUnlock();
}
} catch( BadLocationException exc ) {
}
}catch(UnsupportedFlavorException ufe){
}catch(IOException ioe){
}
}
}
}
}
public void mousePressed(MouseEvent evt) {
JTextComponent c = component;
if (c != null) {
Utilities.getEditorUI(c).getWordMatch().clear(); // [PENDING] should be done cleanly
// Position the cursor at the appropriate place in the document
if ((SwingUtilities.isLeftMouseButton(evt) &&
(evt.getModifiers() & (InputEvent.META_MASK|InputEvent.ALT_MASK)) == 0) ||
!isSelectionVisible()) {
int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
evt.getX(), evt.getY());
if (offset >= 0) {
if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
moveDot(offset);
} else {
setDot(offset);
}
setMagicCaretPosition(null);
}
if (c.isEnabled()) {
c.requestFocus();
}
}
}
}
public void mouseReleased(MouseEvent evt) {
}
public void mouseEntered(MouseEvent evt) {
}
public void mouseExited(MouseEvent evt) {
}
// MouseMotionListener methods
public void mouseDragged(MouseEvent evt) {
JTextComponent c = component;
if (SwingUtilities.isLeftMouseButton(evt)) {
if (c != null) {
int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
evt.getX(), evt.getY());
// fix for #15204
if (offset == -1)
offset = 0;
// fix of #22846
if (offset >= 0 && (evt.getModifiers() & InputEvent.SHIFT_MASK) == 0)
moveDot(offset);
}
}
}
public void mouseMoved(MouseEvent evt) {
}
// PropertyChangeListener methods
public void propertyChange(PropertyChangeEvent evt) {
String propName = evt.getPropertyName();
if ("document".equals(propName)) { // NOI18N
BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument)
? (BaseDocument)evt.getOldValue() : null;
BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument)
? (BaseDocument)evt.getNewValue() : null;
modelChanged(oldDoc, newDoc);
} else if (EditorUI.OVERWRITE_MODE_PROPERTY.equals(propName)) {
Boolean b = (Boolean)evt.getNewValue();
overwriteMode = (b != null) ? b.booleanValue() : false;
updateType();
}
}
// ActionListener methods
/** Fired when blink timer fires */
public void actionPerformed(ActionEvent evt) {
JTextComponent c = component;
if (c != null) {
blinkVisible = !blinkVisible;
Rectangle repaintRect = this;
if (italic) {
repaintRect = new Rectangle(this);
repaintRect.width += repaintRect.height;
}
c.repaint(repaintRect);
}
}
public void foldHierarchyChanged(FoldHierarchyEvent evt) {
foldHierarchyUpdate((FoldHierarchy)evt.getSource());
}
private void foldHierarchyUpdate(FoldHierarchy hierarchy) {
int caretOffset = getDot();
// Update caret offset when it's inside collapsed fold
Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy,
caretOffset, caretOffset);
if (collapsed != null && collapsed.getStartOffset() < caretOffset) {
setDot(collapsed.getEndOffset(), false);
}
// Update caret's visual position
dispatchUpdate();
}
private static class FocusHandler implements FocusListener {
private transient FocusListener fl;
FocusHandler(FocusListener fl) {
this.fl = fl;
}
public void focusGained(FocusEvent e) {
fl.focusGained(e);
}
public void focusLost(FocusEvent e) {
fl.focusLost(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy