All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy