org.fife.ui.rtextarea.ConfigurableCaret Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
/*
* 12/21/2004
*
* ConfigurableCaret.java - The caret used by RTextArea.
* Copyright (C) 2004 Robert Futrell
* robert_futrell at users.sourceforge.net
* http://fifesoft.com/rsyntaxtextarea
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.fife.ui.rtextarea;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.io.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
/**
* The caret used by {@link RTextArea}. This caret has all of the properties
* that javax.swing.text.DefaultCaret
does, as well as adding the
* following niceties:
*
*
* - This caret can paint itself several different ways:
*
* - As a vertical line (like
DefaultCaret
)
* - As a slightly thicker vertical line (like Eclipse)
* - As an underline
* - As a "block caret"
* - As a rectangle around the current character
*
* - On Microsoft Windows and other operating systems that do not
* support system selection (i.e., selecting text, then pasting
* via the middle mouse button), clicking the middle mouse button
* will cause a regular paste operation to occur. On systems
* that support system selection (i.e., all UNIX variants),
* the middle mouse button will behave normally.
*
*
* @author Robert Futrell
* @version 0.6
*/
public class ConfigurableCaret extends DefaultCaret {
/**
* The minimum value of a caret style.
*/
public static final int MIN_STYLE = 0;
/**
* The vertical line style.
*/
public static final int VERTICAL_LINE_STYLE = 0;
/**
* The horizontal line style.
*/
public static final int UNDERLINE_STYLE = 1;
/**
* The block style.
*/
public static final int BLOCK_STYLE = 2;
/**
* The block border style.
*/
public static final int BLOCK_BORDER_STYLE = 3;
/**
* A thicker vertical line (2 pixels instead of 1).
*/
public static final int THICK_VERTICAL_LINE_STYLE = 4;
/**
* The maximum value of a caret style.
*/
public static final int MAX_STYLE = THICK_VERTICAL_LINE_STYLE;
/**
* Action used to select a word on a double click.
*/
static private transient Action selectWord = null;
/**
* Action used to select a line on a triple click.
*/
static private transient Action selectLine = null;
/**
* holds last MouseEvent which caused the word selection
*/
private transient MouseEvent selectedWordEvent = null;
/**
* Used for fastest-possible retrieval of the character at the
* caret's position in the document.
*/
private transient Segment seg;
/**
* Whether the caret is a vertical line, a horizontal line, or a block.
*/
private int style;
/**
* The selection painter. By default this paints selections with the
* text area's selection color.
*/
private ChangeableHighlightPainter selectionPainter;
/**
* Whether this is Java 1.4.
*/
/* TODO: Remove me when 1.4 support is removed. */
private static final boolean IS_JAVA_1_4 =
"1.4".equals(System.getProperty("java.specification.version"));
/**
* Creates the caret using {@link #VERTICAL_LINE_STYLE}.
*/
public ConfigurableCaret() {
this(VERTICAL_LINE_STYLE);
}
/**
* Constructs a new ConfigurableCaret
.
*
* @param style The style to use when painting the caret. If this is
* invalid, then {@link #VERTICAL_LINE_STYLE} is used.
*/
public ConfigurableCaret(int style) {
seg = new Segment();
setStyle(style);
selectionPainter = new ChangeableHighlightPainter();
}
/**
* 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
positionCaret(e);
}
/**
* Adjusts the focus, if necessary.
*
* @param inWindow if true indicates requestFocusInWindow should be used
*/
private void adjustFocus(boolean inWindow) {
RTextArea textArea = getTextArea();
if ((textArea != null) && textArea.isEnabled() &&
textArea.isRequestFocusEnabled()) {
if (inWindow)
textArea.requestFocusInWindow();
else
textArea.requestFocus();
}
}
/**
* Overridden to damage the correct width of the caret, since this caret
* can be different sizes.
*
* @param r The current location of the caret.
*/
protected synchronized void damage(Rectangle r) {
if (r != null) {
validateWidth(r); // Check for "0" or "1" caret width
x = r.x - 1;
y = r.y;
width = r.width + 4;
height = r.height;
repaint();
}
}
/**
* 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 text component. If this is not an
* RTextArea
, an Exception
* will be thrown.
* @see Caret#deinstall
*/
public void deinstall(JTextComponent c) {
if (!(c instanceof RTextArea))
throw new IllegalArgumentException(
"c must be instance of RTextArea");
super.deinstall(c);
}
/**
* Gets the text editor component that this caret is bound to.
*
* @return The RTextArea
.
*/
protected RTextArea getTextArea() {
return (RTextArea)getComponent();
}
/**
* Returns whether this caret's selection uses rounded edges.
*
* @return Whether this caret's edges are rounded.
* @see #setRoundedSelectionEdges
*/
public boolean getRoundedSelectionEdges() {
return ((ChangeableHighlightPainter)getSelectionPainter()).
getRoundedEdges();
}
/**
* Gets the painter for the Highlighter. This is overridden to return
* our custom selection painter.
*
* @return The painter.
*/
protected Highlighter.HighlightPainter getSelectionPainter() {
return selectionPainter;
}
/**
* Gets the current style of this caret.
*
* @return The caret's style.
* @see #setStyle(int)
*/
public int getStyle() {
return style;
}
/**
* Installs this caret on a text component.
*
* @param c The text component. If this is not an {@link RTextArea},
* an Exception
will be thrown.
* @see Caret#install
*/
public void install(JTextComponent c) {
if (!(c instanceof RTextArea))
throw new IllegalArgumentException(
"c must be instance of RTextArea");
super.install(c);
}
/**
* 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 (! e.isConsumed()) {
RTextArea textArea = getTextArea();
int nclicks = e.getClickCount();
if (SwingUtilities.isLeftMouseButton(e)) {
if (nclicks<=2) {
// Only handle these clicks for 1.4. In 1.5 the word
// selection is (also?) handled in mousePressed, and if we
// handle it here, our word selection gets doubled-up.
if (IS_JAVA_1_4) {
if (nclicks==1) {
selectedWordEvent = null;
}
else { // 2
selectWord(e);
selectedWordEvent = null;
}
}
}
else {
nclicks %= 2; // Alternate selecting word/line.
switch (nclicks) {
case 0:
selectWord(e);
selectedWordEvent = null;
break;
case 1:
Action a = null;
ActionMap map = textArea.getActionMap();
if (map != null)
a = map.get(RTextAreaEditorKit.selectLineAction);
if (a == null) {
if (selectLine == null) {
selectLine = new RTextAreaEditorKit.SelectLineAction();
}
a = selectLine;
}
a.actionPerformed(new ActionEvent(textArea,
ActionEvent.ACTION_PERFORMED,
null, e.getWhen(), e.getModifiers()));
}
}
}
else if (SwingUtilities.isMiddleMouseButton(e)) {
if (nclicks == 1 && textArea.isEditable() && textArea.isEnabled()) {
// Paste the system selection, if it exists (e.g., on UNIX
// platforms, the user can select text, the middle-mouse click
// to paste it; this doesn't work on Windows). If the system
// doesn't support system selection, just do a normal paste.
JTextComponent c = (JTextComponent) e.getSource();
if (c != null) {
try {
Toolkit tk = c.getToolkit();
Clipboard buffer = tk.getSystemSelection();
// If the system supports system selections, (e.g. UNIX),
// try to do it.
if (buffer != null) {
adjustCaret(e);
TransferHandler th = c.getTransferHandler();
if (th != null) {
Transferable trans = buffer.getContents(null);
if (trans != null)
th.importData(c, trans);
}
adjustFocus(true);
}
// If the system doesn't support system selections
// (e.g. Windows), just do a normal paste.
else {
textArea.paste();
}
} catch (HeadlessException he) {
// do nothing... there is no system clipboard
}
} // if (c!=null)
} // if (nclicks == 1 && component.isEditable() && component.isEnabled())
} // else if (SwingUtilities.isMiddleMouseButton(e))
} // if (!c.isConsumed())
}
/**
* Paints the cursor.
*
* @param g The graphics context in which to paint.
*/
public void paint(Graphics g) {
// If the cursor is currently visible...
if (isVisible()) {
try {
RTextArea textArea = getTextArea();
g.setColor(textArea.getCaretColor());
TextUI mapper = textArea.getUI();
Rectangle r = mapper.modelToView(textArea, getDot());
// "Correct" the value of rect.width (takes into
// account caret being at EOL (and thus rect.width==1),
// etc.
// We do this even for LINE_STYLE because
// if they change from that caret to block/underline,
// the first time they do so width==1, so it will take
// one caret flash to paint correctly (wider). If we
// do this every time, then it's painted correctly the
// first blink.
validateWidth(r);
// Need to subtract 2 from height, otherwise
// the caret will expand too far vertically.
r.height -= 2;
switch (style) {
// Draw a big rectangle, and xor the foreground color.
case BLOCK_STYLE:
g.setXORMode(Color.WHITE);
// fills x==r.x to x==(r.x+(r.width)-1), inclusive.
g.fillRect(r.x,r.y, r.width,r.height);
break;
// Draw a rectangular border.
case BLOCK_BORDER_STYLE:
// fills x==r.x to x==(r.x+(r.width-1)), inclusive.
g.drawRect(r.x,r.y, r.width-1,r.height);
break;
// Draw an "underline" below the current position.
case UNDERLINE_STYLE:
g.setXORMode(Color.WHITE);
int y = r.y + r.height;
g.drawLine(r.x,y, r.x+r.width-1,y);
break;
// Draw a vertical line.
default:
case VERTICAL_LINE_STYLE:
g.drawLine(r.x,r.y, r.x,r.y+r.height);
break;
// A thicker vertical line.
case THICK_VERTICAL_LINE_STYLE:
g.drawLine(r.x,r.y, r.x,r.y+r.height);
r.x++;
g.drawLine(r.x,r.y, r.x,r.y+r.height);
break;
} // End of switch (style).
} catch (BadLocationException ble) {
ble.printStackTrace();
}
} // End of if (isVisible()).
}
/**
* Deserializes a caret. This is overridden to read the caret's style.
*
* @param s The stream to read from.
* @throws ClassNotFoundException
* @throws IOException
*/
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException {
s.defaultReadObject();
setStyle(s.readInt());
seg = new Segment();
}
/**
* Selects word based on the MouseEvent
*/
private void selectWord(MouseEvent e) {
if (selectedWordEvent != null
&& selectedWordEvent.getX() == e.getX()
&& selectedWordEvent.getY() == e.getY()) {
// We've already the done selection for this.
return;
}
Action a = null;
RTextArea textArea = getTextArea();
ActionMap map = textArea.getActionMap();
if (map != null) {
a = map.get(RTextAreaEditorKit.selectWordAction);
}
if (a == null) {
if (selectWord == null) {
selectWord = new RTextAreaEditorKit.SelectWordAction();
}
a = selectWord;
}
a.actionPerformed(new ActionEvent(textArea,
ActionEvent.ACTION_PERFORMED,
null, e.getWhen(), e.getModifiers()));
selectedWordEvent = e;
}
/**
* Sets whether this caret's selection should have rounded edges.
*
* @param rounded Whether it should have rounded edges.
* @see #getRoundedSelectionEdges()
*/
public void setRoundedSelectionEdges(boolean rounded) {
((ChangeableHighlightPainter)getSelectionPainter()).
setRoundedEdges(rounded);
}
/**
* Overridden to always render the selection, even when the text component
* loses focus.
*
* @param visible Whether the selection should be visible. This parameter
* is ignored.
*/
public void setSelectionVisible(boolean visible) {
super.setSelectionVisible(true);
}
/**
* Sets the style used when painting the caret.
*
* @param style The style to use. If this isn't one of
* VERTICAL_LINE_STYLE
, UNDERLINE_STYLE
,
* or BLOCK_STYLE
, then
* VERTICAL_LINE_STYLE
is used.
* @see #getStyle()
*/
public void setStyle(int style) {
if (styleMAX_STYLE)
style = VERTICAL_LINE_STYLE;
this.style = style;
repaint();
}
/**
* 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:
*
*
* - The
View
classes in the javax.swing.text package
* always return a width of "1" when modelToView
is
* called. We'll be needing the actual width.
* - Even in smart views, such as
RSyntaxTextArea
's
* SyntaxView
and WrappedSyntaxView
that
* return the width of the current character, if the caret is at the
* end of a line for example, the width returned from
* modelToView
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.
*
*
* @param rect The rectangle returned by the current
* View
's modelToView
* method for the caret position.
*/
private 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.
RTextArea textArea = getTextArea();
textArea.getDocument().getText(getDot(),1, seg);
Font font = textArea.getFont();
FontMetrics fm = textArea.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!=null && rect.width<=1).
}
/**
* Serializes this caret. This is overridden to write the style of the
* caret.
*
* @param s The stream to write to.
* @throws IOException If an IO error occurs.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(getStyle());
}
}