mirror of
https://github.com/arduino/Arduino.git
synced 2024-11-30 11:24:12 +01:00
2292 lines
59 KiB
Java
2292 lines
59 KiB
Java
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
package processing.app.syntax;
|
|
|
|
import processing.app.*;
|
|
|
|
import javax.swing.event.*;
|
|
import javax.swing.text.*;
|
|
import javax.swing.undo.*;
|
|
import javax.swing.*;
|
|
import java.awt.datatransfer.*;
|
|
import java.awt.event.*;
|
|
import java.awt.*;
|
|
import java.io.*;
|
|
import java.util.Enumeration;
|
|
import java.util.Vector;
|
|
|
|
/**
|
|
* 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>
|
|
*
|
|
* 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$
|
|
*/
|
|
public 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.
|
|
*/
|
|
public static 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
|
|
*/
|
|
public JEditTextArea(TextAreaDefaults defaults)
|
|
{
|
|
// Enable the necessary events
|
|
enableEvents(AWTEvent.KEY_EVENT_MASK);
|
|
|
|
// Initialize some misc. stuff
|
|
painter = new TextAreaPainter(this,defaults);
|
|
documentHandler = new DocumentHandler();
|
|
eventListenerList = 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;
|
|
|
|
// We don't seem to get the initial focus event?
|
|
focusedComponent = this;
|
|
|
|
// no more need for reflection, pde requires use of java 1.4
|
|
/*
|
|
if (System.getProperty("java.version").startsWith("1.4")) {
|
|
try {
|
|
Class kWheelHandler =
|
|
Class.forName("processing.app.syntax.WheelHandler");
|
|
java.lang.reflect.Constructor konstructor =
|
|
kWheelHandler.getConstructor(new Class[] { getClass() });
|
|
konstructor.newInstance(new Object[] { this });
|
|
} catch (Exception e) {
|
|
System.err.println(e);
|
|
}
|
|
}
|
|
*/
|
|
//new WheelHandler(this);
|
|
addMouseWheelListener(new MouseWheelListener() {
|
|
public void mouseWheelMoved(MouseWheelEvent e) {
|
|
if (!scrollBarsInitialized) return;
|
|
int amt = e.getWheelRotation();
|
|
vertical.setValue(vertical.getValue() + amt * 3);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Get current position of the vertical scroll bar. [fry]
|
|
*/
|
|
public int getScrollPosition() {
|
|
return vertical.getValue();
|
|
}
|
|
|
|
|
|
/**
|
|
* Set position of the vertical scroll bar. [fry]
|
|
*/
|
|
public void setScrollPosition(int what) {
|
|
vertical.setValue(what);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns if this component can be traversed by pressing
|
|
* the Tab key. This returns false.
|
|
*/
|
|
public final boolean isManagingFocus()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public void setInputHandler(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 void setCaretBlinkEnabled(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
|
|
*/
|
|
public void setCaretVisible(boolean caretVisible)
|
|
{
|
|
this.caretVisible = caretVisible;
|
|
blink = true;
|
|
|
|
painter.invalidateSelectedLines();
|
|
}
|
|
|
|
/**
|
|
* Blinks the caret.
|
|
*/
|
|
public final 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(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.
|
|
*/
|
|
public void updateScrollBars() {
|
|
if (vertical != null && visibleLines != 0) {
|
|
vertical.setValues(firstLine,visibleLines,0,getLineCount());
|
|
vertical.setUnitIncrement(2);
|
|
vertical.setBlockIncrement(visibleLines);
|
|
}
|
|
|
|
//if (horizontal != null && width != 0) {
|
|
if ((horizontal != null) && (painter.getWidth() != 0)) {
|
|
int value = horizontal.getValue();
|
|
//System.out.println("updateScrollBars");
|
|
//int width = painter.getWidth();
|
|
int lineCount = getLineCount();
|
|
int maxLineLength = 0;
|
|
for (int i = 0; i < lineCount; i++) {
|
|
int lineLength = getLineLength(i);
|
|
if (lineLength > maxLineLength) {
|
|
maxLineLength = lineLength;
|
|
}
|
|
}
|
|
int charWidth = painter.getFontMetrics().charWidth('w');
|
|
int width = maxLineLength * charWidth;
|
|
int painterWidth = painter.getWidth();
|
|
//System.out.println("max line len " + maxLineLength);
|
|
//System.out.println("width " + width);
|
|
//System.out.println("text area width " + painter.getWidth());
|
|
|
|
// this was the default, but it's enormous
|
|
//horizontal.setValues(-horizontalOffset,width,0,width * 5);
|
|
|
|
// something more reasonable, though this is a bad solution
|
|
//horizontal.setValues(-horizontalOffset,width,0,width * 2);
|
|
|
|
// in general.. time to start looking at that other syntax pkg
|
|
// since most code should fit the window horizontally, just use
|
|
// the default settings for the width, this is a nicer solution
|
|
// until a better update mechanism can be implemented [fry]
|
|
|
|
//horizontal.setValues(0, width, 0, width);
|
|
//0, width - horizontalOffset);
|
|
// works, from pre-75 versions of p5
|
|
//horizontal.setValues(-horizontalOffset, width, 0, width);
|
|
|
|
// gets weird when writing to the end of lines
|
|
//horizontal.setValues(value, painterWidth, 0, width);
|
|
|
|
// seems to work, implemented for 0075
|
|
horizontal.setValues(-horizontalOffset, painterWidth, 0, width);
|
|
|
|
//horizontal.setUnitIncrement(painter.getFontMetrics().charWidth('w'));
|
|
horizontal.setUnitIncrement(charWidth);
|
|
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 void setFirstLine(int firstLine)
|
|
{
|
|
if(firstLine == this.firstLine)
|
|
return;
|
|
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;
|
|
int height = painter.getHeight();
|
|
int lineHeight = painter.getFontMetrics().getHeight();
|
|
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
|
|
*/
|
|
public void setHorizontalOffset(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
|
|
*/
|
|
public boolean setOrigin(int firstLine, int horizontalOffset)
|
|
{
|
|
boolean changed = false;
|
|
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
|
|
*/
|
|
public boolean scrollToCaret()
|
|
{
|
|
int line = getCaretLine();
|
|
int lineStart = getLineStartOffset(line);
|
|
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
|
|
*/
|
|
public boolean scrollTo(int line, 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;
|
|
}
|
|
|
|
int x = _offsetToX(line,offset);
|
|
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 int lineToY(int line)
|
|
{
|
|
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
|
|
*/
|
|
public int yToLine(int y)
|
|
{
|
|
FontMetrics fm = painter.getFontMetrics();
|
|
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(int line, 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 int _offsetToX(int line, int offset)
|
|
{
|
|
TokenMarker tokenMarker = getTokenMarker();
|
|
|
|
/* Use painter's cached info for speed */
|
|
FontMetrics fm = painter.getFontMetrics();
|
|
|
|
getLineText(line,lineSegment);
|
|
|
|
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);
|
|
}
|
|
|
|
Toolkit toolkit = painter.getToolkit();
|
|
Font defaultFont = painter.getFont();
|
|
SyntaxStyle[] styles = painter.getStyles();
|
|
|
|
for(;;)
|
|
{
|
|
byte id = tokens.id;
|
|
if(id == Token.END)
|
|
{
|
|
return x;
|
|
}
|
|
|
|
if(id == Token.NULL)
|
|
fm = painter.getFontMetrics();
|
|
else
|
|
fm = styles[id].getFontMetrics(defaultFont);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts an x co-ordinate to an offset within a line.
|
|
* @param line The line
|
|
* @param x The x co-ordinate
|
|
*/
|
|
public int xToOffset(int line, int x)
|
|
{
|
|
TokenMarker tokenMarker = getTokenMarker();
|
|
|
|
/* Use painter's cached info for speed */
|
|
FontMetrics fm = painter.getFontMetrics();
|
|
|
|
getLineText(line,lineSegment);
|
|
|
|
char[] segmentArray = lineSegment.array;
|
|
int segmentOffset = lineSegment.offset;
|
|
int segmentCount = lineSegment.count;
|
|
|
|
int width = horizontalOffset;
|
|
|
|
if(tokenMarker == null)
|
|
{
|
|
for(int i = 0; i < segmentCount; i++)
|
|
{
|
|
char c = segmentArray[i + segmentOffset];
|
|
int charWidth;
|
|
if(c == '\t')
|
|
charWidth = (int)painter.nextTabStop(width,i)
|
|
- width;
|
|
else
|
|
charWidth = fm.charWidth(c);
|
|
|
|
if(painter.isBlockCaretEnabled())
|
|
{
|
|
if(x - charWidth <= width)
|
|
return i;
|
|
}
|
|
else
|
|
{
|
|
if(x - charWidth / 2 <= width)
|
|
return i;
|
|
}
|
|
|
|
width += charWidth;
|
|
}
|
|
|
|
return segmentCount;
|
|
}
|
|
else
|
|
{
|
|
Token tokens;
|
|
if(painter.currentLineIndex == line && painter
|
|
.currentLineTokens != null)
|
|
tokens = painter.currentLineTokens;
|
|
else
|
|
{
|
|
painter.currentLineIndex = line;
|
|
tokens = painter.currentLineTokens
|
|
= tokenMarker.markTokens(lineSegment,line);
|
|
}
|
|
|
|
int offset = 0;
|
|
Toolkit toolkit = painter.getToolkit();
|
|
Font defaultFont = painter.getFont();
|
|
SyntaxStyle[] styles = painter.getStyles();
|
|
|
|
for(;;)
|
|
{
|
|
byte id = tokens.id;
|
|
if(id == Token.END)
|
|
return offset;
|
|
|
|
if(id == Token.NULL)
|
|
fm = painter.getFontMetrics();
|
|
else
|
|
fm = styles[id].getFontMetrics(defaultFont);
|
|
|
|
int length = tokens.length;
|
|
|
|
for(int i = 0; i < length; i++)
|
|
{
|
|
char c = segmentArray[segmentOffset + offset + i];
|
|
int charWidth;
|
|
if(c == '\t')
|
|
charWidth = (int)painter.nextTabStop(width,offset + i)
|
|
- width;
|
|
else
|
|
charWidth = fm.charWidth(c);
|
|
|
|
if(painter.isBlockCaretEnabled())
|
|
{
|
|
if(x - charWidth <= width)
|
|
return offset + i;
|
|
}
|
|
else
|
|
{
|
|
if(x - charWidth / 2 <= width)
|
|
return offset + i;
|
|
}
|
|
|
|
width += charWidth;
|
|
}
|
|
|
|
offset += length;
|
|
tokens = tokens.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a point to an offset, from the start of the text.
|
|
* @param x The x co-ordinate of the point
|
|
* @param y The y co-ordinate of the point
|
|
*/
|
|
public int xyToOffset(int x, int y)
|
|
{
|
|
int line = yToLine(y);
|
|
int start = getLineStartOffset(line);
|
|
return start + xToOffset(line,x);
|
|
}
|
|
|
|
/**
|
|
* Returns the document this text area is editing.
|
|
*/
|
|
public final SyntaxDocument getDocument()
|
|
{
|
|
return document;
|
|
}
|
|
|
|
/**
|
|
* Sets the document this text area is editing.
|
|
* @param document The document
|
|
*/
|
|
public void setDocument(SyntaxDocument document) {
|
|
if (this.document == document)
|
|
return;
|
|
if (this.document != null)
|
|
this.document.removeDocumentListener(documentHandler);
|
|
this.document = document;
|
|
|
|
document.addDocumentListener(documentHandler);
|
|
|
|
select(0, 0);
|
|
updateScrollBars();
|
|
painter.repaint();
|
|
}
|
|
|
|
|
|
/**
|
|
* Set document with a twist, includes the old caret
|
|
* and scroll positions, added for p5. [fry]
|
|
*/
|
|
public void setDocument(SyntaxDocument document,
|
|
int start, int stop, int scroll) {
|
|
if (this.document == document)
|
|
return;
|
|
if (this.document != null)
|
|
this.document.removeDocumentListener(documentHandler);
|
|
this.document = document;
|
|
|
|
document.addDocumentListener(documentHandler);
|
|
|
|
select(start, stop);
|
|
updateScrollBars();
|
|
setScrollPosition(scroll);
|
|
painter.repaint();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the document's token marker. Equivalent to calling
|
|
* <code>getDocument().getTokenMarker()</code>.
|
|
*/
|
|
public final TokenMarker getTokenMarker()
|
|
{
|
|
return document.getTokenMarker();
|
|
}
|
|
|
|
/**
|
|
* Sets the document's token marker. Equivalent to caling
|
|
* <code>getDocument().setTokenMarker()</code>.
|
|
* @param tokenMarker The token marker
|
|
*/
|
|
public final void setTokenMarker(TokenMarker tokenMarker)
|
|
{
|
|
document.setTokenMarker(tokenMarker);
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the document. Equivalent to calling
|
|
* <code>getDocument().getLength()</code>.
|
|
*/
|
|
public final int getDocumentLength()
|
|
{
|
|
return document.getLength();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of lines in the document.
|
|
*/
|
|
public final int getLineCount()
|
|
{
|
|
return document.getDefaultRootElement().getElementCount();
|
|
}
|
|
|
|
/**
|
|
* Returns the line containing the specified offset.
|
|
* @param offset The offset
|
|
*/
|
|
public final int getLineOfOffset(int offset)
|
|
{
|
|
return document.getDefaultRootElement().getElementIndex(offset);
|
|
}
|
|
|
|
/**
|
|
* Returns the start offset of the specified line.
|
|
* @param line The line
|
|
* @return The start offset of the specified line, or -1 if the line is
|
|
* invalid
|
|
*/
|
|
public int getLineStartOffset(int line)
|
|
{
|
|
Element lineElement = document.getDefaultRootElement()
|
|
.getElement(line);
|
|
if(lineElement == null)
|
|
return -1;
|
|
else
|
|
return lineElement.getStartOffset();
|
|
}
|
|
|
|
/**
|
|
* Returns the end offset of the specified line.
|
|
* @param line The line
|
|
* @return The end offset of the specified line, or -1 if the line is
|
|
* invalid.
|
|
*/
|
|
public int getLineEndOffset(int line)
|
|
{
|
|
Element lineElement = document.getDefaultRootElement()
|
|
.getElement(line);
|
|
if(lineElement == null)
|
|
return -1;
|
|
else
|
|
return lineElement.getEndOffset();
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the specified line.
|
|
* @param line The line
|
|
*/
|
|
public int getLineLength(int line)
|
|
{
|
|
Element lineElement = document.getDefaultRootElement()
|
|
.getElement(line);
|
|
if(lineElement == null)
|
|
return -1;
|
|
else
|
|
return lineElement.getEndOffset()
|
|
- lineElement.getStartOffset() - 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the entire text of this text area.
|
|
*/
|
|
public String getText()
|
|
{
|
|
try
|
|
{
|
|
return document.getText(0,document.getLength());
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the entire text of this text area.
|
|
*/
|
|
public void setText(String text)
|
|
{
|
|
try {
|
|
document.beginCompoundEdit();
|
|
document.remove(0,document.getLength());
|
|
document.insertString(0,text,null);
|
|
|
|
} catch (BadLocationException bl) {
|
|
bl.printStackTrace();
|
|
|
|
} finally {
|
|
document.endCompoundEdit();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the specified substring of the document.
|
|
* @param start The start offset
|
|
* @param len The length of the substring
|
|
* @return The substring, or null if the offsets are invalid
|
|
*/
|
|
public final String getText(int start, int len)
|
|
{
|
|
try
|
|
{
|
|
return document.getText(start,len);
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies the specified substring of the document into a segment.
|
|
* If the offsets are invalid, the segment will contain a null string.
|
|
* @param start The start offset
|
|
* @param len The length of the substring
|
|
* @param segment The segment
|
|
*/
|
|
public final void getText(int start, int len, Segment segment)
|
|
{
|
|
try
|
|
{
|
|
document.getText(start,len,segment);
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
segment.offset = segment.count = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the text on the specified line.
|
|
* @param lineIndex The line
|
|
* @return The text, or null if the line is invalid
|
|
*/
|
|
public final String getLineText(int lineIndex)
|
|
{
|
|
int start = getLineStartOffset(lineIndex);
|
|
return getText(start,getLineEndOffset(lineIndex) - start - 1);
|
|
}
|
|
|
|
/**
|
|
* Copies the text on the specified line into a segment. If the line
|
|
* is invalid, the segment will contain a null string.
|
|
* @param lineIndex The line
|
|
*/
|
|
public final void getLineText(int lineIndex, Segment segment)
|
|
{
|
|
int start = getLineStartOffset(lineIndex);
|
|
getText(start,getLineEndOffset(lineIndex) - start - 1,segment);
|
|
}
|
|
|
|
/**
|
|
* Returns the selection start offset.
|
|
*/
|
|
public final int getSelectionStart()
|
|
{
|
|
return selectionStart;
|
|
}
|
|
|
|
/**
|
|
* Returns the offset where the selection starts on the specified
|
|
* line.
|
|
*/
|
|
public int getSelectionStart(int line)
|
|
{
|
|
if(line == selectionStartLine)
|
|
return selectionStart;
|
|
else if(rectSelect)
|
|
{
|
|
Element map = document.getDefaultRootElement();
|
|
int start = selectionStart - map.getElement(selectionStartLine)
|
|
.getStartOffset();
|
|
|
|
Element lineElement = map.getElement(line);
|
|
int lineStart = lineElement.getStartOffset();
|
|
int lineEnd = lineElement.getEndOffset() - 1;
|
|
return Math.min(lineEnd,lineStart + start);
|
|
}
|
|
else
|
|
return getLineStartOffset(line);
|
|
}
|
|
|
|
/**
|
|
* Returns the selection start line.
|
|
*/
|
|
public final int getSelectionStartLine()
|
|
{
|
|
return selectionStartLine;
|
|
}
|
|
|
|
/**
|
|
* Sets the selection start. The new selection will be the new
|
|
* selection start and the old selection end.
|
|
* @param selectionStart The selection start
|
|
* @see #select(int,int)
|
|
*/
|
|
public final void setSelectionStart(int selectionStart)
|
|
{
|
|
select(selectionStart,selectionEnd);
|
|
}
|
|
|
|
/**
|
|
* Returns the selection end offset.
|
|
*/
|
|
public final int getSelectionEnd()
|
|
{
|
|
return selectionEnd;
|
|
}
|
|
|
|
/**
|
|
* Returns the offset where the selection ends on the specified
|
|
* line.
|
|
*/
|
|
public int getSelectionEnd(int line)
|
|
{
|
|
if(line == selectionEndLine)
|
|
return selectionEnd;
|
|
else if(rectSelect)
|
|
{
|
|
Element map = document.getDefaultRootElement();
|
|
int end = selectionEnd - map.getElement(selectionEndLine)
|
|
.getStartOffset();
|
|
|
|
Element lineElement = map.getElement(line);
|
|
int lineStart = lineElement.getStartOffset();
|
|
int lineEnd = lineElement.getEndOffset() - 1;
|
|
return Math.min(lineEnd,lineStart + end);
|
|
}
|
|
else
|
|
return getLineEndOffset(line) - 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the selection end line.
|
|
*/
|
|
public final int getSelectionEndLine()
|
|
{
|
|
return selectionEndLine;
|
|
}
|
|
|
|
/**
|
|
* Sets the selection end. The new selection will be the old
|
|
* selection start and the bew selection end.
|
|
* @param selectionEnd The selection end
|
|
* @see #select(int,int)
|
|
*/
|
|
public final void setSelectionEnd(int selectionEnd)
|
|
{
|
|
select(selectionStart,selectionEnd);
|
|
}
|
|
|
|
public final boolean isSelectionActive()
|
|
{
|
|
return(selectionStart != selectionEnd);
|
|
}
|
|
|
|
/**
|
|
* Returns the caret position. This will either be the selection
|
|
* start or the selection end, depending on which direction the
|
|
* selection was made in.
|
|
*/
|
|
public final int getCaretPosition()
|
|
{
|
|
return (biasLeft ? selectionStart : selectionEnd);
|
|
}
|
|
|
|
/**
|
|
* Returns the caret line.
|
|
*/
|
|
public final int getCaretLine()
|
|
{
|
|
return (biasLeft ? selectionStartLine : selectionEndLine);
|
|
}
|
|
|
|
/**
|
|
* Returns the mark position. This will be the opposite selection
|
|
* bound to the caret position.
|
|
* @see #getCaretPosition()
|
|
*/
|
|
public final int getMarkPosition()
|
|
{
|
|
return (biasLeft ? selectionEnd : selectionStart);
|
|
}
|
|
|
|
/**
|
|
* Returns the mark line.
|
|
*/
|
|
public final int getMarkLine()
|
|
{
|
|
return (biasLeft ? selectionEndLine : selectionStartLine);
|
|
}
|
|
|
|
/**
|
|
* Sets the caret position. The new selection will consist of the
|
|
* caret position only (hence no text will be selected)
|
|
* @param caret The caret position
|
|
* @see #select(int,int)
|
|
*/
|
|
public final void setCaretPosition(int caret)
|
|
{
|
|
select(caret,caret);
|
|
}
|
|
|
|
/**
|
|
* Selects all text in the document.
|
|
*/
|
|
public final void selectAll()
|
|
{
|
|
select(0,getDocumentLength());
|
|
}
|
|
|
|
/**
|
|
* Moves the mark to the caret position.
|
|
*/
|
|
public final void selectNone()
|
|
{
|
|
select(getCaretPosition(),getCaretPosition());
|
|
}
|
|
|
|
/**
|
|
* Selects from the start offset to the end offset. This is the
|
|
* general selection method used by all other selecting methods.
|
|
* The caret position will be start if start < end, and end
|
|
* if end > start.
|
|
* @param start The start offset
|
|
* @param end The end offset
|
|
*/
|
|
public void select(int start, int end)
|
|
{
|
|
int newStart, newEnd;
|
|
boolean newBias;
|
|
if(start <= end)
|
|
{
|
|
newStart = start;
|
|
newEnd = end;
|
|
newBias = false;
|
|
}
|
|
else
|
|
{
|
|
newStart = end;
|
|
newEnd = start;
|
|
newBias = true;
|
|
}
|
|
|
|
if(newStart < 0 || newEnd > getDocumentLength())
|
|
{
|
|
throw new IllegalArgumentException("Bounds out of"
|
|
+ " range: " + newStart + "," +
|
|
newEnd);
|
|
}
|
|
|
|
// If the new position is the same as the old, we don't
|
|
// do all this crap, however we still do the stuff at
|
|
// the end (clearing magic position, scrolling)
|
|
if(newStart != selectionStart || newEnd != selectionEnd
|
|
|| newBias != biasLeft)
|
|
{
|
|
int newStartLine = getLineOfOffset(newStart);
|
|
int newEndLine = getLineOfOffset(newEnd);
|
|
|
|
if(painter.isBracketHighlightEnabled())
|
|
{
|
|
if(bracketLine != -1)
|
|
painter.invalidateLine(bracketLine);
|
|
updateBracketHighlight(end);
|
|
if(bracketLine != -1)
|
|
painter.invalidateLine(bracketLine);
|
|
}
|
|
|
|
painter.invalidateLineRange(selectionStartLine,selectionEndLine);
|
|
painter.invalidateLineRange(newStartLine,newEndLine);
|
|
|
|
document.addUndoableEdit(new CaretUndo(selectionStart,selectionEnd));
|
|
|
|
selectionStart = newStart;
|
|
selectionEnd = newEnd;
|
|
selectionStartLine = newStartLine;
|
|
selectionEndLine = newEndLine;
|
|
biasLeft = newBias;
|
|
|
|
fireCaretEvent();
|
|
}
|
|
|
|
// When the user is typing, etc, we don't want the caret
|
|
// to blink
|
|
blink = true;
|
|
caretTimer.restart();
|
|
|
|
// Disable rectangle select if selection start = selection end
|
|
if(selectionStart == selectionEnd)
|
|
rectSelect = false;
|
|
|
|
// Clear the `magic' caret position used by up/down
|
|
magicCaret = -1;
|
|
|
|
scrollToCaret();
|
|
|
|
// notify the line number feller
|
|
if (editorLineStatus != null) {
|
|
editorLineStatus.set(selectionStartLine, selectionEndLine);
|
|
//System.out.println("why " + selectionStartLine + " " + selectionEndLine);
|
|
//System.out.println(getLineOfOffset(start) + " " +
|
|
// getLineOfOffset(end));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the selected text, or null if no selection is active.
|
|
*/
|
|
public final String getSelectedText()
|
|
{
|
|
if(selectionStart == selectionEnd)
|
|
return null;
|
|
|
|
if(rectSelect)
|
|
{
|
|
// Return each row of the selection on a new line
|
|
|
|
Element map = document.getDefaultRootElement();
|
|
|
|
int start = selectionStart - map.getElement(selectionStartLine)
|
|
.getStartOffset();
|
|
int end = selectionEnd - map.getElement(selectionEndLine)
|
|
.getStartOffset();
|
|
|
|
// Certain rectangles satisfy this condition...
|
|
if(end < start)
|
|
{
|
|
int tmp = end;
|
|
end = start;
|
|
start = tmp;
|
|
}
|
|
|
|
StringBuffer buf = new StringBuffer();
|
|
Segment seg = new Segment();
|
|
|
|
for(int i = selectionStartLine; i <= selectionEndLine; i++)
|
|
{
|
|
Element lineElement = map.getElement(i);
|
|
int lineStart = lineElement.getStartOffset();
|
|
int lineEnd = lineElement.getEndOffset() - 1;
|
|
int lineLen = lineEnd - lineStart;
|
|
|
|
lineStart = Math.min(lineStart + start,lineEnd);
|
|
lineLen = Math.min(end - start,lineEnd - lineStart);
|
|
|
|
getText(lineStart,lineLen,seg);
|
|
buf.append(seg.array,seg.offset,seg.count);
|
|
|
|
if(i != selectionEndLine)
|
|
buf.append('\n');
|
|
}
|
|
|
|
return buf.toString();
|
|
}
|
|
else
|
|
{
|
|
return getText(selectionStart,
|
|
selectionEnd - selectionStart);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces the selection with the specified text.
|
|
* @param selectedText The replacement text for the selection
|
|
*/
|
|
public void setSelectedText(String selectedText)
|
|
{
|
|
if(!editable)
|
|
{
|
|
throw new InternalError("Text component"
|
|
+ " read only");
|
|
}
|
|
|
|
document.beginCompoundEdit();
|
|
|
|
try
|
|
{
|
|
if(rectSelect)
|
|
{
|
|
Element map = document.getDefaultRootElement();
|
|
|
|
int start = selectionStart - map.getElement(selectionStartLine)
|
|
.getStartOffset();
|
|
int end = selectionEnd - map.getElement(selectionEndLine)
|
|
.getStartOffset();
|
|
|
|
// Certain rectangles satisfy this condition...
|
|
if(end < start)
|
|
{
|
|
int tmp = end;
|
|
end = start;
|
|
start = tmp;
|
|
}
|
|
|
|
int lastNewline = 0;
|
|
int currNewline = 0;
|
|
|
|
for(int i = selectionStartLine; i <= selectionEndLine; i++)
|
|
{
|
|
Element lineElement = map.getElement(i);
|
|
int lineStart = lineElement.getStartOffset();
|
|
int lineEnd = lineElement.getEndOffset() - 1;
|
|
int rectStart = Math.min(lineEnd,lineStart + start);
|
|
|
|
document.remove(rectStart,Math.min(lineEnd - rectStart,
|
|
end - start));
|
|
|
|
if(selectedText == null)
|
|
continue;
|
|
|
|
currNewline = selectedText.indexOf('\n',lastNewline);
|
|
if(currNewline == -1)
|
|
currNewline = selectedText.length();
|
|
|
|
document.insertString(rectStart,selectedText
|
|
.substring(lastNewline,currNewline),null);
|
|
|
|
lastNewline = Math.min(selectedText.length(),
|
|
currNewline + 1);
|
|
}
|
|
|
|
if(selectedText != null &&
|
|
currNewline != selectedText.length())
|
|
{
|
|
int offset = map.getElement(selectionEndLine)
|
|
.getEndOffset() - 1;
|
|
document.insertString(offset,"\n",null);
|
|
document.insertString(offset + 1,selectedText
|
|
.substring(currNewline + 1),null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
document.remove(selectionStart,
|
|
selectionEnd - selectionStart);
|
|
if(selectedText != null)
|
|
{
|
|
document.insertString(selectionStart,
|
|
selectedText,null);
|
|
}
|
|
}
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
throw new InternalError("Cannot replace"
|
|
+ " selection");
|
|
}
|
|
// No matter what happends... stops us from leaving document
|
|
// in a bad state
|
|
finally
|
|
{
|
|
document.endCompoundEdit();
|
|
}
|
|
|
|
setCaretPosition(selectionEnd);
|
|
}
|
|
|
|
/**
|
|
* Returns true if this text area is editable, false otherwise.
|
|
*/
|
|
public final boolean isEditable()
|
|
{
|
|
return editable;
|
|
}
|
|
|
|
/**
|
|
* Sets if this component is editable.
|
|
* @param editable True if this text area should be editable,
|
|
* false otherwise
|
|
*/
|
|
public final void setEditable(boolean editable)
|
|
{
|
|
this.editable = editable;
|
|
}
|
|
|
|
/**
|
|
* Returns the right click popup menu.
|
|
*/
|
|
public final JPopupMenu getRightClickPopup()
|
|
{
|
|
return popup;
|
|
}
|
|
|
|
/**
|
|
* Sets the right click popup menu.
|
|
* @param popup The popup
|
|
*/
|
|
//public final void setRightClickPopup(EditPopupMenu popup)
|
|
public final void setRightClickPopup(JPopupMenu popup)
|
|
{
|
|
this.popup = popup;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the `magic' caret position. This can be used to preserve
|
|
* the column position when moving up and down lines.
|
|
*/
|
|
public final int getMagicCaretPosition()
|
|
{
|
|
return magicCaret;
|
|
}
|
|
|
|
/**
|
|
* Sets the `magic' caret position. This can be used to preserve
|
|
* the column position when moving up and down lines.
|
|
* @param magicCaret The magic caret position
|
|
*/
|
|
public final void setMagicCaretPosition(int magicCaret)
|
|
{
|
|
this.magicCaret = magicCaret;
|
|
}
|
|
|
|
/**
|
|
* Similar to <code>setSelectedText()</code>, but overstrikes the
|
|
* appropriate number of characters if overwrite mode is enabled.
|
|
* @param str The string
|
|
* @see #setSelectedText(String)
|
|
* @see #isOverwriteEnabled()
|
|
*/
|
|
public void overwriteSetSelectedText(String str)
|
|
{
|
|
// Don't overstrike if there is a selection
|
|
if(!overwrite || selectionStart != selectionEnd)
|
|
{
|
|
setSelectedText(str);
|
|
return;
|
|
}
|
|
|
|
// Don't overstrike if we're on the end of
|
|
// the line
|
|
int caret = getCaretPosition();
|
|
int caretLineEnd = getLineEndOffset(getCaretLine());
|
|
if(caretLineEnd - caret <= str.length())
|
|
{
|
|
setSelectedText(str);
|
|
return;
|
|
}
|
|
|
|
document.beginCompoundEdit();
|
|
|
|
try
|
|
{
|
|
document.remove(caret,str.length());
|
|
document.insertString(caret,str,null);
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
}
|
|
finally
|
|
{
|
|
document.endCompoundEdit();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if overwrite mode is enabled, false otherwise.
|
|
*/
|
|
public final boolean isOverwriteEnabled()
|
|
{
|
|
return overwrite;
|
|
}
|
|
|
|
/**
|
|
* Sets if overwrite mode should be enabled.
|
|
* @param overwrite True if overwrite mode should be enabled,
|
|
* false otherwise.
|
|
*/
|
|
public final void setOverwriteEnabled(boolean overwrite)
|
|
{
|
|
this.overwrite = overwrite;
|
|
painter.invalidateSelectedLines();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the selection is rectangular, false otherwise.
|
|
*/
|
|
public final boolean isSelectionRectangular()
|
|
{
|
|
return rectSelect;
|
|
}
|
|
|
|
/**
|
|
* Sets if the selection should be rectangular.
|
|
* @param rectSelect True if the selection should be rectangular,
|
|
* false otherwise.
|
|
*/
|
|
public final void setSelectionRectangular(boolean rectSelect)
|
|
{
|
|
this.rectSelect = rectSelect;
|
|
painter.invalidateSelectedLines();
|
|
}
|
|
|
|
/**
|
|
* Returns the position of the highlighted bracket (the bracket
|
|
* matching the one before the caret)
|
|
*/
|
|
public final int getBracketPosition()
|
|
{
|
|
return bracketPosition;
|
|
}
|
|
|
|
/**
|
|
* Returns the line of the highlighted bracket (the bracket
|
|
* matching the one before the caret)
|
|
*/
|
|
public final int getBracketLine()
|
|
{
|
|
return bracketLine;
|
|
}
|
|
|
|
/**
|
|
* Adds a caret change listener to this text area.
|
|
* @param listener The listener
|
|
*/
|
|
public final void addCaretListener(CaretListener listener)
|
|
{
|
|
eventListenerList.add(CaretListener.class,listener);
|
|
}
|
|
|
|
/**
|
|
* Removes a caret change listener from this text area.
|
|
* @param listener The listener
|
|
*/
|
|
public final void removeCaretListener(CaretListener listener)
|
|
{
|
|
eventListenerList.remove(CaretListener.class,listener);
|
|
}
|
|
|
|
/**
|
|
* Deletes the selected text from the text area and places it
|
|
* into the clipboard.
|
|
*/
|
|
public void cut()
|
|
{
|
|
if(editable)
|
|
{
|
|
copy();
|
|
setSelectedText("");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Places the selected text into the clipboard.
|
|
*/
|
|
public void copy()
|
|
{
|
|
if(selectionStart != selectionEnd)
|
|
{
|
|
Clipboard clipboard = getToolkit().getSystemClipboard();
|
|
|
|
String selection = getSelectedText();
|
|
|
|
int repeatCount = inputHandler.getRepeatCount();
|
|
StringBuffer buf = new StringBuffer();
|
|
for(int i = 0; i < repeatCount; i++)
|
|
buf.append(selection);
|
|
|
|
clipboard.setContents(new StringSelection(buf.toString()),null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inserts the clipboard contents into the text.
|
|
*/
|
|
public void paste() {
|
|
if (editable) {
|
|
Clipboard clipboard = getToolkit().getSystemClipboard();
|
|
try {
|
|
// The MacOS MRJ doesn't convert \r to \n, so do it here
|
|
String selection = ((String)clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor)).replace('\r','\n');
|
|
|
|
// particularly on macosx when pasting from safari,
|
|
// replace unicode x00A0 (non-breaking space)
|
|
// with just a plain space. [fry 030929]
|
|
selection = selection.replace('\u00A0', ' ');
|
|
|
|
int repeatCount = inputHandler.getRepeatCount();
|
|
StringBuffer buf = new StringBuffer();
|
|
for (int i = 0; i < repeatCount; i++)
|
|
buf.append(selection);
|
|
selection = buf.toString();
|
|
setSelectedText(selection);
|
|
|
|
} catch(Exception e) {
|
|
getToolkit().beep();
|
|
System.err.println("Clipboard does not contain a string");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the AWT when this component is removed from it's parent.
|
|
* This stops clears the currently focused component.
|
|
*/
|
|
public void removeNotify()
|
|
{
|
|
super.removeNotify();
|
|
if(focusedComponent == this)
|
|
focusedComponent = null;
|
|
}
|
|
|
|
/**
|
|
* Forwards key events directly to the input handler.
|
|
* This is slightly faster than using a KeyListener
|
|
* because some Swing overhead is avoided.
|
|
*/
|
|
public EditorListener editorListener;
|
|
|
|
/**
|
|
* The component that tracks the current line number.
|
|
*/
|
|
public EditorLineStatus editorLineStatus;
|
|
|
|
|
|
public void processKeyEvent(KeyEvent evt) {
|
|
// this had to be added in Processing 007X, because the menu key
|
|
// events weren't making it up to the frame.
|
|
super.processKeyEvent(evt);
|
|
|
|
//System.out.println("jedittextarea: " + evt);
|
|
//System.out.println();
|
|
if (inputHandler == null) return;
|
|
|
|
switch(evt.getID()) {
|
|
case KeyEvent.KEY_TYPED:
|
|
//if ((editorListener != null) && !editorListener.keyTyped(evt)) {
|
|
inputHandler.keyTyped(evt);
|
|
break;
|
|
case KeyEvent.KEY_PRESSED:
|
|
if ((editorListener != null) && !editorListener.keyPressed(evt)) {
|
|
inputHandler.keyPressed(evt);
|
|
}
|
|
break;
|
|
case KeyEvent.KEY_RELEASED:
|
|
inputHandler.keyReleased(evt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// protected members
|
|
protected static String CENTER = "center";
|
|
protected static String RIGHT = "right";
|
|
protected static String BOTTOM = "bottom";
|
|
|
|
protected static JEditTextArea focusedComponent;
|
|
protected static Timer caretTimer;
|
|
|
|
protected TextAreaPainter painter;
|
|
|
|
//protected EditPopupMenu popup;
|
|
protected JPopupMenu popup;
|
|
|
|
protected EventListenerList eventListenerList;
|
|
protected MutableCaretEvent caretEvent;
|
|
|
|
protected boolean caretBlinks;
|
|
protected boolean caretVisible;
|
|
protected boolean blink;
|
|
|
|
protected boolean editable;
|
|
|
|
protected int firstLine;
|
|
protected int visibleLines;
|
|
protected int electricScroll;
|
|
|
|
protected int horizontalOffset;
|
|
|
|
protected JScrollBar vertical;
|
|
protected JScrollBar horizontal;
|
|
protected boolean scrollBarsInitialized;
|
|
|
|
protected InputHandler inputHandler;
|
|
protected SyntaxDocument document;
|
|
protected DocumentHandler documentHandler;
|
|
|
|
protected Segment lineSegment;
|
|
|
|
protected int selectionStart;
|
|
protected int selectionStartLine;
|
|
protected int selectionEnd;
|
|
protected int selectionEndLine;
|
|
protected boolean biasLeft;
|
|
|
|
protected int bracketPosition;
|
|
protected int bracketLine;
|
|
|
|
protected int magicCaret;
|
|
protected boolean overwrite;
|
|
protected boolean rectSelect;
|
|
|
|
|
|
protected void fireCaretEvent()
|
|
{
|
|
Object[] listeners = eventListenerList.getListenerList();
|
|
for(int i = listeners.length - 2; i >= 0; i--)
|
|
{
|
|
if(listeners[i] == CaretListener.class)
|
|
{
|
|
((CaretListener)listeners[i+1]).caretUpdate(caretEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void updateBracketHighlight(int newCaretPosition)
|
|
{
|
|
if(newCaretPosition == 0)
|
|
{
|
|
bracketPosition = bracketLine = -1;
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
int offset = TextUtilities.findMatchingBracket(
|
|
document,newCaretPosition - 1);
|
|
if(offset != -1)
|
|
{
|
|
bracketLine = getLineOfOffset(offset);
|
|
bracketPosition = offset - getLineStartOffset(bracketLine);
|
|
return;
|
|
}
|
|
}
|
|
catch(BadLocationException bl)
|
|
{
|
|
bl.printStackTrace();
|
|
}
|
|
|
|
bracketLine = bracketPosition = -1;
|
|
}
|
|
|
|
protected void documentChanged(DocumentEvent evt)
|
|
{
|
|
DocumentEvent.ElementChange ch =
|
|
evt.getChange(document.getDefaultRootElement());
|
|
|
|
int count;
|
|
if(ch == null)
|
|
count = 0;
|
|
else
|
|
count = ch.getChildrenAdded().length -
|
|
ch.getChildrenRemoved().length;
|
|
|
|
int line = getLineOfOffset(evt.getOffset());
|
|
if(count == 0)
|
|
{
|
|
painter.invalidateLine(line);
|
|
}
|
|
// do magic stuff
|
|
else if(line < firstLine)
|
|
{
|
|
setFirstLine(firstLine + count);
|
|
}
|
|
// end of magic stuff
|
|
else
|
|
{
|
|
painter.invalidateLineRange(line,firstLine + visibleLines);
|
|
updateScrollBars();
|
|
}
|
|
}
|
|
|
|
class ScrollLayout implements LayoutManager
|
|
{
|
|
//final int LEFT_EXTRA = 5;
|
|
|
|
public void addLayoutComponent(String name, Component comp)
|
|
{
|
|
if(name.equals(CENTER))
|
|
center = comp;
|
|
else if(name.equals(RIGHT))
|
|
right = comp;
|
|
else if(name.equals(BOTTOM))
|
|
bottom = comp;
|
|
else if(name.equals(LEFT_OF_SCROLLBAR))
|
|
leftOfScrollBar.addElement(comp);
|
|
}
|
|
|
|
public void removeLayoutComponent(Component comp)
|
|
{
|
|
if(center == comp)
|
|
center = null;
|
|
if(right == comp)
|
|
right = null;
|
|
if(bottom == comp)
|
|
bottom = null;
|
|
else
|
|
leftOfScrollBar.removeElement(comp);
|
|
}
|
|
|
|
public Dimension preferredLayoutSize(Container parent)
|
|
{
|
|
Dimension dim = new Dimension();
|
|
Insets insets = getInsets();
|
|
dim.width = insets.left + insets.right;
|
|
dim.height = insets.top + insets.bottom;
|
|
|
|
Dimension centerPref = center.getPreferredSize();
|
|
dim.width += centerPref.width;
|
|
dim.height += centerPref.height;
|
|
Dimension rightPref = right.getPreferredSize();
|
|
dim.width += rightPref.width;
|
|
Dimension bottomPref = bottom.getPreferredSize();
|
|
dim.height += bottomPref.height;
|
|
|
|
return dim;
|
|
}
|
|
|
|
public Dimension minimumLayoutSize(Container parent)
|
|
{
|
|
Dimension dim = new Dimension();
|
|
Insets insets = getInsets();
|
|
dim.width = insets.left + insets.right;
|
|
dim.height = insets.top + insets.bottom;
|
|
|
|
Dimension centerPref = center.getMinimumSize();
|
|
dim.width += centerPref.width;
|
|
dim.height += centerPref.height;
|
|
Dimension rightPref = right.getMinimumSize();
|
|
dim.width += rightPref.width;
|
|
Dimension bottomPref = bottom.getMinimumSize();
|
|
dim.height += bottomPref.height;
|
|
|
|
dim.height += 5;
|
|
|
|
return dim;
|
|
}
|
|
|
|
public void layoutContainer(Container parent)
|
|
{
|
|
Dimension size = parent.getSize();
|
|
Insets insets = parent.getInsets();
|
|
int itop = insets.top;
|
|
int ileft = insets.left;
|
|
int ibottom = insets.bottom;
|
|
int iright = insets.right;
|
|
|
|
int rightWidth = right.getPreferredSize().width;
|
|
int bottomHeight = bottom.getPreferredSize().height;
|
|
int centerWidth = size.width - rightWidth - ileft - iright;
|
|
int centerHeight = size.height - bottomHeight - itop - ibottom;
|
|
|
|
center.setBounds(ileft, // + LEFT_EXTRA,
|
|
itop,
|
|
centerWidth, // - LEFT_EXTRA,
|
|
centerHeight);
|
|
|
|
right.setBounds(ileft + centerWidth,
|
|
itop,
|
|
rightWidth,
|
|
centerHeight);
|
|
|
|
// Lay out all status components, in order
|
|
Enumeration status = leftOfScrollBar.elements();
|
|
while (status.hasMoreElements()) {
|
|
Component comp = (Component)status.nextElement();
|
|
Dimension dim = comp.getPreferredSize();
|
|
comp.setBounds(ileft,
|
|
itop + centerHeight,
|
|
dim.width,
|
|
bottomHeight);
|
|
ileft += dim.width;
|
|
}
|
|
|
|
bottom.setBounds(ileft,
|
|
itop + centerHeight,
|
|
size.width - rightWidth - ileft - iright,
|
|
bottomHeight);
|
|
}
|
|
|
|
// private members
|
|
private Component center;
|
|
private Component right;
|
|
private Component bottom;
|
|
private Vector leftOfScrollBar = new Vector();
|
|
}
|
|
|
|
static class CaretBlinker implements ActionListener
|
|
{
|
|
public void actionPerformed(ActionEvent evt)
|
|
{
|
|
if(focusedComponent != null
|
|
&& focusedComponent.hasFocus())
|
|
focusedComponent.blinkCaret();
|
|
}
|
|
}
|
|
|
|
class MutableCaretEvent extends CaretEvent
|
|
{
|
|
MutableCaretEvent()
|
|
{
|
|
super(JEditTextArea.this);
|
|
}
|
|
|
|
public int getDot()
|
|
{
|
|
return getCaretPosition();
|
|
}
|
|
|
|
public int getMark()
|
|
{
|
|
return getMarkPosition();
|
|
}
|
|
}
|
|
|
|
/*
|
|
#ifdef JDK14
|
|
class WheelHandler implements MouseWheelListener {
|
|
|
|
public void mouseWheelMoved(MouseWheelEvent e) {
|
|
if (!scrollBarsInitialized) return;
|
|
|
|
int amt = e.getWheelRotation();
|
|
//System.out.println(amt);
|
|
vertical.setValue(vertical.getValue() + amt * wheelMultiplier);
|
|
}
|
|
}
|
|
#endif
|
|
*/
|
|
|
|
class AdjustHandler implements AdjustmentListener
|
|
{
|
|
public void adjustmentValueChanged(final AdjustmentEvent evt)
|
|
{
|
|
if(!scrollBarsInitialized)
|
|
return;
|
|
|
|
// If this is not done, mousePressed events accumilate
|
|
// and the result is that scrolling doesn't stop after
|
|
// the mouse is released
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
public void run()
|
|
{
|
|
if(evt.getAdjustable() == vertical)
|
|
setFirstLine(vertical.getValue());
|
|
else
|
|
setHorizontalOffset(-horizontal.getValue());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class ComponentHandler extends ComponentAdapter
|
|
{
|
|
public void componentResized(ComponentEvent evt)
|
|
{
|
|
recalculateVisibleLines();
|
|
scrollBarsInitialized = true;
|
|
}
|
|
}
|
|
|
|
class DocumentHandler implements DocumentListener
|
|
{
|
|
public void insertUpdate(DocumentEvent evt)
|
|
{
|
|
documentChanged(evt);
|
|
|
|
int offset = evt.getOffset();
|
|
int length = evt.getLength();
|
|
|
|
int newStart;
|
|
int newEnd;
|
|
|
|
if (selectionStart > offset ||
|
|
(selectionStart == selectionEnd && selectionStart == offset))
|
|
newStart = selectionStart + length;
|
|
else
|
|
newStart = selectionStart;
|
|
|
|
if(selectionEnd >= offset)
|
|
newEnd = selectionEnd + length;
|
|
else
|
|
newEnd = selectionEnd;
|
|
|
|
select(newStart,newEnd);
|
|
}
|
|
|
|
public void removeUpdate(DocumentEvent evt)
|
|
{
|
|
documentChanged(evt);
|
|
|
|
int offset = evt.getOffset();
|
|
int length = evt.getLength();
|
|
|
|
int newStart;
|
|
int newEnd;
|
|
|
|
if(selectionStart > offset)
|
|
{
|
|
if(selectionStart > offset + length)
|
|
newStart = selectionStart - length;
|
|
else
|
|
newStart = offset;
|
|
}
|
|
else
|
|
newStart = selectionStart;
|
|
|
|
if(selectionEnd > offset)
|
|
{
|
|
if(selectionEnd > offset + length)
|
|
newEnd = selectionEnd - length;
|
|
else
|
|
newEnd = offset;
|
|
}
|
|
else
|
|
newEnd = selectionEnd;
|
|
|
|
select(newStart,newEnd);
|
|
}
|
|
|
|
public void changedUpdate(DocumentEvent evt)
|
|
{
|
|
}
|
|
}
|
|
|
|
class DragHandler implements MouseMotionListener
|
|
{
|
|
public void mouseDragged(MouseEvent evt)
|
|
{
|
|
if (popup != null && popup.isVisible()) return;
|
|
|
|
setSelectionRectangular((evt.getModifiers()
|
|
& InputEvent.CTRL_MASK) != 0);
|
|
select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY()));
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent evt) {}
|
|
}
|
|
|
|
class FocusHandler implements FocusListener
|
|
{
|
|
public void focusGained(FocusEvent evt)
|
|
{
|
|
//System.out.println("JEditTextArea: focusGained");
|
|
setCaretVisible(true);
|
|
focusedComponent = JEditTextArea.this;
|
|
}
|
|
|
|
public void focusLost(FocusEvent evt)
|
|
{
|
|
//System.out.println("JEditTextArea: focusLost");
|
|
setCaretVisible(false);
|
|
focusedComponent = null;
|
|
}
|
|
}
|
|
|
|
class MouseHandler extends MouseAdapter
|
|
{
|
|
public void mousePressed(MouseEvent evt)
|
|
{
|
|
requestFocus();
|
|
|
|
// Focus events not fired sometimes?
|
|
setCaretVisible(true);
|
|
focusedComponent = JEditTextArea.this;
|
|
|
|
// isPopupTrigger wasn't working for danh on windows
|
|
boolean trigger = (evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0;
|
|
// but it's required for macosx, since control-click does
|
|
// the same thing as a right-mouse click
|
|
if (!trigger && evt.isPopupTrigger()) trigger = true;
|
|
|
|
if (trigger && (popup != null)) {
|
|
popup.show(painter,evt.getX(),evt.getY());
|
|
return;
|
|
}
|
|
|
|
int line = yToLine(evt.getY());
|
|
int offset = xToOffset(line,evt.getX());
|
|
int dot = getLineStartOffset(line) + offset;
|
|
|
|
switch(evt.getClickCount()) {
|
|
|
|
case 1:
|
|
doSingleClick(evt,line,offset,dot);
|
|
break;
|
|
|
|
case 2:
|
|
// It uses the bracket matching stuff, so
|
|
// it can throw a BLE
|
|
try {
|
|
doDoubleClick(evt,line,offset,dot);
|
|
} catch(BadLocationException bl) {
|
|
bl.printStackTrace();
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
doTripleClick(evt,line,offset,dot);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
private void doSingleClick(MouseEvent evt, int line,
|
|
int offset, int dot) {
|
|
if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
|
rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0;
|
|
select(getMarkPosition(),dot);
|
|
} else {
|
|
setCaretPosition(dot);
|
|
}
|
|
}
|
|
|
|
|
|
private void doDoubleClick(MouseEvent evt, int line,
|
|
int offset, int dot) throws BadLocationException
|
|
{
|
|
// Ignore empty lines
|
|
if (getLineLength(line) == 0)
|
|
return;
|
|
|
|
try {
|
|
int bracket = TextUtilities.findMatchingBracket(document,
|
|
Math.max(0,dot - 1));
|
|
if (bracket != -1) {
|
|
int mark = getMarkPosition();
|
|
// Hack
|
|
if (bracket > mark) {
|
|
bracket++;
|
|
mark--;
|
|
}
|
|
select(mark,bracket);
|
|
return;
|
|
}
|
|
} catch(BadLocationException bl) {
|
|
bl.printStackTrace();
|
|
}
|
|
|
|
String noWordSep = (String)document.getProperty("noWordSep");
|
|
if(noWordSep == null)
|
|
noWordSep = "";
|
|
|
|
// Ok, it's not a bracket... select the word
|
|
String lineText = getLineText(line);
|
|
|
|
int wordStart = 0;
|
|
int wordEnd = lineText.length();
|
|
|
|
char ch = lineText.charAt(Math.max(0,offset - 1));
|
|
|
|
// special case for whitespace (fry 0122, bug #348)
|
|
// this is really nasty.. turns out that double-clicking any non-letter
|
|
// or digit char gets lumped together.. sooo, this quickly gets messy,
|
|
// because really it needs to check whether the chars are of the same
|
|
// type.. so a double space or double - might be grouped together,
|
|
// but what about a +=1? do + and - get grouped but not the 1? blech,
|
|
// coming back to this later. it's not a difficult fix, just a
|
|
// time-consuming one to track down all the proper cases.
|
|
/*
|
|
if (ch == ' ') {
|
|
//System.out.println("yeehaa");
|
|
|
|
for(int i = offset - 1; i >= 0; i--) {
|
|
if (lineText.charAt(i) == ' ') {
|
|
wordStart = i;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
for(int i = offset; i < lineText.length(); i++) {
|
|
if (lineText.charAt(i) == ' ') {
|
|
wordEnd = i + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
*/
|
|
|
|
// If the user clicked on a non-letter char,
|
|
// we select the surrounding non-letters
|
|
boolean selectNoLetter = (!Character.isLetterOrDigit(ch)
|
|
&& noWordSep.indexOf(ch) == -1);
|
|
|
|
for(int i = offset - 1; i >= 0; i--) {
|
|
ch = lineText.charAt(i);
|
|
if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) &&
|
|
noWordSep.indexOf(ch) == -1)) {
|
|
wordStart = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(int i = offset; i < lineText.length(); i++) {
|
|
ch = lineText.charAt(i);
|
|
if(selectNoLetter ^ (!Character.isLetterOrDigit(ch) &&
|
|
noWordSep.indexOf(ch) == -1)) {
|
|
wordEnd = i;
|
|
break;
|
|
}
|
|
}
|
|
//}
|
|
|
|
int lineStart = getLineStartOffset(line);
|
|
select(lineStart + wordStart,lineStart + wordEnd);
|
|
|
|
/*
|
|
String lineText = getLineText(line);
|
|
String noWordSep = (String)document.getProperty("noWordSep");
|
|
int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
|
|
int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
|
|
|
|
int lineStart = getLineStartOffset(line);
|
|
select(lineStart + wordStart,lineStart + wordEnd);
|
|
*/
|
|
}
|
|
|
|
private void doTripleClick(MouseEvent evt, int line,
|
|
int offset, int dot)
|
|
{
|
|
select(getLineStartOffset(line),getLineEndOffset(line)-1);
|
|
}
|
|
}
|
|
|
|
class CaretUndo extends AbstractUndoableEdit
|
|
{
|
|
private int start;
|
|
private int end;
|
|
|
|
CaretUndo(int start, int end)
|
|
{
|
|
this.start = start;
|
|
this.end = end;
|
|
}
|
|
|
|
public boolean isSignificant()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public String getPresentationName()
|
|
{
|
|
return "caret move";
|
|
}
|
|
|
|
public void undo() throws CannotUndoException
|
|
{
|
|
super.undo();
|
|
|
|
select(start,end);
|
|
}
|
|
|
|
public void redo() throws CannotRedoException
|
|
{
|
|
super.redo();
|
|
|
|
select(start,end);
|
|
}
|
|
|
|
public boolean addEdit(UndoableEdit edit)
|
|
{
|
|
if(edit instanceof CaretUndo)
|
|
{
|
|
CaretUndo cedit = (CaretUndo)edit;
|
|
start = cedit.start;
|
|
end = cedit.end;
|
|
cedit.die();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static
|
|
{
|
|
caretTimer = new Timer(500,new CaretBlinker());
|
|
caretTimer.setInitialDelay(500);
|
|
caretTimer.start();
|
|
}
|
|
}
|