Package org.syntax.jedit

Source Code of org.syntax.jedit.JEditTextArea$CaretUndo

/*
*  soapUI, copyright (C) 2004-2011 eviware.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:
* <ul>
* <li>More flexible key binding scheme
* <li>Supports macro recorders
* <li>Rectangular selection
* <li>Bracket highlighting
* <li>Syntax highlighting
* <li>Command repetition
* <li>Block caret can be enabled
* </ul>
* It is also faster and doesn't have as many problems. It can be used in other
* applications; the only other part of jEdit it depends on is the syntax
* package.
* <p>
*
* To use it in your app, treat it like any other component, for example:
*
* <pre>
* JEditTextArea ta = new JEditTextArea();
* ta.setTokenMarker( new JavaTokenMarker() );
* ta.setText( &quot;public class Test {\n&quot; + &quot;    public static void main(String[] args) {\n&quot;
*     + &quot;        System.out.println(\&quot;Hello World\&quot;);\n&quot; + &quot;    }\n&quot; + &quot;}&quot; );
* </pre>
*
* @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<JEditTextArea>( 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
   * <code>getDocument().getTokenMarker()</code>.
   */
  public final TokenMarker getTokenMarker()
  {
    return document.getTokenMarker();
  }

  /**
   * Sets the document's token marker. Equivalent to caling
   * <code>getDocument().setTokenMarker()</code>.
   *
   * @param tokenMarker
   *           The token marker
   */
  public final void setTokenMarker( TokenMarker tokenMarker )
  {
    document.setTokenMarker( tokenMarker );
  }

  /**
   * Returns the length of the document. Equivalent to calling
   * <code>getDocument().getLength()</code>.
   */
  public final int getDocumentLength()
  {
    return document.getLength();
  }

  /**
   * Returns the number of lines in the document.
   */
  public final int getLineCount()
  {
    return document.getDefaultRootElement().getElementCount();
  }

  /**
   * Returns the line containing the specified offset.
   *
   * @param offset
   *           The offset
   */
  public final int getLineOfOffset( int offset )
  {
    return document.getDefaultRootElement().getElementIndex( offset );
  }

  /**
   * Returns the start offset of the specified line.
   *
   * @param line
   *           The line
   * @return The start offset of the specified line, or -1 if the line is
   *         invalid
   */
  public int getLineStartOffset( int line )
  {
    Element lineElement = document.getDefaultRootElement().getElement( line );
    if( lineElement == null )
      return -1;
    else
      return lineElement.getStartOffset();
  }

  /**
   * Returns the end offset of the specified line.
   *
   * @param line
   *           The line
   * @return The end offset of the specified line, or -1 if the line is
   *         invalid.
   */
  public int getLineEndOffset( int line )
  {
    Element lineElement = document.getDefaultRootElement().getElement( line );
    if( lineElement == null )
      return -1;
    else
      return lineElement.getEndOffset();
  }

  /**
   * Returns the length of the specified line.
   *
   * @param line
   *           The line
   */
  public int 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 &lt; end, and end if end &gt; 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 <code>setSelectedText()</code>, but overstrikes the appropriate
   * number of characters if overwrite mode is enabled.
   *
   * @param str
   *           The string
   * @see #setSelectedText(String)
   * @see #isOverwriteEnabled()
   */
  public void overwriteSetSelectedText( String str )
  {
    // Don't overstrike if there is a selection
    if( !overwrite || selectionStart != selectionEnd )
    {
      setSelectedText( str );
      return;
    }

    // Don't overstrike if we're on the end of
    // the line
    int caret = getCaretPosition();
    int caretLineEnd = getLineEndOffset( getCaretLine() );
    if( caretLineEnd - caret <= str.length() )
    {
      setSelectedText( str );
      return;
    }

    document.beginCompoundEdit();

    try
    {
      document.remove( caret, str.length() );
      document.insertString( caret, str, null );
    }
    catch( BadLocationException bl )
    {
      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' );

        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<JEditTextArea> 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<Component> leftOfScrollBar = new Vector<Component>();
  }

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

Related Classes of org.syntax.jedit.JEditTextArea$CaretUndo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.