processing.app.syntax.JEditTextArea Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pde Show documentation
Show all versions of pde Show documentation
Processing is a programming language, development environment, and online community.
This PDE package contains the Processing IDE.
/* -*- mode: java; 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 java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import javax.swing.*;
import java.awt.im.InputMethodRequests;
import java.awt.print.Printable;
import processing.app.syntax.im.InputMethodSupport;
import processing.core.PApplet;
/**
* The text area component from the JEdit Syntax (syntax.jedit.org) project.
* This is a very early version of what later was completely rewritten and
* become jEdit (jedit.org). Over the years we've also added minor features
* for use with Processing (notably mouse wheel support and copyAsHTML). [fry]
*
* 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:
*
* - More flexible key binding scheme
*
- Supports macro recorders
*
- Rectangular selection
*
- Bracket highlighting
*
- Syntax highlighting
*
- Command repetition
*
- Block caret can be enabled
*
* 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.
*
* To use it in your app, treat it like any other component, for example:
*
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"
* + "}");
*
* @author Slava Pestov
*/
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";
/** The size of the offset between the leftmost padding and the code */
public static final int leftHandGutter = 6;
private InputMethodSupport inputMethodSupport = null;
private Brackets bracketHelper = new Brackets();
/**
* Creates a new JEditTextArea with the specified settings.
* @param defaults The default settings
*/
public JEditTextArea(TextAreaDefaults defaults, InputHandler inputHandler) {
// Enable the necessary events
enableEvents(AWTEvent.KEY_EVENT_MASK);
if (!DISABLE_CARET) {
caretTimer = new Timer(500, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (hasFocus()) {
blinkCaret();
}
}
});
caretTimer.setInitialDelay(500);
caretTimer.start();
}
// Initialize some misc. stuff
painter = createPainter(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(Adjustable.VERTICAL));
add(BOTTOM, horizontal = new JScrollBar(Adjustable.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());
// send tab keys through to the text area
// http://dev.processing.org/bugs/show_bug.cgi?id=1267
setFocusTraversalKeysEnabled(false);
// Load the defaults
setInputHandler(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;
addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (scrollBarsInitialized) {
if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
int scrollAmount = e.getUnitsToScroll();
// System.out.println("rot/amt = " + e.getWheelRotation() + " " + amt);
// int max = vertical.getMaximum();
// System.out.println("UNIT SCROLL of " + amt + " at value " + vertical.getValue() + " and max " + max);
// System.out.println(" get wheel rotation is " + e.getWheelRotation());
// int ex = e.getModifiersEx();
// String mods = InputEvent.getModifiersExText(ex);
// if (ex != 0) {
// System.out.println(" 3 2 1 0");
// System.out.println(" 10987654321098765432109876543210");
// System.out.println(" " + PApplet.binary(e.getModifiersEx()));
//// if (mods.length() > 0) {
// System.out.println(" mods extext = " + mods + " " + mods.length() + " " + PApplet.hex(mods.charAt(0)));
// }
// System.out.println(" " + e);
// inertia scrolling on OS X will fire several shift-wheel events
// that are negative values.. this makes the scrolling area jump.
boolean isHorizontal = Platform.isMacOS() && e.isShiftDown();
if (isHorizontal) {
horizontal.setValue(horizontal.getValue() + scrollAmount);
}else{
vertical.setValue(vertical.getValue() + scrollAmount);
}
}
}
}
});
}
/**
* Override this to provide your own painter for this {@link JEditTextArea}.
* @param defaults
* @return a newly constructed {@link TextAreaPainter}.
*/
protected TextAreaPainter createPainter(final TextAreaDefaults defaults) {
return new TextAreaPainter(this, defaults);
}
/**
* Inline Input Method Support for Japanese.
*/
public InputMethodRequests getInputMethodRequests() {
if (Preferences.getBoolean("editor.input_method_support")) {
if (inputMethodSupport == null) {
inputMethodSupport = new InputMethodSupport(this);
}
return inputMethodSupport;
}
return null;
}
/**
* Get current position of the vertical scroll bar. [fry]
* @deprecated Use {@link #getVerticalScrollPosition()}.
*/
public int getScrollPosition() {
return getVerticalScrollPosition();
}
/**
* Set position of the vertical scroll bar. [fry]
* @deprecated Use {@link #setVerticalScrollPosition(int)}.
*/
public void setScrollPosition(int what) {
setVerticalScrollPosition(what);
}
/**
* Get current position of the vertical scroll bar.
*/
public int getVerticalScrollPosition() {
return vertical.getValue();
}
/**
* Set position of the vertical scroll bar.
*/
public void setVerticalScrollPosition(int what) {
vertical.setValue(what);
}
/**
* Get current position of the horizontal scroll bar.
*/
public int getHorizontalScrollPosition() {
return horizontal.getValue();
}
/**
* Set position of the horizontal scroll bar.
*/
public void setHorizontalScrollPosition(int what) {
horizontal.setValue(what);
}
/**
* Returns the object responsible for painting this text area.
*/
public final TextAreaPainter getPainter() {
return painter;
}
public final Printable getPrintable() {
return painter.getPrintable();
}
/**
* 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 area 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.getScrollWidth();
// Update to how horizontal scrolling is handled
// http://code.google.com/p/processing/issues/detail?id=280
// http://code.google.com/p/processing/issues/detail?id=316
//setValues(int newValue, int newExtent, int newMin, int newMax)
if (horizontalOffset < 0) {
horizontal.setValues(-horizontalOffset, painterWidth, -leftHandGutter, width);
} else {
horizontal.setValues(-leftHandGutter, painterWidth, -leftHandGutter, 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 < 0 || firstLine > getLineCount()) {
throw new IllegalArgumentException("First line out of range: "
+ firstLine + " [0, " + getLineCount() + "]");
}
if (firstLine == this.firstLine) return;
this.firstLine = firstLine;
if (firstLine != vertical.getValue()) {
updateScrollBars();
}
painter.repaint();
}
/**
* Convenience for checking what's on-screen. [fry]
*/
public final int getLastLine() {
return getFirstLine() + getVisibleLines();
}
/**
* 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();
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;
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.max(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 coordinate. 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);
} else {
// If syntax coloring is enabled, we have to do this
// because tokens can vary in width
Token tokens;
if (painter.currentLineIndex == line && painter.currentLineTokens != null) {
tokens = painter.currentLineTokens;
} else {
painter.currentLineIndex = line;
tokens = painter.currentLineTokens = tokenMarker.markTokens(lineSegment, line);
}
// 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, this);
fm = painter.getFontMetrics(styles[id]);
}
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();
// System.out.println("metrics: " + fm);
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;
// Font defaultFont = painter.getFont();
SyntaxStyle[] styles = painter.getStyles();
// System.out.println("painter is " + painter + ", doc is " + document);
for (;;) {
byte id = tokens.id;
if(id == Token.END)
return offset;
if (id == Token.NULL) {
fm = painter.getFontMetrics();
} else {
//fm = styles[id].getFontMetrics(defaultFont, this);
fm = painter.getFontMetrics(styles[id]);
}
int length = tokens.length;
for (int i = 0; i < length; i++) {
// System.out.println("segmentOffset = " + segmentOffset +
// ", offset = " + offset +
// ", i = " + i +
// ", length = " + length +
// ", array len = " + segmentArray.length);
if (segmentOffset + offset + i >= segmentArray.length) {
return segmentArray.length - segmentOffset - 1;
}
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);
bracketHelper.invalidate();
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);
bracketHelper.invalidate();
select(start, stop);
updateScrollBars();
setVerticalScrollPosition(scroll);
painter.repaint();
}
/**
* Returns the document's token marker. Equivalent to calling
* getDocument().getTokenMarker()
.
*/
public final TokenMarker getTokenMarker() {
return document.getTokenMarker();
}
/**
* Sets the document's token marker. Equivalent to caling
* getDocument().setTokenMarker()
.
* @param tokenMarker The token marker
*/
public final void setTokenMarker(TokenMarker tokenMarker) {
document.setTokenMarker(tokenMarker);
}
/**
* Returns the length of the document. Equivalent to calling
* getDocument().getLength()
.
*/
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);
return (lineElement == null) ? -1 : lineElement.getStartOffset();
}
public int getLineStartNonWhiteSpaceOffset(int line) {
int offset = getLineStartOffset(line);
int length = getLineLength(line);
String str = getText(offset, length);
for(int i = 0; i < str.length(); i++) {
if(!Character.isWhitespace(str.charAt(i))) {
return offset + i;
}
}
return offset + length;
}
/**
* 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 getLineStopOffset(int line) {
Element lineElement = document.getDefaultRootElement().getElement(line);
return (lineElement == null) ? -1 : lineElement.getEndOffset();
}
public int getLineStopNonWhiteSpaceOffset(int line) {
int offset = getLineStopOffset(line);
int length = getLineLength(line);
String str = getText(offset - length - 1, length);
for (int i = 0; i < length; i++) {
if(!Character.isWhitespace(str.charAt(length - i - 1))) {
return offset - i;
}
}
return offset - length;
}
/**
* Returns the start offset of the line after this line, or the end of
* this line if there is no next line.
* @param line The line
* @return The end offset of the specified line, or -1 if the line is
* invalid.
*/
public int getLineSelectionStopOffset(int line) {
Element lineElement = document.getDefaultRootElement().getElement(line);
return (lineElement == null) ? -1 :
Math.min(lineElement.getEndOffset(), getDocumentLength());
}
/**
* Returns the length of the specified line.
* @param line The line
*/
public int getLineLength(int line) {
Element lineElement = document.getDefaultRootElement().getElement(line);
return (lineElement == null) ? -1 :
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,getLineStopOffset(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,getLineStopOffset(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 getSelectionStop()
{
return selectionEnd;
}
/**
* Returns the offset where the selection ends on the specified
* line.
*/
public int getSelectionStop(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 getLineStopOffset(line) - 1;
}
/**
* Returns the selection end line.
*/
public final int getSelectionStopLine()
{
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()) && start != end)
{
throw new IllegalArgumentException("Bounds out of"
+ " range: " + newStart + "," +
newEnd + " [" + getDocumentLength() + "]");
}
// 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;
if (!DISABLE_CARET) {
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));
// }
}
private enum CharacterKinds {
Word,
Whitespace,
Other
}
private CharacterKinds CharacterKind( char ch, String noWordSep )
{
if ( Character.isLetterOrDigit(ch) || ch=='_' || noWordSep.indexOf(ch) != -1 )
return CharacterKinds.Word;
else if ( Character.isWhitespace(ch) )
return CharacterKinds.Whitespace;
else
return CharacterKinds.Other;
}
protected void setNewSelectionWord( int line, int offset )
{
if (getLineLength(line) == 0) {
newSelectionStart = getLineStartOffset(line);
newSelectionEnd = newSelectionStart;
return;
}
String noWordSep = (String)document.getProperty("noWordSep");
if(noWordSep == null)
noWordSep = "";
String lineText = getLineText(line);
int wordStart = 0;
int wordEnd = lineText.length();
int charPos = PApplet.constrain(offset - 1, 0, lineText.length() - 1);
char ch = lineText.charAt(charPos);
CharacterKinds thisWord = CharacterKind(ch,noWordSep);
for(int i = offset - 1; i >= 0; i--) {
ch = lineText.charAt(i);
if(CharacterKind(ch,noWordSep) != thisWord) {
wordStart = i + 1;
break;
}
}
for(int i = offset; i < lineText.length(); i++) {
ch = lineText.charAt(i);
if(CharacterKind(ch,noWordSep) != thisWord) {
wordEnd = i;
break;
}
}
int lineStart = getLineStartOffset(line);
newSelectionStart = lineStart + wordStart;
newSelectionEnd = lineStart + wordEnd;
}
/**
* 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;
}
StringBuilder sb = new StringBuilder();
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);
sb.append(seg.array,seg.offset,seg.count);
if(i != selectionEndLine)
sb.append('\n');
}
return sb.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) {
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");
} finally {
// No matter what happens... stops us from leaving document in a bad state
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(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 setSelectedText()
, 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 = getLineStopOffset(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();
StringBuilder sb = new StringBuilder();
for(int i = 0; i < repeatCount; i++)
sb.append(selection);
clipboard.setContents(new StringSelection(sb.toString()), null);
}
}
/**
* Copy the current selection as HTML, formerly "Format for Discourse".
*
* Original code by owd.
*
* Revised and updated for revision 0108 by Ben Fry (10 March 2006).
*
* Updated for 0122 to simply copy the code directly to the clipboard,
* rather than opening a new window.
*
* Updated for 0144 to only format the selected lines.
*
* Updated for 0185 to incorporate the HTML changes from the Arduino project,
* and set the formatter to always use HTML (disabling, but not removing the
* YaBB version of the code) and also fixing it for the Tools API.
*
* Updated for 0190 to simply be part of JEditTextArea, removed YaBB code.
* Simplest and most sensible to have it live here, since it's no longer
* specific to any language or version of the PDE.
*/
public void copyAsHTML() {
StringBuilder cf = new StringBuilder("\n");
int selStart = getSelectionStart();
int selStop = getSelectionStop();
int startLine = getSelectionStartLine();
int stopLine = getSelectionStopLine();
// If no selection, convert all the lines
if (selStart == selStop) {
startLine = 0;
stopLine = getLineCount() - 1;
} else {
// Make sure the selection doesn't end at the beginning of the last line
if (getLineStartOffset(stopLine) == selStop) {
stopLine--;
}
}
// Read the code line by line
for (int i = startLine; i <= stopLine; i++) {
emitAsHTML(cf, i);
}
cf.append("\n
");
HtmlSelection formatted = new HtmlSelection(cf.toString());
Clipboard clipboard = processing.app.ui.Toolkit.getSystemClipboard();
clipboard.setContents(formatted, new ClipboardOwner() {
public void lostOwnership(Clipboard clipboard, Transferable contents) {
// i don't care about ownership
}
});
}
private void emitAsHTML(StringBuilder cf, int line) {
Segment segment = new Segment();
getLineText(line, segment);
char[] segmentArray = segment.array;
int limit = segment.getEndIndex();
int segmentOffset = segment.offset;
int segmentCount = segment.count;
TokenMarker tokenMarker = getTokenMarker();
// If syntax coloring is disabled, do simple translation
if (tokenMarker == null) {
for (int j = 0; j < segmentCount; j++) {
char c = segmentArray[j + segmentOffset];
//cf = cf.append(c);
appendAsHTML(cf, c);
}
} else {
// If syntax coloring is enabled, we have to do this
// because tokens can vary in width
Token tokens;
if ((painter.getCurrentLineIndex() == line) &&
(painter.getCurrentLineTokens() != null)) {
tokens = painter.getCurrentLineTokens();
} else {
painter.setCurrentLineIndex(line);
painter.setCurrentLineTokens(tokenMarker.markTokens(segment, line));
tokens = painter.getCurrentLineTokens();
}
int offset = 0;
SyntaxStyle[] styles = painter.getStyles();
for (;;) {
byte id = tokens.id;
if (id == Token.END) {
char c = segmentArray[segmentOffset + offset];
if (segmentOffset + offset < limit) {
//cf.append(c);
appendAsHTML(cf, c);
} else {
cf.append('\n');
}
return; // cf.toString();
}
if (id != Token.NULL) {
cf.append("");
if (styles[id].isBold())
cf.append("");
}
int length = tokens.length;
for (int j = 0; j < length; j++) {
char c = segmentArray[segmentOffset + offset + j];
if (offset == 0 && c == ' ') {
// Force spaces at the beginning of the line
cf.append(" ");
} else {
appendAsHTML(cf, c);
//cf.append(c);
}
// Place close tags [/]
if (j == (length - 1) && id != Token.NULL && styles[id].isBold())
cf.append("");
if (j == (length - 1) && id != Token.NULL)
cf.append("");
}
offset += length;
tokens = tokens.next;
}
}
}
/**
* Handle encoding HTML entities for lt, gt, and anything non-ASCII.
*/
private void appendAsHTML(StringBuilder buffer, char c) {
if (c == '<') {
buffer.append("<");
} else if (c == '>') {
buffer.append(">");
} else if (c == '&') {
buffer.append("&");
} else if (c == '\'') {
buffer.append("'");
} else if (c == '"') {
buffer.append(""");
} else if (c > 127) {
buffer.append("" + ((int) c) + ";"); // use unicode entity
} else {
buffer.append(c); // normal character
}
}
/**
* Inserts the clipboard contents into the text.
*/
public void paste() {
// System.out.println("focus owner is: " + isFocusOwner());
if (editable) {
Clipboard clipboard = getToolkit().getSystemClipboard();
try {
String selection =
((String) clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor));
if (selection.contains("\r\n")) {
selection = selection.replaceAll("\r\n", "\n");
} else if (selection.contains("\r")) {
// The Mac OS MRJ doesn't convert \r to \n, so do it here
selection = selection.replace('\r','\n');
}
// Remove tabs and replace with spaces
// http://code.google.com/p/processing/issues/detail?id=69
if (selection.contains("\t")) {
int tabSize = Preferences.getInteger("editor.tabs.size");
char[] c = new char[tabSize];
Arrays.fill(c, ' ');
String tabString = new String(c);
selection = selection.replaceAll("\t", tabString);
}
// Replace unicode x00A0 (non-breaking space) with just a plain space.
// Seen often on Mac OS X when pasting from Safari. [fry 030929]
selection = selection.replace('\u00A0', ' ');
// Remove ASCII NUL characters. Reported when pasting from
// Acrobat Reader and PDF documents. [fry 130719]
// https://github.com/processing/processing/issues/1973
if (selection.indexOf('\0') != -1) {
//System.out.println("found NUL charaacters");
//int before = selection.length();
selection = selection.replaceAll("\0", "");
//int after = selection.length();
//System.out.println(before + " " + after);
}
int repeatCount = inputHandler.getRepeatCount();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < repeatCount; i++) {
sb.append(selection);
}
selection = sb.toString();
setSelectedText(selection);
} catch (Exception e) {
getToolkit().beep();
System.err.println("Clipboard does not contain a string");
DataFlavor[] flavors = clipboard.getAvailableDataFlavors();
for (DataFlavor f : flavors) {
try {
Object o = clipboard.getContents(this).getTransferData(f);
System.out.println(f + " = " + o);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
/**
* 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;
if (!DISABLE_CARET) {
caretTimer.stop();
}
}
// /**
// * 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;
}
}
*/
public void processKeyEvent(KeyEvent event) {
// this had to be added in Processing 007X, because the menu key
// events weren't making it up to the frame.
super.processKeyEvent(event);
if (inputHandler != null) {
switch (event.getID()) {
case KeyEvent.KEY_TYPED:
inputHandler.keyTyped(event);
break;
case KeyEvent.KEY_PRESSED:
inputHandler.keyPressed(event);
break;
case KeyEvent.KEY_RELEASED:
inputHandler.keyReleased(event);
break;
}
}
}
// protected members
protected static String CENTER = "center";
protected static String RIGHT = "right";
protected static String BOTTOM = "bottom";
protected Timer caretTimer;
static private final boolean DISABLE_CARET = false;
protected TextAreaPainter painter;
protected JPopupMenu popup;
protected EventListenerList eventListenerList;
protected MutableCaretEvent caretEvent;
protected boolean caretBlinks;
protected boolean caretVisible;
protected boolean blink;
protected boolean editable = true;
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 newSelectionStart; // hack to get around lack of multiple returns in Java
protected int newSelectionEnd;
protected boolean selectWord;
protected boolean selectLine;
protected int selectionAncorStart;
protected int selectionAncorEnd;
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 = bracketHelper.findMatchingBracket(document.getText(0,
document.getLength()), 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)
{
bracketHelper.invalidate();
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(line);
}
// 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();
}
}
class AdjustHandler implements AdjustmentListener
{
public void adjustmentValueChanged(final AdjustmentEvent evt)
{
if(!scrollBarsInitialized)
return;
// If this is not done, mousePressed events accumulate
// 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;
if (!selectWord && !selectLine) {
//setSelectionRectangular((evt.getModifiers() & InputEvent.CTRL_MASK) != 0);
setSelectionRectangular(evt.isControlDown());
try {
select(getMarkPosition(), xyToOffset(evt.getX(), evt.getY()));
} catch (ArrayIndexOutOfBoundsException e) {
Messages.loge("xToOffset problem", e);
}
} else {
int line = yToLine(evt.getY());
if ( selectWord ) {
setNewSelectionWord( line, xToOffset(line,evt.getX()) );
} else {
newSelectionStart = getLineStartOffset(line);
newSelectionEnd = getLineSelectionStopOffset(line);
}
if ( newSelectionStart < selectionAncorStart ) {
select(newSelectionStart,selectionAncorEnd);
} else if ( newSelectionEnd > selectionAncorEnd ) {
select(selectionAncorStart,newSelectionEnd);
} else {
select(newSelectionStart,newSelectionEnd);
}
}
}
public void mouseMoved(MouseEvent evt) {}
}
class FocusHandler implements FocusListener {
public void focusGained(FocusEvent evt) {
setCaretVisible(true);
}
public void focusLost(FocusEvent evt) {
setCaretVisible(false);
}
}
class MouseHandler extends MouseAdapter {
public void mousePressed(MouseEvent event) {
// try {
// requestFocus();
// // Focus events not fired sometimes?
// setCaretVisible(true);
// focusedComponent = JEditTextArea.this;
// Here be dragons: for release 0195, this fixes a problem where the
// line segment data from the previous window was being used for
// selections, causing an exception when the window you're clicking to
// was not full of text. Simply ignoring clicks when not focused fixes
// the problem, though it's not clear why the wrong Document data was
// being using regardless of the focusedComponent.
// if (focusedComponent != JEditTextArea.this) return;
if (!hasFocus()) {
// System.out.println("requesting focus in window");
// The following condition check fixes #3649 [manindra, 08/20/15]
if(!requestFocusInWindow()) {
return;
}
}
// isPopupTrigger() is handled differently across platforms,
// so it may fire during release, or during the press.
// http://docs.oracle.com/javase/7/docs/api/java/awt/event/MouseEvent.html#isPopupTrigger()
// However, we have to exit out of this method if it's a right-click
// anyway, because otherwise it'll de-select the current word.
// As a result, better to just check for BUTTON3 now, indicating that
// isPopupTrigger() is going to fire on the release anyway.
boolean windowsRightClick =
Platform.isWindows() && (event.getButton() == MouseEvent.BUTTON3);
if ((event.isPopupTrigger() || windowsRightClick) && (popup != null)) {
// // Windows fires the popup trigger on release (see mouseReleased() below)(
// if (!Base.isWindows()) {
// if (event.isPopupTrigger() && (popup != null)) {
// If user right-clicked inside the selection, preserve it;
// move caret to click offset otherwise
int offset = xyToOffset(event.getX(), event.getY());
int selectionStart = getSelectionStart();
int selectionStop = getSelectionStop();
if (offset < selectionStart || offset >= selectionStop) {
select(offset, offset);
}
popup.show(painter, event.getX(), event.getY());
return;
// }
}
int line = yToLine(event.getY());
int offset = xToOffset(line, event.getX());
int dot = getLineStartOffset(line) + offset;
selectLine = false;
selectWord = false;
switch (event.getClickCount()) {
case 1:
doSingleClick(event,line,offset,dot);
break;
case 2:
// It uses the bracket matching stuff, so it can throw a BLE
try {
doDoubleClick(event, line, offset, dot);
} catch (BadLocationException bl) {
bl.printStackTrace();
}
break;
case 3:
doTripleClick(event,line,offset,dot);
break;
}
// } catch (ArrayIndexOutOfBoundsException aioobe) {
// aioobe.printStackTrace();
// int line = yToLine(evt.getY());
// System.out.println("line is " + line + ", line count is " + getLineCount());
// }
}
/*
// Because isPopupTrigger() is handled differently across platforms,
// it may fire during release, or during the press.
// http://docs.oracle.com/javase/7/docs/api/java/awt/event/MouseEvent.html#isPopupTrigger()
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger() && (popup != null)) {
popup.show(painter, event.getX(), event.getY());
}
}
*/
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) {
try {
String text = document.getText(0, document.getLength());
int bracket = bracketHelper.findMatchingBracket(text, 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();
}
setNewSelectionWord( line, offset );
select(newSelectionStart,newSelectionEnd);
selectWord = true;
selectionAncorStart = selectionStart;
selectionAncorEnd = selectionEnd;
/*
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) {
selectLine = true;
select(getLineStartOffset(line),getLineSelectionStopOffset(line));
selectionAncorStart = selectionStart;
selectionAncorEnd = selectionEnd;
}
}
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;
}
return false;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy