📄 jedittextarea.java
字号:
package org.jfree.designer.text.jedit;
/*
* JEditTextArea.java - jEdit's text component
* Copyright (C) 1999 Slava Pestov
*
* You may use and modify this package for any purpose. Redistribution is
* permitted, in both source and binary form, provided that this notice
* remains intact in all source distributions of this package.
*/
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.EventListenerList;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import org.jfree.designer.text.jedit.syntax.SyntaxDocument;
import org.jfree.designer.text.jedit.syntax.SyntaxStyle;
import org.jfree.designer.text.jedit.syntax.Token;
import org.jfree.designer.text.jedit.syntax.TokenMarker;
/**
* jEdit's text area component. It is more suited for editing program source code than
* JEditorPane, because it drops the unnecessary features (images, variable-width lines,
* and so on) and adds a whole bunch of useful goodies such as: <ul> <li>More flexible key
* binding scheme <li>Supports macro recorders <li>Rectangular selection <li>Bracket
* highlighting <li>Syntax highlighting <li>Command repetition <li>Block caret can be
* enabled </ul> It is also faster and doesn't have as many problems. It can be used in
* other applications; the only other part of jEdit it depends on is the syntax
* package.<p>
* <p/>
* To use it in your app, treat it like any other component, for example:
* <pre>JEditTextArea ta = new JEditTextArea();
* ta.setTokenMarker(new JavaTokenMarker());
* ta.setText("public class Test {\n"
* + " public static void main(String[] args) {\n"
* + " System.out.println(\"Hello World\");\n"
* + " }\n"
* + "}");</pre>
*
* @author Slava Pestov
* @version $Id: JEditTextArea.java,v 1.3 2004/04/20 18:54:51 taqua Exp $
*/
public final class JEditTextArea
extends JComponent
{
/**
* Adding components with this name to the text area will place them left of the
* horizontal scroll bar. In jEdit, the status bar is added this way.
*/
private static final String LEFT_OF_SCROLLBAR = "los";
/**
* Creates a new JEditTextArea with the default settings.
*/
public JEditTextArea ()
{
this(TextAreaDefaults.getDefaults());
}
/**
* Creates a new JEditTextArea with the specified settings.
*
* @param defaults The default settings
*/
private JEditTextArea (final TextAreaDefaults defaults)
{
// Enable the necessary events
enableEvents(AWTEvent.KEY_EVENT_MASK);
// Initialize some misc. stuff
painter = new TextAreaPainter(this, defaults);
documentHandler = new DocumentHandler();
listenerList = new EventListenerList();
caretEvent = new MutableCaretEvent();
lineSegment = new Segment();
bracketLine = bracketPosition = -1;
blink = true;
// Initialize the GUI
setLayout(new ScrollLayout());
add(CENTER, painter);
add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL));
add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
// Add some event listeners
vertical.addAdjustmentListener(new AdjustHandler());
horizontal.addAdjustmentListener(new AdjustHandler());
painter.addComponentListener(new ComponentHandler());
painter.addMouseListener(new MouseHandler());
painter.addMouseMotionListener(new DragHandler());
addFocusListener(new FocusHandler());
// Load the defaults
setInputHandler(defaults.inputHandler);
setDocument(defaults.document);
editable = defaults.editable;
caretVisible = defaults.caretVisible;
caretBlinks = defaults.caretBlinks;
electricScroll = defaults.electricScroll;
popup = defaults.popup;
// We don't seem to get the initial focus event?
focusedComponent = this;
}
/**
* Returns the object responsible for painting this text area.
*/
public final TextAreaPainter getPainter ()
{
return painter;
}
/**
* Returns the input handler.
*/
public final InputHandler getInputHandler ()
{
return inputHandler;
}
/**
* Sets the input handler.
*
* @param inputHandler The new input handler
*/
private void setInputHandler (final InputHandler inputHandler)
{
this.inputHandler = inputHandler;
}
/**
* Returns true if the caret is blinking, false otherwise.
*/
public final boolean isCaretBlinkEnabled ()
{
return caretBlinks;
}
/**
* Toggles caret blinking.
*
* @param caretBlinks True if the caret should blink, false otherwise
*/
public final void setCaretBlinkEnabled (final boolean caretBlinks)
{
this.caretBlinks = caretBlinks;
if (!caretBlinks)
{
blink = false;
}
painter.invalidateSelectedLines();
}
/**
* Returns true if the caret is visible, false otherwise.
*/
public final boolean isCaretVisible ()
{
return (!caretBlinks || blink) && caretVisible;
}
/**
* Sets if the caret should be visible.
*
* @param caretVisible True if the caret should be visible, false otherwise
*/
private void setCaretVisible (final boolean caretVisible)
{
this.caretVisible = caretVisible;
blink = true;
painter.invalidateSelectedLines();
}
/**
* Blinks the caret.
*/
private void blinkCaret ()
{
if (caretBlinks)
{
blink = !blink;
painter.invalidateSelectedLines();
}
else
{
blink = true;
}
}
/**
* Returns the number of lines from the top and button of the text area that are always
* visible.
*/
public final int getElectricScroll ()
{
return electricScroll;
}
/**
* Sets the number of lines from the top and bottom of the text area that are always
* visible
*
* @param electricScroll The number of lines always visible from the top or bottom
*/
public final void setElectricScroll (final int electricScroll)
{
this.electricScroll = electricScroll;
}
/**
* Updates the state of the scroll bars. This should be called if the number of lines in
* the document changes, or when the size of the text are changes.
*/
private void updateScrollBars ()
{
if (vertical != null && visibleLines != 0)
{
vertical.setValues(firstLine, visibleLines, 0, getLineCount());
vertical.setUnitIncrement(2);
vertical.setBlockIncrement(visibleLines);
}
final int width = painter.getWidth();
if (horizontal != null && width != 0)
{
horizontal.setValues(-horizontalOffset, width, 0, width * 5);
horizontal.setUnitIncrement(painter.getFontMetrics()
.charWidth('w'));
horizontal.setBlockIncrement(width / 2);
}
}
/**
* Returns the line displayed at the text area's origin.
*/
public final int getFirstLine ()
{
return firstLine;
}
/**
* Sets the line displayed at the text area's origin without updating the scroll bars.
*/
public final void setFirstLine (final int firstLine)
{
if (firstLine == this.firstLine)
{
return;
}
//final int oldFirstLine = this.firstLine;
this.firstLine = firstLine;
if (firstLine != vertical.getValue())
{
updateScrollBars();
}
painter.repaint();
}
/**
* Returns the number of lines visible in this text area.
*/
public final int getVisibleLines ()
{
return visibleLines;
}
/**
* Recalculates the number of visible lines. This should not be called directly.
*/
public final void recalculateVisibleLines ()
{
if (painter == null)
{
return;
}
final int height = painter.getHeight();
final int lineHeight = painter.getFontMetrics().getHeight();
//final int oldVisibleLines = visibleLines;
visibleLines = height / lineHeight;
updateScrollBars();
}
/**
* Returns the horizontal offset of drawn lines.
*/
public final int getHorizontalOffset ()
{
return horizontalOffset;
}
/**
* Sets the horizontal offset of drawn lines. This can be used to implement horizontal
* scrolling.
*
* @param horizontalOffset offset The new horizontal offset
*/
private void setHorizontalOffset (final int horizontalOffset)
{
if (horizontalOffset == this.horizontalOffset)
{
return;
}
this.horizontalOffset = horizontalOffset;
if (horizontalOffset != horizontal.getValue())
{
updateScrollBars();
}
painter.repaint();
}
/**
* A fast way of changing both the first line and horizontal offset.
*
* @param firstLine The new first line
* @param horizontalOffset The new horizontal offset
* @return True if any of the values were changed, false otherwise
*/
private boolean setOrigin (final int firstLine, final int horizontalOffset)
{
boolean changed = false;
//final int oldFirstLine = this.firstLine;
if (horizontalOffset != this.horizontalOffset)
{
this.horizontalOffset = horizontalOffset;
changed = true;
}
if (firstLine != this.firstLine)
{
this.firstLine = firstLine;
changed = true;
}
if (changed)
{
updateScrollBars();
painter.repaint();
}
return changed;
}
/**
* Ensures that the caret is visible by scrolling the text area if necessary.
*
* @return True if scrolling was actually performed, false if the caret was already
* visible
*/
private boolean scrollToCaret ()
{
final int line = getCaretLine();
final int lineStart = getLineStartOffset(line);
final int offset = Math.max(0, Math.min(getLineLength(line) - 1,
getCaretPosition() - lineStart));
return scrollTo(line, offset);
}
/**
* Ensures that the specified line and offset is visible by scrolling the text area if
* necessary.
*
* @param line The line to scroll to
* @param offset The offset in the line to scroll to
* @return True if scrolling was actually performed, false if the line and offset was
* already visible
*/
private boolean scrollTo (final int line, final int offset)
{
// visibleLines == 0 before the component is realized
// we can't do any proper scrolling then, so we have
// this hack...
if (visibleLines == 0)
{
setFirstLine(Math.max(0, line - electricScroll));
return true;
}
int newFirstLine = firstLine;
int newHorizontalOffset = horizontalOffset;
if (line < firstLine + electricScroll)
{
newFirstLine = Math.max(0, line - electricScroll);
}
else if (line + electricScroll >= firstLine + visibleLines)
{
newFirstLine = (line - visibleLines) + electricScroll + 1;
if (newFirstLine + visibleLines >= getLineCount())
{
newFirstLine = getLineCount() - visibleLines;
}
if (newFirstLine < 0)
{
newFirstLine = 0;
}
}
final int x = _offsetToX(line, offset);
final int width = painter.getFontMetrics().charWidth('w');
if (x < 0)
{
newHorizontalOffset = Math.min(0, horizontalOffset
- x + width + 5);
}
else if (x + width >= painter.getWidth())
{
newHorizontalOffset = horizontalOffset +
(painter.getWidth() - x) - width - 5;
}
return setOrigin(newFirstLine, newHorizontalOffset);
}
/**
* Converts a line index to a y co-ordinate.
*
* @param line The line
*/
public final int lineToY (final int line)
{
final FontMetrics fm = painter.getFontMetrics();
return (line - firstLine) * fm.getHeight()
- (fm.getLeading() + fm.getMaxDescent());
}
/**
* Converts a y co-ordinate to a line index.
*
* @param y The y co-ordinate
*/
private int yToLine (final int y)
{
final FontMetrics fm = painter.getFontMetrics();
final int height = fm.getHeight();
return Math.max(0, Math.min(getLineCount() - 1,
y / height + firstLine));
}
/**
* Converts an offset in a line into an x co-ordinate. This is a slow version that can
* be used any time.
*
* @param line The line
* @param offset The offset, from the start of the line
*/
public final int offsetToX (final int line, final int offset)
{
// don't use cached tokens
painter.currentLineTokens = null;
return _offsetToX(line, offset);
}
/**
* Converts an offset in a line into an x co-ordinate. This is a fast version that
* should only be used if no changes were made to the text since the last repaint.
*
* @param line The line
* @param offset The offset, from the start of the line
*/
public final int _offsetToX (final int line, final int offset)
{
final TokenMarker tokenMarker = getTokenMarker();
/* Use painter's cached info for speed */
FontMetrics fm = painter.getFontMetrics();
getLineText(line, lineSegment);
final int segmentOffset = lineSegment.offset;
int x = horizontalOffset;
/* If syntax coloring is disabled, do simple translation */
if (tokenMarker == null)
{
lineSegment.count = offset;
return x + Utilities.getTabbedTextWidth(lineSegment,
fm, x, painter, 0);
}
/* If syntax coloring is enabled, we have to do this because
* tokens can vary in width */
else
{
Token tokens;
if (painter.currentLineIndex == line
&& painter.currentLineTokens != null)
{
tokens = painter.currentLineTokens;
}
else
{
painter.currentLineIndex = line;
tokens = painter.currentLineTokens
= tokenMarker.markTokens(lineSegment, line);
}
//final Toolkit toolkit = painter.getToolkit();
final Font defaultFont = painter.getFont();
final SyntaxStyle[] styles = painter.getStyles();
for (; ;)
{
final byte id = tokens.id;
if (id == Token.END)
{
return x;
}
if (id == Token.NULL)
{
fm = painter.getFontMetrics();
}
else
{
fm = styles[id].getFontMetrics(this, defaultFont);
}
final int length = tokens.length;
if (offset + segmentOffset < lineSegment.offset + length)
{
lineSegment.count = offset - (lineSegment.offset - segmentOffset);
return x + Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0);
}
else
{
lineSegment.count = length;
x += Utilities.getTabbedTextWidth(lineSegment, fm, x, painter, 0);
lineSegment.offset += length;
}
tokens = tokens.next;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -