📄 configurablecaret.java
字号:
* <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.
if (component != null) {
active = e;
TextUI mapper = component.getUI();
if (visible != e) {
visible = e;
// repaint the caret
try {
Rectangle loc = mapper.modelToView(component, dot);
validateWidth(loc);
damage(loc);
}
catch (BadLocationException badloc) {
// hmm... not legally positioned
}
}
} // End of if (component != null).
if (flasher != null) {
if (visible) {
flasher.start();
}
else {
flasher.stop();
}
}
}
/*****************************************************************************/
public String toString() {
String s = "Dot=" + dot;
s += " Mark=" + mark;
return s;
}
/*****************************************************************************/
private void updateSystemSelection() {
if (this.dot != this.mark && component != null) {
Clipboard clip = getSystemSelection();
if (clip != null) {
String selectedText = null;
selectedText = component.getSelectedText();
clip.setContents(new StringSelection(selectedText),
getClipboardOwner());
ownsSelection = true;
}
}
}
/*****************************************************************************/
/**
* Helper function used by the block and underline carets to
* ensure the width of the painted caret is valid. This is
* done for the following reasons:
*
* <ul>
* <li>The <code>View</code> classes in the javax.swing.text
* package always return a width of "1" when
* <code>modelToView</code> is called. We'll be needing
* the actual width.</li>
* <li>Even in smart views, such as
* <code>RSyntaxTextArea</code>'s <code>SyntaxView</code>
* and <code>WrappedSyntaxView</code> that return the
* width of the current character, if the caret is at
* the end of a line for example, the width returned
* from <code>modelToView</code> will be 0 (as the width
* of unprintable characters such as '\n' is calculated
* as 0). In this case, we'll use a default width
* value.</li>
* </ul>
*
* @param rect The rectangle returned by the current
* <code>View</code>'s <code>modelToView</code>
* method for the caret position.
*/
private final void validateWidth(Rectangle rect) {
// If the width value > 1, we assume the View is
// a "smart" view that returned the proper width.
// So only worry about this stuff if width <= 1.
if (rect != null && rect.width <= 1) {
// The width is either 1 (most likely, we're
// using a "dumb" view like those in
// javax.swing.text) or 0 (most likely,
// we're using a "smart" view like
// org.fife.ui.rsyntaxtextarea.SyntaxView,
// we're at the end of a line, and the
// width of '\n' is being computed as 0).
try {
// Try to get a width for the character
// at the caret position. We use the text
// area's font instead of g's because
// g's may vary in an RSyntaxTextArea.
component.getDocument().getText(dot, 1, seg);
Font font = component.getFont();
FontMetrics fm = component.
getFontMetrics(font);
rect.width = fm.charWidth(
seg.array[seg.offset]);
// This width being returned 0 likely means
// that it is an unprintable character (which
// is almost 100% to be a newline char, i.e.,
// we're at the end of a line). So, just use
// the width of a space.
if (rect.width == 0) {
rect.width = fm.charWidth(' ');
}
}
catch (BadLocationException ble) {
// This shouldn't ever happen.
ble.printStackTrace();
rect.width = 8;
}
} // End of if (rect.width<=1).
}
/*****************************************************************************/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(getStyle());
}
/*****************************************************************************/
/*********************** PRIVATE INNER CLASSES *******************************/
/*****************************************************************************/
/**
* Scrolls the text component that contains this caret so that this
* caret is visible. This operation is wrapped in a
* <code>Runnable</code> so that it is threadsafe.
*/
class SafeScroller
implements Runnable {
private Rectangle r;
SafeScroller(Rectangle r) {
this.r = r;
}
public void run() {
if (component != null) {
component.scrollRectToVisible(r);
}
}
}
/*****************************************************************************/
/**
* Handles many events from this caret.
*/
class Handler
implements PropertyChangeListener, DocumentListener,
ActionListener, ClipboardOwner {
/**
* 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);
if (r != null && r.width != 0 && r.height != 0) {
validateWidth(r);
damage(r);
}
}
catch (BadLocationException ble) {
}
}
}
visible = !visible;
repaint();
}
/**
* 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 (!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 adjust = 0;
int offset = e.getOffset();
int length = e.getLength();
int newDot = dot;
short changed = 0;
if (component.inUndoRedo()) {
setDot(offset + length);
return;
}
if (newDot >= offset) {
newDot += length;
changed |= 1;
}
int newMark = mark;
if (newMark >= offset) {
newMark += length;
changed |= 2;
}
if (changed != 0) {
if (newMark == newDot) {
setDot(newDot);
ensureValidPosition();
}
else {
setDot(newMark);
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);
}
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 (!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 adjust = 0;
int newDot = dot;
int newMark = mark;
if (component.inUndoRedo()) {
setDot(offs0);
return;
}
if (newDot >= offs1) {
newDot -= (offs1 - offs0);
}
else if (newDot >= offs0) {
newDot = offs0;
}
if (newMark >= offs1) {
newMark -= (offs1 - offs0);
}
else if (newMark >= offs0) {
newMark = offs0;
}
if (newMark == newDot) {
forceCaretPositionChange = true;
try {
setDot(newDot);
}
finally {
forceCaretPositionChange = false;
}
ensureValidPosition();
}
else {
setDot(newMark);
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);
}
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 (!SwingUtilities.isEventDispatchThread()) {
return;
}
if (component.inUndoRedo()) {
setDot(e.getOffset() + e.getLength());
}
}
/**
* 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);
}
}
}
}
/**
* 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);
}
}
}
}
/*****************************************************************************/
/**
* Filter bypass for this caret class.
*/
private class DefaultFilterBypass
extends NavigationFilter.FilterBypass {
public Caret getCaret() {
return ConfigurableCaret.this;
}
public void setDot(int dot, Position.Bias bias) {
handleSetDot(dot);
}
public void moveDot(int dot, Position.Bias bias) {
handleMoveDot(dot);
}
}
/*****************************************************************************/
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -