org.syntax.jedit.JEditTextArea Maven / Gradle / Ivy
The newest version!
/*
* soapUI, copyright (C) 2004-2011 smartbear.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package org.syntax.jedit;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.lang.ref.WeakReference;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.EventListenerList;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import org.syntax.jedit.tokenmarker.Token;
import org.syntax.jedit.tokenmarker.TokenMarker;
import com.eviware.soapui.SoapUI;
/**
* 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
* @version $Id$
*/
public class JEditTextArea extends JComponent implements Scrollable
{
/**
* 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 final 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 = createPainter( defaults );
documentHandler = new DocumentHandler();
listenerList = new EventListenerList();
caretEvent = new MutableCaretEvent();
lineSegment = new Segment();
bracketLine = bracketPosition = -1;
blink = true;
setAutoscrolls( true );
// Initialize the GUI
// setLayout(new ScrollLayout());
// add(CENTER,painter);
setLayout( new BorderLayout() );
add( painter, BorderLayout.CENTER );
// setBackground( Color.WHITE );
// setBorder( null );
// add(RIGHT,vertical = new JScrollBar(JScrollBar.VERTICAL));
// add(BOTTOM,horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
// Add some event listeners
// vertical.addAdjustmentListener(new AdjustHandler());
// horizontal.addAdjustmentListener(new AdjustHandler());
painter.addComponentListener( new ComponentHandler() );
painter.addMouseListener( new MouseHandler() );
painter.addMouseMotionListener( new DragHandler() );
addFocusListener( new FocusHandler() );
// Load the defaults
setInputHandler( defaults.inputHandler );
setDocument( defaults.document );
editable = defaults.editable;
caretVisible = defaults.caretVisible;
caretBlinks = defaults.caretBlinks;
// electricScroll = defaults.electricScroll;
popup = defaults.popup;
// We don't seem to get the initial focus event?
focusedComponentRef = new WeakReference( this );
addMouseWheelListener( new MouseWheelListener()
{
public void mouseWheelMoved( MouseWheelEvent e )
{
if( ( e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ) == Toolkit
.getDefaultToolkit().getMenuShortcutKeyMask() )
{
int caretLine = getCaretLine();
// int caretPosition = getCaretPosition();
int newLine = caretLine + e.getWheelRotation();
if( newLine < 0 )
newLine = 0;
else if( newLine > getLineCount() - 1 )
newLine = getLineCount() - 1;
int newPos = getLineStartOffset( newLine );
setCaretPosition( newPos );
}
else
{
Rectangle rect = getVisibleRect();
rect.setLocation( ( int )rect.getX(),
( int )rect.getY() + painter.getFontMetrics().getHeight() * 3 * e.getWheelRotation() );
scrollRectToVisible( rect );
}
}
} );
}
/**
* Allow subclasses to override.
*
* @param defaults
* @return
* @author lars
*/
protected TextAreaPainter createPainter( TextAreaDefaults defaults )
{
return new TextAreaPainter( this, defaults );
}
/**
* 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 && caretVisible )
{
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()
{
revalidate();
}
/**
* 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();
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 void 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) { scrollRectToVisible( new Rectangle( horizontalOffset,
* firstLine*painter.getFontMetrics().getHeight(), 1, 1));
*
* 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 void scrollToCaret()
{
int line = getCaretLine();
int lineStart = getLineStartOffset( line );
int offset = Math.max( 0, Math.min( getTabExpandedLineLength( line ) - 1, getCaretPosition() - lineStart ) );
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 void 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 >= getVisibleRect().getWidth() ) {
* newHorizontalOffset = horizontalOffset +
* (x-(int)getVisibleRect().getWidth()) + width + 5; }
*/
if( offset > 0 )
x += ( width + 5 );
int y = lineToY( line );
if( line > 0 )
y += 5;
if( line > 0 )
line++ ;
scrollRectToVisible( new Rectangle( x, y, 1, painter.getFontMetrics().getHeight() ) );
updateScrollBars();
painter.repaint();
// setOrigin(line, x);
}
/**
* 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() );
}
public int getLineHeight()
{
FontMetrics fm = painter.getFontMetrics();
return fm.getHeight();
}
/**
* 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 = 0; // -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 = 0; // -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 );
}
public int pointToOffset( Point pt )
{
return xyToOffset( ( int )pt.getX(), ( int )pt.getY() );
}
/**
* 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;
if( getParent() != null )
document.addDocumentListener( documentHandler );
select( 0, 0 );
updateScrollBars();
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 );
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 getTabExpandedLineLength( int line )
{
Element lineElement = document.getDefaultRootElement().getElement( line );
if( lineElement == null )
return -1;
int length = lineElement.getEndOffset() - lineElement.getStartOffset() - 1;
try
{
String txt = document.getText( lineElement.getStartOffset(), length );
for( int c = 0; c < txt.length(); c++ )
if( txt.charAt( c ) == '\t' )
length += 7;
return length;
}
catch( BadLocationException e )
{
e.printStackTrace();
return length;
}
}
/**
* 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;
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 )
{
SoapUI.logError( bl );
return null;
}
}
/**
* Sets the entire text of this text area.
*/
public synchronized void setText( String text )
{
try
{
document.beginCompoundEdit();
document.remove( 0, document.getLength() );
document.insertString( 0, text, null );
revalidate();
}
catch( BadLocationException bl )
{
SoapUI.logError( bl );
}
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 )
{
SoapUI.logError( bl );
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 )
{
SoapUI.logError( bl );
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 );
}
/**
* 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();
}
/**
* 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 )
{
SoapUI.logError( bl );
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 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 = 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 )
{
SoapUI.logError( bl );
}
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 overwrite
* 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 )
{
listenerList.add( CaretListener.class, listener );
}
/**
* Removes a caret change listener from this text area.
*
* @param listener
* The listener
*/
public final void removeCaretListener( CaretListener listener )
{
listenerList.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' );
// after previous in come cases we get double \n so just remove one
selection = selection.replace( "\n\n", "\n" );
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( focusedComponentRef != null && focusedComponentRef.get() == this )
focusedComponentRef = null;
if( this.document != null )
this.document.removeDocumentListener( documentHandler );
}
@Override
public void addNotify()
{
super.addNotify();
if( this.document != null )
this.document.addDocumentListener( documentHandler );
}
/**
* Forwards key events directly to the input handler. This is slightly faster
* than using a KeyListener because some Swing overhead is avoided.
*/
public void processKeyEvent( KeyEvent evt )
{
if( inputHandler == null )
return;
switch( evt.getID() )
{
case KeyEvent.KEY_TYPED :
inputHandler.keyTyped( evt );
break;
case KeyEvent.KEY_PRESSED :
inputHandler.keyPressed( evt );
break;
case KeyEvent.KEY_RELEASED :
inputHandler.keyReleased( evt );
break;
}
if( !evt.isConsumed() )
{
KeyListener[] keyListeners = getKeyListeners();
for( KeyListener listener : keyListeners )
{
switch( evt.getID() )
{
case KeyEvent.KEY_TYPED :
listener.keyTyped( evt );
break;
case KeyEvent.KEY_PRESSED :
listener.keyPressed( evt );
break;
case KeyEvent.KEY_RELEASED :
listener.keyReleased( evt );
break;
}
if( evt.isConsumed() )
break;
}
if( !evt.isConsumed() )
getParent().dispatchEvent( evt );
}
}
// protected members
protected static final String CENTER = "center";
protected static final String RIGHT = "right";
protected static final String BOTTOM = "bottom";
protected static WeakReference focusedComponentRef;
protected static final Timer caretTimer;
protected TextAreaPainter painter;
protected JPopupMenu popup;
protected EventListenerList listenerList;
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 = listenerList.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 )
{
SoapUI.logError( bl );
}
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
{
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;
*/
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 - ileft - iright;
int centerHeight = size.height - itop - ibottom;
center.setBounds( ileft, itop, centerWidth, 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( focusedComponentRef != null && focusedComponentRef.get() != null && focusedComponentRef.get().hasFocus() )
focusedComponentRef.get().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 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 )
{
if( isEditable() )
setCaretVisible( true );
focusedComponentRef = new WeakReference( JEditTextArea.this );
}
public void focusLost( FocusEvent evt )
{
setCaretVisible( false );
focusedComponentRef = null;
}
}
class MouseHandler extends MouseAdapter
{
@Override
public void mouseClicked( MouseEvent e )
{
if( popup != null && e.isPopupTrigger() )
{
doPopup( e );
}
}
private void doPopup( MouseEvent evt )
{
popup.show( painter, evt.getX(), evt.getY() );
}
@Override
public void mouseReleased( MouseEvent e )
{
if( popup != null && e.isPopupTrigger() )
{
doPopup( e );
}
}
public void mousePressed( MouseEvent evt )
{
requestFocus();
// Focus events not fired sometimes?
if( isEditable() )
setCaretVisible( true );
focusedComponentRef = new WeakReference( JEditTextArea.this );
if( popup != null && evt.isPopupTrigger() )
{
doPopup( evt );
return;
}
if( evt.getButton() != MouseEvent.BUTTON1 )
{
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 )
{
SoapUI.logError( bl );
}
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( getTabExpandedLineLength( 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 )
{
SoapUI.logError( bl );
}
// Ok, it's not a bracket... select the word
String lineText = getLineText( line );
char ch = lineText.charAt( Math.max( 0, offset - 1 ) );
String noWordSep = ( String )document.getProperty( "noWordSep" );
if( noWordSep == null )
noWordSep = "";
// If the user clicked on a non-letter char,
// we select the surrounding non-letters
boolean selectNoLetter = ( !Character.isLetterOrDigit( ch ) && noWordSep.indexOf( ch ) == -1 );
int wordStart = 0;
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;
}
}
int wordEnd = lineText.length();
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();
}
public Dimension getPreferredSize()
{
Dimension preferredSize = painter.getPreferredSize();
if( getParent() instanceof JViewport )
{
JViewport viewport = ( JViewport )getParent();
Dimension size = viewport.getSize();
preferredSize = new Dimension( ( int )( preferredSize.getWidth() < size.getWidth() ? size.getWidth()
: preferredSize.getWidth() ), ( int )( preferredSize.getHeight() < size.getHeight() ? size.getHeight()
: preferredSize.getHeight() ) );
}
return preferredSize;
}
public Dimension getMaximumSize()
{
return painter.getMaximumSize();
}
public Dimension getMinimumSize()
{
return painter.getMinimumSize();
}
public int getMaxLineLength()
{
int max = 0;
for( int c = 0; c < getLineCount(); c++ )
{
int lineLength = getTabExpandedLineLength( c );
if( lineLength > max )
max = lineLength;
}
return max;
}
public Dimension getPreferredScrollableViewportSize()
{
return getPreferredSize();
}
public int getScrollableBlockIncrement( Rectangle arg0, int arg1, int arg2 )
{
return getFontMetrics( getFont() ).getHeight() * 5;
}
public boolean getScrollableTracksViewportHeight()
{
return false;
}
public boolean getScrollableTracksViewportWidth()
{
return false;
}
public int getScrollableUnitIncrement( Rectangle arg0, int arg1, int arg2 )
{
return getFontMetrics( getFont() ).getHeight();
}
}