|
What this is
Other links
The source code/*
* 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
|
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 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.